diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 00000000..0d579851 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,78 @@ +version: 'build #{build}' + +environment: + matrix: + - linking: shared + compiler: msvc2013 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 + - linking: static + compiler: msvc2013 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 + - linking: shared + compiler: msvc2015 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + - linking: static + compiler: msvc2015 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + - linking: shared + compiler: msvc2017 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + - linking: static + compiler: msvc2017 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + - linking: static + compiler: mingw + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + +platform: + - Win32 + - x64 + +before_build: + - if %compiler%==msvc2013 ( set "CMAKE_GENERATOR=Visual Studio 12 2013" ) + else if %compiler%==msvc2015 ( set "CMAKE_GENERATOR=Visual Studio 14 2015" ) + else if %compiler%==msvc2017 ( set "CMAKE_GENERATOR=Visual Studio 15 2017" ) + else if %compiler%==mingw ( set "CMAKE_GENERATOR=MinGW Makefiles" ) + - if %compiler%-%platform%==mingw-Win32 ( set "PATH=C:\msys64\mingw32\bin;%PATH%" ) + else if %compiler%-%platform%==mingw-x64 ( set "PATH=C:\msys64\mingw64\bin;%PATH%" ) + else if %platform%==x64 ( set "CMAKE_GENERATOR=%CMAKE_GENERATOR% Win64" ) + - if %linking%==static ( set CMAKE_FLAGS=-DBUILD_SHARED_LIBS=OFF ) + - if %compiler%==mingw ( set "outdir=build\out" ) else ( set "outdir=build\out\Release" ) + - ren "C:\Program Files\Git\usr\bin\sh.exe" _sh.exe + - set "simultaneous=3" + +build_script: + - md build && cd build + - if not %compiler%==mingw ( set "CFLAGS=/MP%simultaneous% %CFLAGS%" ) + - if not %compiler%==mingw ( set "CPPFLAGS=/MP%simultaneous% %CPPFLAGS%" ) + - if not %compiler%==mingw ( set "CXXFLAGS=/MP%simultaneous% %CXXFLAGS%" ) + - cmake -G "%CMAKE_GENERATOR%" %CMAKE_FLAGS% .. + - if %compiler%==mingw ( mingw32-make -j%simultaneous% tester examples ) + else ( msbuild libui.sln /t:Build /p:Configuration=Release /p:Platform=%platform% ) + - cd %APPVEYOR_BUILD_FOLDER% + +after_build: + - if %platform%==x64 ( set "arch=amd64" ) else ( set "arch=386" ) + - if %APPVEYOR_REPO_TAG%==true ( set "version=%APPVEYOR_REPO_TAG_NAME%" ) + else ( set "version=%APPVEYOR_REPO_BRANCH%" ) + - if %linking%==shared ( set "artifact=shared" ) else ( set "artifact=%compiler%-static" ) + - set "artifact=%version%-windows-%arch%-%artifact%" + - del .\%outdir%\libui.exp # remove unnecessary files + - 7z a libui-%artifact%.zip .\%outdir%\libui.* ui.h ui_windows.h + - 7z l libui-%artifact%.zip + - 7z a examples-%artifact%.zip .\%outdir%\*.exe + - 7z l examples-%artifact%.zip + +artifacts: + - path: libui-*.zip + name: libui + - path: examples-*.zip + name: examples + +deploy: + provider: GitHub + artifact: libui, examples + auth_token: + secure: li92W7mFAC8HbAVeZN6Ugmo5H1GzKSjr6DXlMniLcCRspKmi2Nz1nlslSa+9sLfo + on: + appveyor_repo_tag: true # deploy on tag push only diff --git a/.travis.yml b/.travis.yml index 82070d64..1d969f33 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,97 @@ -os: - - linux - -# This makes us use Ubuntu 14 instead of 12 -dist: trusty - language: c -script: - - sudo apt-get update - - sudo apt-get install libgtk-3-dev -y || sudo apt-cache search libgtk3 - - make -f GNUmakefile - - make -f GNUmakefile test - - make -f GNUmakefile examples -# TODO osx -# need to figure out how to force language: objective-c and turn off the apt-get rules +include: &toolchain_linux_amd64 + os: linux + dist: trusty + addons: + apt: + update: true + packages: + - libgtk-3-dev + +include: &toolchain_linux_386 + os: linux + dist: trusty + addons: + apt: + packages: + - gcc-multilib + - g++-multilib + - libgtk-3-dev:i386 + # the rest fixes broken dependencies of libgtk:i386 + - libgirepository-1.0-1:i386 + - libglib2.0-dev:i386 + - gir1.2-glib-2.0:i386 + - gir1.2-atk-1.0:i386 + - libatk1.0-dev:i386 + - libfreetype6-dev:i386 + - libfontconfig1-dev:i386 + - libcairo2-dev:i386 + - libgdk-pixbuf2.0-dev:i386 + - libpango1.0-dev:i386 + - libxft-dev:i386 + - libpng12-dev:i386 + +include: &toolchain_osx_amd64 + os: osx + osx_image: xcode8 + +# Travis CI build matrix. +# Each entry below will trigger an extra, parallel build on Travis. +matrix: + include: + - env: linking=shared arch=amd64 + <<: *toolchain_linux_amd64 + - env: linking=static arch=amd64 + <<: *toolchain_linux_amd64 + - env: linking=shared arch=386 + <<: *toolchain_linux_386 + - env: linking=static arch=386 + <<: *toolchain_linux_386 + - env: linking=shared arch=amd64 + <<: *toolchain_osx_amd64 + - env: linking=static arch=amd64 + <<: *toolchain_osx_amd64 + +install: + - if [[ "${arch}" == "386" ]]; then + export CFLAGS=-m32; + export CXXFLAGS=-m32; + export PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig; + fi + - if [[ "${linking}" == "static" ]]; then + export CMAKE_FLAGS=-DBUILD_SHARED_LIBS=OFF; + fi + +script: + - cmake --version + - mkdir build + - pushd build + - cmake -G "Unix Makefiles" ${CMAKE_FLAGS} .. + - make tester examples + - popd + +after_success: + - ls -lR build/out + - file build/out/test + - export platform="$TRAVIS_OS_NAME" + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cp ui.h ui_darwin.h build/out/; export platform=darwin; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then cp ui.h ui_unix.h build/out/; fi + - if [[ "x${TRAVIS_TAG}" != "x" ]]; then export version=${TRAVIS_TAG}; else export version=${TRAVIS_BRANCH}; fi + - export artifact=${version}-${platform}-${arch}-${linking} + - echo ${artifact} + - pushd build/out + - # TODO do not include symlinks in the archive + - tar -czvf libui-${artifact}.tgz libui.* *.h + - tar -czvf examples-${artifact}.tgz `find . -type f ! -name "*.*"` + - popd + +deploy: + provider: releases + api_key: + secure: "fmgC97mlXQY/ASWAL/GyTJfiJIo/hsVFf6bP3Zz8odv259PJUFGgnZ9kNIgJcFQ5961lXDFi7pBMMSetm1GZ2EBZxIXnUfe1kfIhw62ybJHIwB2+g2tc8A4zzfkWJVY4xVYpitOU3iMuu5Z8U2n+68RYWKpcxhbkVw5v8Zu2Rms=" + file: build/out/*.tgz + file_glob: true + skip_cleanup: true + on: + tags: true diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..c7193fbb --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,238 @@ +# 3 june 2016 +# see https://cmake.org/gitweb?p=cmake.git;a=commit;h=95cdf132489c79e88a10fdf7a7566fa002c7680b (thanks ngladitz in irc.freenode.net/#cmake) +cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR) + +# TODOs +# - silence entering/leaving messages? +# - uname -s for more refined OS control +# - Haiku for haiku +# - debian DESTDIR? https://github.com/andlabs/libui/pull/10 +# - libui-combined* needs to be deleted so that custom command can run every time +# - add notelemetry.obj to *ALL TARGETS* on VS2015 and up - https://www.infoq.com/news/2016/06/visual-cpp-telemetry +# - switch to 3.1.0 features + +# the docs say we need to set this up prior to project() +# the docs don't say this, but the CACHE part is important; see https://stackoverflow.com/questions/34208360/cmake-seems-to-ignore-cmake-osx-deployment-target +# TODO figure out what other variables must be set with CACHE +# TODO figure out if FORCE is needed here +# TODO figure out whether STRING "" is best or if something else is better; also what FORCE does because I forget and later I say it's needed +set(CMAKE_OSX_DEPLOYMENT_TARGET "10.8" CACHE STRING "" FORCE) + +# we want to disable incremental linking +# see also: +# - https://github.com/bulletphysics/bullet3/blob/master/CMakeLists.txt#L43 +# - https://cmake.org/pipermail/cmake/2010-February/035174.html +# this must also go before project() +set(MSVC_INCREMENTAL_DEFAULT ON) + +# default to debug builds +# do this before project() just to be safe +# either DEBUG or Debug will work; we use DEBUG as that's what cmake does internally (https://cmake.org/pipermail/cmake/2013-June/055177.html) +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE DEBUG CACHE STRING "" FORCE) +endif() + +project(libui) +option(BUILD_SHARED_LIBS "Whether to build libui as a shared library or a static library" ON) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/out") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/out") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/out") +set(CMAKE_PDB_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/out") + +if(APPLE) + set(_OSNAME darwin) + set(_HASVERSION TRUE) + set(_VERSION "A") + + # always use our rpath + set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) + # the / is required by some older versions of OS X + set(CMAKE_INSTALL_RPATH "@executable_path/") + set(CMAKE_MACOSX_RPATH TRUE) +elseif(WIN32) + set(_OSNAME windows) + + # and don't include the default libraries with ANY of the builds + # note the CACHE FORCE stuff is required here + set(CMAKE_C_STANDARD_LIBRARIES CACHE STRING "" FORCE) + set(CMAKE_CXX_STANDARD_LIBRARIES CACHE STRING "" FORCE) +else() + set(_OSNAME unix) + set(_HASVERSION TRUE) + set(_VERSION "0") + + # always use our rpath + set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) + set(CMAKE_INSTALL_RPATH "\$ORIGIN") +endif() + +# common flags +if(MSVC) + # TODO subsystem version + + # TODO /Wall does too much + # TODO -Wno-switch equivalent + # TODO /sdl turns C4996 into an ERROR + # don't use /analyze; that requires us to write annotations everywhere + # TODO undecided flags from qo? + # the RTC flags are only supplied in debug builds because they are only supposed to be used by debug builds (see "This is because run-time error checks are not valid in a release (optimized) build." in https://docs.microsoft.com/cpp/build/reference/rtc-run-time-error-checks) + # /RTCc is not supplied because it's discouraged as of VS2015; see https://www.reddit.com/r/cpp/comments/46mhne/rtcc_rejects_conformant_code_with_visual_c_2015/d06auq5 + # /EHsc is to shut the compiler up in some cases + # TODO make /EHsc C++-only + set(_COMMON_CFLAGS + /W4 /wd4100 + /bigobj /nologo + $<$:/RTC1 /RTCs /RTCu> + /EHsc + ) + + # note the /MANIFEST:NO (which must be / and uppercase); thanks FraGag (https://github.com/andlabs/libui/issues/93#issuecomment-223183436) + # TODO warnings on undefined symbols + set(_COMMON_LDFLAGS + /LARGEADDRESSAWARE + /NOLOGO + /INCREMENTAL:NO + /MANIFEST:NO + ) + + # TODO autogenerate a .def file? + + # more incremental linking fixes + # TODO actually get rid of incremental linking here +else() + set(_COMMON_CFLAGS + -Wall -Wextra -pedantic + -Wno-unused-parameter + -Wno-switch + -fvisibility=hidden + ) + # don't use C_VERSION or CXX_VERSION because they use GNU standards + # TODO we can actually do this; set both C_EXTENSIONS and CXX_EXTENSIONS to OFF + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --std=c99") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++11") + + set(_COMMON_LDFLAGS + -fvisibility=hidden + ) + + # don't require shipping the MinGW-w64 DLLs + if(WIN32) + list(APPEND _COMMON_LDFLAGS + -static + -static-libgcc + -static-libstdc++ + ) + endif() + + # TODO document this + if(ADDRESS_SANITIZER) + set(_COMMON_CFLAGS ${_COMMON_CFLAGS} + -fsanitize=address + -fno-omit-frame-pointer + ) + set(_COMMON_LDFLAGS ${_COMMON_LDFLAGS} + -fsanitize=address + -fno-omit-frame-pointer + ) + endif() +endif() + +# problem: +# - target_link_libraries() only supports - for flags +# - but cmake only doesn't generate the manifest if the flag has a / +macro(_target_link_options_private _target) + foreach(_opt IN LISTS ${ARGN}) + set_property(TARGET ${_target} APPEND_STRING PROPERTY + LINK_FLAGS " ${_opt}") + endforeach() +endmacro() + +add_subdirectory("common") +add_subdirectory("${_OSNAME}") +add_library(libui ${_LIBUI_SOURCES}) +target_include_directories(libui + PUBLIC . + PRIVATE ${_LIBUI_INCLUEDIRS}) +target_compile_definitions(libui + PRIVATE ${_LIBUI_DEFS}) +# cmake produces this for us by default but only for shared libraries +target_compile_definitions(libui + PRIVATE libui_EXPORTS) +target_compile_options(libui + PUBLIC ${_COMMON_CFLAGS} + PRIVATE ${_LIBUI_CFLAGS}) +# TODO link directories? +if(BUILD_SHARED_LIBS) + target_link_libraries(libui + PRIVATE ${_LIBUI_LIBS}) +endif() +# TODO INTERFACE libs don't inherit to grandhcildren? +# on Windows the linker for static libraries is different; don't give it the flags +if(BUILD_SHARED_LIBS) + _target_link_options_private(libui + _COMMON_LDFLAGS + _LIBUI_LDFLAGS) +endif() +if(NOT BUILD_SHARED_LIBS) + # TODO figure out a way to tell libui that it's static + target_compile_definitions(libui + PUBLIC _UI_STATIC) +endif() +if(NOT MSVC) + # on non-MSVC compilers cmake adds an extra lib- + # note that we apply this to libui, not to any intermediates + set_target_properties(libui PROPERTIES + OUTPUT_NAME ui) + + # flags for warning on undefined symbols + # TODO figure out why FreeBSD follows linked libraries here + # TODO figure out MSVC equivalents + if(BUILD_SHARED_LIBS) + if(NOT (${CMAKE_SYSTEM_NAME} STREQUAL FreeBSD)) + # on OS X we don't need to do this; Apple's linker warns about undefined symbols in -shared builds! + if(NOT APPLE) + target_link_libraries(libui + PRIVATE -Wl,--no-undefined -Wl,--no-allow-shlib-undefined + ) + endif() + endif() + endif() +endif() +if(BUILD_SHARED_LIBS) + if(_HASVERSION) + set_target_properties(libui PROPERTIES + SOVERSION "${_VERSION}") + endif() +endif() +# TODO why is this not a default?! +set_property(TARGET libui PROPERTY + POSITION_INDEPENDENT_CODE True) + +macro(_add_exec _name) + # TODOTODO re-add WIN32 when merging back + add_executable(${_name} + EXCLUDE_FROM_ALL + ${ARGN}) + target_link_libraries(${_name} libui) + _target_link_options_private(${_name} + _COMMON_LDFLAGS) + # TODO does this propagate? + set_property(TARGET ${_name} PROPERTY + POSITION_INDEPENDENT_CODE True) + # TODO see above about INTERFACE + if(NOT BUILD_SHARED_LIBS) + target_link_libraries(${_name} + ${_LIBUI_LIBS}) + endif() + + # TODOfor some reason these don't propagate + if(NOT WIN32) + target_include_directories(${_name} + PUBLIC .) + target_compile_options(${_name} + PUBLIC ${_COMMON_CFLAGS}) + endif() +endmacro() +add_subdirectory("test") +add_subdirectory("examples") diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..94bd199b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,131 @@ +# Contributing to libui + +libui is an open source project that openly accepts contributions. I appreciate your help! + +## Rules for contributing code + +While libui is open to contributions, a number of recent, significantly large contributions and uncontributed forks have recently surfaced that do not present themselves in a form that makes it easy for libui to accept them. In order to give your contribution a high chance of being accepted into libui, please keep the following in mind as you prepare your contribution. + +### Commit messages and pull request description + +libui does not enforce rules about the length or detail that a commit message. I'm not looking for an essay. However, single-word descriptions of nontrivial changes are *not* acceptable. I should be able to get a glimpse of what a commit does from the commit message, even if it's just one sentence to describe a trivial change. (Yes, I know I haven't followed this rule strictly myself, but I try not to break it too.) And a commit message should encompass everything; typically, I make a number of incremental commits toward a feature, so the commit messages don't have to be too long to explain everything. + +Your pull request description, on the other hand, must be a summary of the sum total of all the changes made to libui. Don't just drop a pull request on me with a one-line-long elevator pitch of what you added. Describe your proposed API changes, implementation requirements, and any important consequences of your work. + +### Code formatting + +libui uses K&R C formatting rules for overall code structure: spaces after keywords like `if`, `{` on the same line as a statement with a space, `{` on its own line after a function or method signature (even those inside the class body), no space after the name of a function, etc. + +Use hard tabs, NOT spaces, for indentation. I use a proportional-width font and my text editor doesn't set tabs to a multiple of the space width, so I *will* be able to tell. If you use a fixed-width font, I suggest setting a tab width of 4 spaces per tab, but don't put diagrams in comments with hard tabs, because not everyone does this. + +Expressions should have a space around binary operators, and use parentheses where it would help humans gather the meaning of an expression, regardless of whether a computer could tell what is correct. + +When breaking expressions into multiple lines, always break *after* an operator, such as `,` or `&&`. + +There should be a newline between a function's variables and a function's code. After that, you can place newlines to delimit different parts of a function, but don't go crazy. + +In the event you are unsure of something, refer to existing libui code for examples. I may wind up fixing minor details later anyway, so don't fret about getting minor details right the first time. + +### Naming + +libui uses camel-case for naming, with a handful of very specific exceptions (namely GObject method names, where GObject itself enforces the naming convention). + +All public API names should begin with `ui` and followed by a capital letter. All public struct field names should begin with a capital letter. This is identical to the visibiilty rules of Go, assuming a package name of `ui`. + +Private API names — specifcally those used by more than one source file — should begin with `uipriv` and be followed by a capital letter. This avoids namespace collisions in static libraries. + +Static functions and static objects do not have naming restrictions. + +Acronyms should **NOT** be mixed-case. `http` for the first word in a camel-case name, `HTTP` for all else, but **NEVER** `Http`. This is possibly the only aspect of the controversial nature of code style that I consider indefensibly stupid. + +### API documentation + +(TODO I am writing an API documentation tool; once that becomes stable enough I can talk about documenting libui properly. You'll see vestiges of it throughout ui.h, though.) + +### Other commenting + +(TODO write this part) + +### Compatibility + +libui takes backward compatibility seriously. Your code should not break the current compatibility requirements. All platforms provide a series of macros, defined in the various `uipriv_*.h` files (or `winapi.hpp` on Windows), that specify the minimum required version. If you find yourself needing to remove these or ignore resultant warnings or errors, you're probably breaking compatibility. + +Choosing to drop older versions of Windows, GTK+, and OS X that I could have easily continued to support was not done lightly. If you want to discuss dropping support for an older version of any of these for the benefit of libui, file an issue pleading your case (see below). + +GTK+ versions are harder to drop because I am limited by Linux distribution packaging. In general, I will consider bumping GTK+ versions on a new Ubuntu LTS release, choosing the earliest version available on the major distributions at the time of the *previous* Ubuntu LTS release. As of writing, the next milestone will be *after* April 2018, and the target GTK+ version appears to be 3.18, judging by Ubuntu 16.04 LTS alone. This may be bumped back depending on other distros (or it may not be bumped at all), but you may wish to keep this in mind as you write. + +(TODO talk about future.c/.cpp/.m files) + +As for language compatibility, libui is written in C99. I have no intention of changing this. + +As for build system compatibility, libui uses CMake 3.1.0. If you wish to bump the version, file an issue pleading your case (but see below). + +**If you do plead your case**, keep in mind that "it's old" is not a sufficient reason to drop things. If you can prove that **virtually no one** uses the minimum version anymore, then that is stronger evidence. The best evidence, however, is that not upgrading will hold libui back in some significant way — but beware that there are some things I won't add to libui itself. + +### Windows-specific notes + +The Windows backend of libui is written in C++ using C++11. + +Despite using C++, please refrain from using the following: + +- using C++ in ui_windows.h (this file should still be C compatible) +- smart pointers +- namespaces +- `using namespace` +- ATL, MFC, WTL + +The following are not recommended, for consistency with the rest of libui: + +- variable declarations anywhere in a function (keep them all at the top) +- `for (int x...` (C++11 foreach syntax is fine, though) +- omitting the `struct` on type names for ordinary structs + +The format of a class should be + +```c++ +class name : public ancestor { + int privateVariable; + // etc. +public: + // public stuff here +}; +``` + +### GTK+-specific notes + +Avoid GNU-specific language features. I build with strict C99 conformance. + +### OS X-specific notes + +Avoid GNU-specific/clang-specific language features. I build with strict C99 conformance. + +libui is presently **not** ARC-compliant. Features that require ARC should be avoided for now. I may consider changing this in the future, but it will be a significant change. + +To ensure maximum compiler output in the event of a coding error, there should not be any implicit method calls in Objective-C code. For instance, don't do + +```objective-c +[[array objectAtIndex:i] method] +``` + +Instead, cast the result of `objectAtIndex:` to the appropriate type, and then call the method. (TODO learn about, then decide a policy on, soft-generics on things other than `id`) + +The format of a class should be + +```objective-c +@interface name : parent { + // ivars +} +// properties +- (ret)method:(int)arg; +// more methods +@end + +@implementation name + +- (ret)method:(int)arg +{ + // note the lack of semicolon +} + +@end +``` diff --git a/Compatibility.md b/Compatibility.md new file mode 100644 index 00000000..bc16f1bf --- /dev/null +++ b/Compatibility.md @@ -0,0 +1,141 @@ +# Useful things in newer versions + +## Windows +### Windows 7 +http://channel9.msdn.com/blogs/pdc2008/pc43 + +TODO look up PDC 2008 talk "new shell user interface" + +- new animation and text engine +- ribbon control (didn't this have some additional license?) +- LVITEM.piColFmt + +### Windows 8 + +### Windows 8.1 + +### Windows 10 + +## GTK+ +TODO what ships with Ubuntu Quantal (12.10)? + +### GTK+ 3.6 +ships with: Ubuntu Raring (13.04) + +- GtkEntry and GtkTextView have input purposes and input hints for external input methods but do not change input themselves + - according to Company, we connect to insert-text for that +- GtkLevelBar +- GtkMenuButton +- **GtkSearchEntry** + +### GTK+ 3.8 +ships with: Ubuntu Saucy (13.10) + +Not many interesting new things to us here, unless you count widget-internal tickers and single-click instead of double-click to select list items (a la KDE)... and oh yeah, also widget opacity. + +### GTK+ 3.10 +ships with: **Ubuntu Trusty (14.04 LTS)** +
GLib version: 2.40 + +- tab character stops in GtkEntry +- GtkHeaderBar + - intended for titlebar overrides; GtkInfoBar is what I keep thinking GtkHeaderBar is +- **GtkListBox** +- GtkRevealer for smooth animations of disclosure triangles +- GtkSearchBar for custom search popups +- **GtkStack and GtkStackSwitcher** +- titlebar overrides (seems to be the hot new thing) + +### GTK+ 3.12 +ships with: Ubuntu Utopic (14.10) +
GLib version: 2.42 + +- GtkActionBar (basically like the bottom-of-the-window toolbars in Mac programs) +- gtk_get_locale_direction(), for internationalization +- more control over GtkHeaderBar +- **GtkPopover** + - GtkPopovers on GtkMenuButtons +- GtkStack signaling +- **gtk_tree_path_new_from_indicesv()** (for when we add Table if we have trees too) + +### GTK+ 3.14 +ships with: **Debian Jessie**, Ubuntu Vivid (15.04) +
GLib version: Debian: 2.42, Ubuntu: 2.44 + +- gestures +- better GtkListbox selection handling +- more style classes (TODO also prior?) +- delayed switch changes on GtkSwitch + +### GTK+ 3.16 +ships with: Ubuntu Wily (15.10) +
GLib version: 2.46 + +- gtk_clipboard_get_default() (???) +- **GtkGLArea** +- proper xalign and yalign for GtkLabel; should get rid of runtime deprecation warnings +- better control of GtkListBox model-based creation (probably not relevant but) +- GtkModelButton (for GActions; probably not relevant?) +- wide handles on GtkPaned +- GtkPopoverMenu +- IPP paper names in GtkPaperSize (TODO will this be important for printing?) +- multiple matches in GtkSearchEntry (TODO evaluate priority) +- **GtkStackSidebar** +- GTK_STYLE_CLASS_LABEL, GTK_STYLE_CLASS_MONOSPACE, GTK_STYLE_CLASS_STATUSBAR, GTK_STYLE_CLASS_TOUCH_SELECTION, GTK_STYLE_CLASS_WIDE (TODO figure out which of these are useful) +- GtkTextView: extend-selection +- GtkTextView: font fallbacks + +### GTK+ 3.18 + +### GTK+ 3.20 + +## Cocoa +### Mac OS X 10.8 + +- Foundation ([full details](https://developer.apple.com/library/mac/releasenotes/Foundation/RN-FoundationOlderNotes/#//apple_ref/doc/uid/TP40008080-TRANSLATED_CHAPTER_965-TRANSLATED_DEST_999B)) + - NSDateComponents supports leap months + - NSNumberFormatter and NSDateFormatter default to 10.4 behavior by default (need to explicitly do this on 10.7) + - **NSUserNotification and NSUserNotificationCenter for Growl-style notifications** + - better linguistic triggers for Spanish and Italian + - NSByteCountFormatter +- AppKit ([full details](https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/#X10_8Notes)) + - view-based NSTableView/NSOutlineView have expansion tooltips + - NSScrollView magnification + - Quick Look events; TODO see if they conflict with keyboard handling in Area + - NSPageController (maybe useful?) + - not useful for package UI, but may be useful for a new library (probably not by me): NSSharingService + - NSOpenPanel and NSSavePanel are now longer NSPanels or NSWindows in sandboxed applications; this may be an issue should anyone dare to enable sandboxing on a program that uses package ui + - NSTextAlternatives + - -[NSOpenGLContext setFullScreen] now ineffective + - +[NSColor underPageBackgroundColor] + +### Mac OS X 10.9 + +- Foundation ([full details](https://developer.apple.com/library/mac/releasenotes/Foundation/RN-Foundation/)) + - system-provided progress reporting/cancellation support + - NSURLComponents + - **NSCalendar, NSDateFormatter, and NSNumberFormatter are now thread-safe** + - various NSCalendar and NSDateComponents improvements +- AppKit ([full details](https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKit/)) + - sheet handling is now block-based, queued, and in NSWindow; the delegate-based NSApplication API will still exist, except without the queue + - similar changes to NSAlert + - **return value changes to NSAlert** + - window visibility APIs (occlusion) + - NSApplicationActivationPolicyAccessory + - fullscreen toolbar behavior changes + - status items for multiple menu bars + - better NSSharingService support + - a special accelerated scrolling mode, Responsive Scrolling; won't matter for us since I plan to support the scroll wheel and it won't + - NSScrollView live scrolling notifications + - NSScrollView floating (anchored/non-scrolling) subviews + - better multimonitor support + - better key-value observing for NSOpenPanel/NSSavePanel (might want to look this up to see if we can override some other juicy details... TODO) + - better accessory view key-view handling in NSOpenPanel/NSSavePanel + - NSAppearance + - **-[NSTableView moveRowAtIndex:toIndex:] bug regarding first responders fixed** + - view-specific RTL overrides + +### Mac OS X 10.10 + +### Mac OS X 10.11 +* **NSLayoutGuide** diff --git a/GNUmakefile b/GNUmakefile deleted file mode 100644 index ce74d750..00000000 --- a/GNUmakefile +++ /dev/null @@ -1,72 +0,0 @@ -# 16 october 2015 - -# TODO http://stackoverflow.com/questions/4122831/disable-make-builtin-rules-and-variables-from-inside-the-make-file - -# silence entering/leaving messages -MAKEFLAGS += --no-print-directory - -OUTDIR = out -OBJDIR = .obj - -# MAME does this so :/ -ifeq ($(OS),Windows_NT) - OS = windows -endif - -ifndef OS - UNAME = $(shell uname -s) - ifeq ($(UNAME),Darwin) - OS = darwin - else ifeq ($(UNAME),Haiku) - OS = haiku - else - OS = unix - endif -endif - -# default is to build with debug symbols -ifndef NODEBUG - NODEBUG = 0 -endif - -# parameters -export OS -# TODO CC, CXX, RC, LD -export CFLAGS -export CXXFLAGS -# TODO RCFLAGS -export LDFLAGS -export NODEBUG -export EXAMPLE -export PREFIX - -# for Debian - see https://github.com/andlabs/libui/pull/10 -export DESTDIR - -# other important variables -export OBJDIR -export OUTDIR - -libui: - @$(MAKE) -f build/GNUmakefile.libui inlibuibuild=1 - -clean: - rm -rf $(OBJDIR) $(OUTDIR) - -test: libui - @$(MAKE) -f build/GNUmakefile.test inlibuibuild=1 - -# TODO provide a build option for the queuemaintest - -example: libui - @$(MAKE) -f build/GNUmakefile.example inlibuibuild=1 - -examples: - @$(MAKE) -f GNUmakefile example EXAMPLE=controlgallery - @$(MAKE) -f GNUmakefile example EXAMPLE=histogram - @$(MAKE) -f GNUmakefile example EXAMPLE=cpp-multithread - -.PHONY: examples - -install: - @$(MAKE) -f build/GNUmakefile.libui install inlibuibuild=1 diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 00000000..d439ed40 --- /dev/null +++ b/NEWS.md @@ -0,0 +1,113 @@ +# Old News + +* **27 November 2016** + * Decided to split the table stuff into its own branch. It will be developed independently of everything else, along with a few other features. + +* **2 November 2016** + * Added two new functions to replace the deleted `uiWindowPosition()` and friends: `uiAreaBeginUserWindowMove()` and `uiAreaBeginUserWindowResize()`. When used in a `uiAreaHandler.Mouse()` event handler, these let you initiate a user-driven mouse move or mouse resize of the window at any point in a uiArea. + +* **31 October 2016** + * @krakjoe noticed that I accidentally used thread-unsafe code in uiQueueMain() on Unix. Fixed. + +* **24 October 2016** + * `uiWindowSetContentSize()` on Unix no longer needs to call up the GTK+ main loop. As a result, bugs related to strange behavior using that function (and the now-deleted `uiWindowSetPosition()` and `uiWindowCenter()`) should go away. I'll need to go through the bugs to verify as much, though. + +* **22 October 2016** + * Due to being unable to guarantee they will work (especially as we move toward capability-driven window systems like Wayland), or being unable to work without hacking that breaks other things, the following functions have been removed: `uiWindowPosition()`, `uiWindowSetPosition()`, `uiWindowCenter()`, and `uiWindowOnPositionChanged()`. Centering may come back at some point in the future, albeit in a possibly restricted form. A function to initiate a user move when a part of a uiArea is clicked will be provided soon. + +* **21 October 2016** + * `uiDrawTextWeightUltraBold` is now spelled correctly. Thanks to @krakjoe. + +* **18 June 2016** + * Help decide [the design of tables and trees in libui](https://github.com/andlabs/libui/issues/159); the implementation starts within the next few days, if not tomorrow! + +* **17 June 2016** + * **CMake 3.1.0 is now required.** This is due to CMake's rapid development pace in the past few years adding things libui needs to build on as many systems as possible. If your OS is supported by libui but its repositories ship with an older version of CMake, you will need to find an updated one somewhere. + * Please help [plan out a better menu API](https://github.com/andlabs/libui/issues/152). + * `uiMainSteps()` no longer takes any arguments and no longer needs to invoke a function to do the work. You still need to call it, but once you do, it will return immediately and you can then get right to your main loop. + * **CMake 3.1.0 is now required.** This is due to CMake's rapid development pace in the past few years adding things libui needs to build on as many systems as possible. If your OS is supported by libui but its repositories ship with an older version of CMake, you will need to find an updated one somewhere. + * Added `uiNewVerticalSeparator()` to complement `uiNewHorizontalSeparator()`. + +* **16 June 2016** + * Added `uiWindowContentSize()`, `uiWindowSetContentSize()`, and `uiWindowOnContentSizeChanged()` methods for manipulating uiWindow content sizes. Note the use of "content size"; the size you work with does NOT include window decorations (titlebars, menus, etc.). + * Added `uiWindowFullscreen()` and `uiWindowSetFullscreen()` to allow making fullscreen uiWindows, taking advantage of OS facilities for fullscreen and without changing the screen resolution (!). + * Added `uiWindowBorderless()` and `uiWindowSetBorderless()` for allowing borderless uiWindows. + * Added `uiMainSteps()`. You call this instead of `uiMain()` if you want to run the main loop yourself. You pass in a function that will be called; within that function, you call `uiMainStep()` repeatedly until it returns 0, doing whatever you need to do in the meantime. (This was needed because just having `uiMainStep()` by itself only worked on some systems.) + * Added `uiProgressBarValue()` and allowed passing -1 to `uiProgressBarSetValue()` to make an indeterminate progress bar. Thanks to @emersion. + +* **15 June 2016** + * Added `uiFormDelete()`; thanks to @emersion. + * Added `uiWindowPosition()`, `uiWindowSetPosition()`, `uiWindowCenter()`, and `uiWindowOnPositionChanged()`, methods for manipulating uiWindow position. + +* **14 June 2016** + * uiDarwinControl now has a `ChildVisibilityChanged()` method and a corresponding `NotifyVisibilityChanged()` function that is called by the default show/hide handlers. This is used to make visibility changes work on OS X; uiBox, uiForm, and uiGrid all respect these now. + * The same has been done on the Windows side as well. + * Hiding and showing controls and padding calculations are now correct on Windows at long last. + * Hiding a control in a uiForm now hides its label on all platforms. + +* **13 June 2016** + * `intmax_t` and `uintmax_t` are no longer used for libui API functions; now we use `int`. This should make things much easier for bindings. `int` should be at least 32 bits wide; this should be sufficient for all but the most extreme cases. + +* **12 June 2016** + * Added `uiGrid`, a new container control that arranges controls in rows and columns, with stretchy ("expanding") rows, stretchy ("expanding") columns, cells that span rows and columns, and cells whose content is aligned in either direction rather than just filling. It's quite powerful, is it? =P + +* **8 June 2016** + * Added `uiForm`, a new container control that arranges controls vertically, with properly aligned labels on each. Have fun! + +* **6 June 2016** + * Added `uiRadioButtonsSelected()`, `uiRadioButtonsSetSelected()`, and `uiRadioButtonsOnSelected()` to control selection of a radio button and catch an event when such a thing happens. + +* **5 June 2016** + * **Alpha 3.1 is here.** This was a much-needed update to Alpha 3 that changes a few things: + * **The build system is now cmake.** cmake 2.8.11 or higher is needed. + * Static linking is now fully possible. + * MinGW linking is back, but static only. + * Added `uiNewPasswordEntry()`, which creates a new `uiEntry` suitable for entering passwords. + * Added `uiNewSearchEntry()`, which creates a new `uiEntry` suitable for searching. On some systems, the `OnChanged()` event will be slightly delayed and/or combined, to produce a more natural feel when searching. + +* **29 May 2016** + * **Alpha 3 is here!** Get it [here](https://github.com/andlabs/libui/releases/tag/alpha3). + * The next packaged release will introduce: + * uiGrid, another way to lay out controls, a la GtkGrid + * uiOpenGLArea, a way to render OpenGL content in a libui uiArea + * uiTable, a data grid control that may or may not have tree facilities (if it does, it will be called uiTree instead) + * a complete, possibly rewritten, drawing and text rendering infrastructure + * Thanks to @pcwalton, we can now statically link libui! Simply do `make STATIC=1` instead of just `make`. + * On Windows you must link both `libui.lib` and `libui.res` AND provide a Common Controls 6 manifest for output static binaries to work properly. + +* **28 May 2016** + * As promised, **the minimum system requirements are now OS X 10.8 and GTK+ 3.10 for OS X and Unix, respectively**. + +* **26 May 2016** + * Two OS X-specific functions have been added: `uiDarwinMarginAmount()` and `uiDarwinPaddingAmount()`. These return the amount of margins and padding, respectively, to give to a control, and are intended for container implementations. These are suitable for the constant of a NSLayoutConstraint. They both take a pointer parameter that is reserved for future use and should be `NULL`. + +* **25 May 2016** + * uiDrawTextLayout attributes are now specified in units of *graphemes* on all platforms. This means characters as seen from a user's perspective, not Unicode codepoints or UTF-8 bytes. So a long string of combining marker codepoints after one codepoint would still count as one grapheme. + +* **24 May 2016** + * You can now help choose [a potential new build system for libui](https://github.com/andlabs/libui/issues/62). + * Tomorrow I will decide if OS X 10.7 will also be dropped alongside GTK+ 3.4-3.8 this Saturday. Stay tuned. + * As promised, `uiCombobox` is now split into `uiCombobox` for non-editable comboboxes and `uiEditableCombobox` for editable comboboxes. Mind the function changes as well :) + * There is a new function `uiMainStep()`, which runs one iteration of the main loop. It takes a single boolean argument, indicating whether to wait for an event to occur or not. It returns true if an event was processed (or if no event is available if you don't want to wait) and false if the event loop was told to stop (for instance, `uiQuit()` was called). + +* **23 May 2016** + * Fixed surrogate pair drawing on OS X. + +* **22 May 2016** + * Two more open questions I'd like your feedback on are available [here](https://github.com/andlabs/libui/issues/48) and [here](https://github.com/andlabs/libui/issues/25). + * Sometime in the next 48 hours (before 23:59 EDT on 24 May 2016) I will split `uiCombobox` into two separate controls, `uiCombobox` and `uiEditableCombobox`, each with slightly different events and "selected item" mechanics. Prepare your existing code. + * Removed `uiControlVerifyDestroy()`; that is now part of `uiFreeControl()` itself. + * Added `uiPi`, a constant for π. This is provided for C and C++ programmers, where there is no standard named constant for π; bindings authors shouldn't need to worry about this. + * Fixed uiMultilineEntry not properly having line breaks on Windows. + * Added `uiNewNonWrappingMultilineEntry()`, which creates a uiMultilineEntry that scrolls horizontally instead of wrapping lines. (This is not documented as being changeable after the fact on Windows, hence it's a creation-time choice.) + * uiAreas on Windows and some internal Direct2D areas now respond to `WM_PRINTCLIENT` properly, which should hopefully increase the quality of screenshots. + * uiDateTimePicker on GTK+ works properly on RTL layouts and no longer disappears off the bottom of the screen if not enough room is available. It will also no longer be marked for localization of the time format (what the separator should be and whether to use 24-hour time), as that information is not provided by the locale system. :( + * Added `uiUserBugCannotSetParentOnToplevel()`, which should be used by implementations of toplevel controls in their `SetParent()` implementations. This will also be the beginning of consolidating common user bug messages into a single place, though this will be one of the only few exported user bug functions. + * uiSpinbox and uiSlider now merely swap their min and max if min ≥ max. They will no longer panic and do nothing, respectively. + * Matrix scaling will no longer leave the matrix in an invalid state on OS X and GTK+. + * `uiMultilineEntrySetText()` and `uiMutlilineEntryAppend()` on GTK+ no longer fire `OnChanged()` events. + +* **21 May 2016** + * I will now post announcements and updates here. + * Now that Ubuntu 16.04 LTS is here, no earlier than next Saturday, 28 May 2016 at noon EDT, **I will bump the minimum GTK+ version from 3.4 to 3.10**. This will add a lot of new features that I can now add to libui, such as search-oriented uiEntries, lists of arbitrary control layouts, and more. If you are still running a Linux distribution that doesn't come with 3.10, you will either need to upgrade or use jhbuild to set up a newer version of GTK+ in a private environment. + * You can decide if I should also drop OS X 10.7 [here](https://github.com/andlabs/libui/issues/46). diff --git a/OLD_uitable.h b/OLD_uitable.h new file mode 100644 index 00000000..a54398c5 --- /dev/null +++ b/OLD_uitable.h @@ -0,0 +1,65 @@ +// 20 june 2016 +// kept in a separate file for now + +typedef struct uiImage uiImage; + +// TODO use const void * for const correctness +_UI_EXTERN uiImage *uiNewImage(double width, double height); +_UI_EXTERN void uiFreeImage(uiImage *i); +_UI_EXTERN void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int pixelStride); + +typedef struct uiTableModel uiTableModel; +typedef struct uiTableModelHandler uiTableModelHandler; + +// TODO actually validate these +_UI_ENUM(uiTableModelColumnType) { + uiTableModelColumnString, + uiTableModelColumnImage, + uiTableModelColumnInt, + uiTableModelColumnColor, +}; + +// TODO validate ranges; validate types on each getter/setter call (? table columns only?) +struct uiTableModelHandler { + int (*NumColumns)(uiTableModelHandler *, uiTableModel *); + uiTableModelColumnType (*ColumnType)(uiTableModelHandler *, uiTableModel *, int); + int (*NumRows)(uiTableModelHandler *, uiTableModel *); + void *(*CellValue)(uiTableModelHandler *, uiTableModel *, int, int); + void (*SetCellValue)(uiTableModelHandler *, uiTableModel *, int, int, const void *); +}; + +_UI_EXTERN void *uiTableModelStrdup(const char *str); +// TODO rename the strdup one to this too +_UI_EXTERN void *uiTableModelGiveColor(double r, double g, double b, double a); +_UI_EXTERN void *uiTableModelGiveInt(int i); +// TODO TakeString +// TODO add const +_UI_EXTERN int uiTableModelTakeInt(void *v); + +_UI_EXTERN uiTableModel *uiNewTableModel(uiTableModelHandler *mh); +_UI_EXTERN void uiFreeTableModel(uiTableModel *m); +_UI_EXTERN void uiTableModelRowInserted(uiTableModel *m, int newIndex); +_UI_EXTERN void uiTableModelRowChanged(uiTableModel *m, int index); +_UI_EXTERN void uiTableModelRowDeleted(uiTableModel *m, int oldIndex); +// TODO reordering/moving + +typedef struct uiTableColumn uiTableColumn; + +_UI_EXTERN void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand); +// TODO images shouldn't expand... +_UI_EXTERN void uiTableColumnAppendImagePart(uiTableColumn *c, int modelColumn, int expand); +_UI_EXTERN void uiTableColumnAppendButtonPart(uiTableColumn *c, int modelColumn, int expand); +// TODO should these have labels? +_UI_EXTERN void uiTableColumnAppendCheckboxPart(uiTableColumn *c, int modelColumn, int expand); +_UI_EXTERN void uiTableColumnAppendProgressBarPart(uiTableColumn *c, int modelColumn, int expand); +// TODO Editable? +_UI_EXTERN void uiTableColumnPartSetEditable(uiTableColumn *c, int part, int editable); +_UI_EXTERN void uiTableColumnPartSetTextColor(uiTableColumn *c, int part, int modelColumn); + +typedef struct uiTable uiTable; +#define uiTable(this) ((uiTable *) (this)) +_UI_EXTERN uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name); +_UI_EXTERN uiTableColumn *uiTableAppendTextColumn(uiTable *t, const char *name, int modelColumn); +// TODO getter? +_UI_EXTERN void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn); +_UI_EXTERN uiTable *uiNewTable(uiTableModel *model); diff --git a/README.md b/README.md index 50736524..30d7c9fb 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,175 @@ # libui: a portable GUI library for C This README is being written.
-[![Build Status](https://travis-ci.org/andlabs/libui.png)](https://travis-ci.org/andlabs/libui) +[![Build Status, Linux and macOS](https://travis-ci.org/andlabs/libui.svg?branch=master)](https://travis-ci.org/andlabs/libui)
+[![Build Status, Windows](https://ci.appveyor.com/api/projects/status/ouyk78c52mmisa31?svg=true)](https://ci.appveyor.com/project/andlabs/libui) + +## Status + +It has come to my attention that I have not been particularly clear about how usable or feature-complete libui is, and that this has fooled many people into expecting more from libui right this moment than I have explicitly promised to make available. I apologize for not doing this sooner. + +libui is currently **mid-alpha** software. Much of what is currently present runs stabily enough for the examples and perhaps some small programs to work, but the stability is still a work-in-progress, much of what is already there is not feature-complete, some of it will be buggy on certain platforms, and there's a lot of stuff missing. In short, here's a list of features that I would like to add to libui, but that aren't in yet: + +- trees +- clipboard support, including drag and drop +- more and better dialogs +- printing +- accessibility for uiArea and custom controls +- document-based programs +- tighter OS integration (especially for document-based programs), to allow programs to fully feel native, rather than merely look and act native +- better support for standard dialogs and features (search bars, etc.) +- OpenGL support + +In addition, [here](https://github.com/andlabs/libui/issues?utf8=%E2%9C%93&q=master+in%3Atitle+is%3Aissue+is%3Aopen) is a list of issues generalizing existing problems. + +Furthermore, libui is not properly fully documented yet. This is mainly due to the fact that the API was initially unstable enough so as to result in rewriting documentation multiple times, in addition to me not being happy with really any existing C code documentation tool. That being said, I have started to pin down my ideal code documentation style in parts of `ui.h`, most notably in the uiAttributedString APIs. Over time, I plan on extending this to the rest of the headers. You can also use [the documentation for libui's Go bindings](https://godoc.org/github.com/andlabs/ui) as a reference, though it is somewhat stale and not optimally written. + +But libui is not dead; I am working on it whenever I can, and I hope to get it to a point of real quality soon! + +## News + +*Note that today's entry (Eastern Time) may be updated later today.* + +* **1 September 2018** + * **Alpha 4.1 is here.** This is an emergency fix to Alpha 4 to fix `uiImageAppend()` not working as documented. It now works properly, with one important difference you'll need to care about: **it now requires image data to be alpha-premultiplied**. In addition, `uiImage` also is implemented slightly more nicely now, and `ui.h` has minor documentation typo fixes. + * Alpha 4.1 also tries to make everything properly PIC-enabled. + +* **10 August 2018** + * **Alpha 4 is finally here.** Everything from Alpha 3.5 and what's listed below is in this release; the two biggest changes are still the new text drawing API and new uiTable control. In between all that is a whole bunch of bugfixes, and hopefully more stability too. Thanks to everybody who helped contribute! + * Alpha 4 should hopefully also include automated binary releases via CI. Thanks to those who helped set that up! + +* **8 August 2018** + * Finally introduced an API for loading images, `uiImage`, and a new control, `uiTable`, for displaying tabular data. These provide enough basic functionality for now, but will be improved over time. You can read the documentation for the new features as they are [here](https://github.com/andlabs/libui/blob/f47e1423cf95ad7b1001663f3381b5a819fc67b9/uitable.h). Thanks to everyone who helped get to this point, in particular @bcampbell for the initial Windows code, and to everyone else for their patience! + +* **30 May 2018** + * Merged the previous Announcements and Updates section of this README into a single News section, and merged the respective archive files into a single NEWS.md file. + +* **16 May 2018** + * Thanks to @parro-it and @msink, libui now has better CI, including AppVeyor for Windows CI, and automated creation of binary releases when I make a tagged release. + +* **13 May 2018** + * Added new functions to work with uiDateTimePickers: `uiDateTimePickerTime()`, `uiDateTimePickerSetTime()`, and `uiDateTimePickerOnChanged()`. These operate on standard `` `struct tm`s. Thanks @cody271! + * Release builds on Windows with MSVC should be fixed now; thanks @l0calh05t, @slahn, @mischnic, and @zentner-kyle. + +* **12 May 2018** + * GTK+ and OS X now have a cleaner build process for static libraries which no longer has intermediate files and differing configurations. As a result, certain issues should no longer be present. New naming rules for internal symbols of libui have also started being drafted; runtime symbols and edge cases still need to be handled (and the rules applied to Windows) before this can become a regular thing. + +* **2 May 2018** + * On Windows, you no longer need to carry around a `libui.res` file with static builds. You do need to link in the appropriate manifest file, such as the one in the `windows/` folder (I still need to figure out exactly what is needed apart from the Common Controls v6 dependency, or at least to create a complete-ish template), or at least include it alongside your executables. This also means you should no longer see random cmake errors when building the static libraries. + +* **18 April 2018** + * Introduced a new `uiTimer()` function for running code on a timer on the main thread. (Thanks to @cody271.) + * Migrated all code in the `common/` directory to use `uipriv` prefixes for everything that isn't `static`. This is the first step toward fixing static library oddities within libui, allowing libui to truly be safely used as either a static library or a shared library. + +* **18 March 2018** + * Introduced an all-new formatted text API that allows you to process formatted text in ways that the old API wouldn't allow. You can read on the whole API [here](https://github.com/andlabs/libui/blob/8944a3fc5528445b9027b1294b6c86bae03eeb89/ui_attrstr.h). There is also a new examples for it: `drawtext`, which shows the whole API at a glance. It doesn't yet support measuring or manipulating text, nor does it currently support functions that would be necessary for things like text editors; all of this will be added back later. + * libui also now uses my [utf library](https://github.com/andlabs/utf) for UTF-8 and UTF-16 processing, to allow consistent behavior across platforms. This usage is not completely propagated throughout libui, but the Windows port uses it in most places now, and eventually this will become what libui will use throughout. + * Also introduced a formal set of contribution guidelines, see `CONTRIBUTING.md` for details. They are still WIP. + +* **17 February 2018** + * The longstanding Enter+Escape crashes on Windows have finally been fixed (thanks to @lxn). + * **Alpha 3.5 is now here.** This is a quickie release primiarly intended to deploy the above fix to package ui itself. **It is a partial binary release; sorry!** More new things will come in the next release, which will also introduce semver (so it will be called v0.4.0 instead). + * Alpha 3.5 also includes a new control gallery example. The screenshots below have not been updated yet. + +*Old announcements can be found in the NEWS.md file.* ## Runtime Requirements * Windows: Windows Vista SP2 with Platform Update or newer -* Unix: GTK+ 3.4 or newer -* Mac OS X: OS X 10.7 or newer +* Unix: GTK+ 3.10 or newer +* Mac OS X: OS X 10.8 or newer ## Build Requirements * All platforms: - * GNU make 3.81 or newer (Xcode comes with this; on Windows you will need to get it yourself) -* Windows: Microsoft Visual Studio 2013 or newer (2013 is needed for `va_copy()`) - * MinGW is currently unsupported. MinGW-w64 support will be re-added once the following features come in: - * [Isolation awareness](https://msdn.microsoft.com/en-us/library/aa375197%28v=vs.85%29.aspx) - * Linker symbols for some functions such as `TaskDialog()` (which I thought I submitted...) -* Unix: nothing specific -* Mac OS X: nothing specific, so long as you can build Cocoa programs + * CMake 3.1.0 or newer +* Windows: either + * Microsoft Visual Studio 2013 or newer (2013 is needed for `va_copy()`) — you can build either a static or a shared library + * MinGW-w64 (other flavors of MinGW may not work) — **you can only build a static library**; shared library support will be re-added once the following features come in: + * [Isolation awareness](https://msdn.microsoft.com/en-us/library/aa375197%28v=vs.85%29.aspx), which is how you get themed controls from a DLL without needing a manifest +* Unix: nothing else specific +* Mac OS X: nothing else specific, so long as you can build Cocoa programs -(TODO write some notes on make variables and cross-compiling) +## Building + +Out-of-tree builds typical of cmake are preferred: + +``` +$ # you must be in the top-level libui directory, otherwise this won't work +$ mkdir build +$ cd build +$ cmake .. +``` + +Pass `-DBUILD_SHARED_LIBS=OFF` to `cmake` to build a static library. The standard cmake build configurations are provided; if none is specified, `Debug` is used. + +If you use a makefile generator with cmake, then + +``` +$ make +$ make tester # for the test program +$ make examples # for examples +``` + +and pass `VERBOSE=1` to see build commands. Build targets will be in the `build/out` folder. + +Project file generators should work, but are untested by me. + +On Windows, I use the `Unix Makefiles` generator and GNU make (built using the `build_w32.bat` script included in the source and run in the Visual Studio command line). In this state, if MinGW-w64 (either 32-bit or 64-bit) is not in your `%PATH%`, cmake will use MSVC by default; otherwise, cmake will use with whatever MinGW-w64 is in your path. `set PATH=%PATH%;c:\msys2\mingw(32/64)\bin` should be enough to temporarily change to a MinGW-w64 build for the current command line session only if you installed MinGW-w64 through [MSYS2](https://msys2.github.io/); no need to change global environment variables constantly. + +## Installation + +#### Arch Linux + +Can be built from AUR: https://aur.archlinux.org/packages/libui-git/ ## Documentation -Needs to be written. Consult ui.h and the examples for details for now. +Needs to be written. Consult `ui.h` and the examples for details for now. + +## Language Bindings + +libui was originally written as part of my [package ui for Go](https://github.com/andlabs/ui). Now that libui is separate, package ui has become a binding to libui. As such, package ui is the only official binding. + +Other people have made bindings to other languages: + +Language | Bindings +--- | --- +C++ | [libui-cpp](https://github.com/billyquith/libui-cpp), [cpp-libui-qtlike](https://github.com/aoloe/cpp-libui-qtlike) +C# / .NET Framework | [LibUI.Binding](https://github.com/NattyNarwhal/LibUI.Binding) +C# / .NET Core | [DevZH.UI](https://github.com/noliar/DevZH.UI), [SharpUI](https://github.com/benpye/sharpui/), [TCD.UI](https://github.com/tacdevel/tcdfx) +CHICKEN Scheme | [wasamasa/libui](https://github.com/wasamasa/libui) +Common Lisp | [jinwoo/cl-ui](https://github.com/jinwoo/cl-ui) +Crystal | [libui.cr](https://github.com/Fusion/libui.cr), [hedron](https://github.com/Qwerp-Derp/hedron) +D | [DerelictLibui (flat API)](https://github.com/Extrawurst/DerelictLibui), [libuid (object-oriented)](https://github.com/mogud/libuid) +Euphoria | [libui-euphoria](https://github.com/ghaberek/libui-euphoria) +Harbour | [HBUI](https://github.com/RJopek/HBUI) +Haskell | [haskell-libui](https://github.com/beijaflor-io/haskell-libui) +JavaScript/Node.js | [libui-node](https://github.com/parro-it/libui-node), [libui.js (merged into libui-node?)](https://github.com/mavenave/libui.js), [proton-native](https://github.com/kusti8/proton-native), [vuido](https://github.com/mimecorg/vuido) +Julia | [Libui.jl](https://github.com/joa-quim/Libui.jl) +Kotlin | [kotlin-libui](https://github.com/msink/kotlin-libui) +Lua | [libuilua](https://github.com/zevv/libuilua), [libui-lua](https://github.com/mdombroski/libui-lua), [lui](http://tset.de/lui/index.html), [lui](https://github.com/zhaozg/lui) +Nim | [ui](https://github.com/nim-lang/ui) +Perl6 | [perl6-libui](https://github.com/Garland-g/perl6-libui) +PHP | [ui](https://github.com/krakjoe/ui) +Python | [pylibui](https://github.com/joaoventura/pylibui), [pylibui-cffi](https://github.com/Yardanico/pylibui-cffi) +Ruby | [libui-ruby](https://github.com/jamescook/libui-ruby) +Rust | [libui-rs](https://github.com/pcwalton/libui-rs), [arcturu/libui-rs](https://github.com/arcturu/libui-rs), [LeoTindall/libui-rs](https://github.com/LeoTindall/libui-rs) +Scala | [scalaui](https://github.com/lolgab/scalaui) +Swift | [libui-swift](https://github.com/sclukey/libui-swift) + +## Frequently Asked Questions + +### Why does my program start in the background on OS X if I run from the command line? +OS X normally does not start program executables directly; instead, it uses [Launch Services](https://developer.apple.com/reference/coreservices/1658613-launch_services?language=objc) to coordinate the launching of the program between the various parts of the system and the loading of info from an .app bundle. One of these coordination tasks is responsible for bringing a newly launched app into the foreground. This is called "activation". + +When you run a binary directly from the Terminal, however, you are running it directly, not through Launch Services. Therefore, the program starts in the background, because no one told it to activate! Now, it turns out [there is an API](https://developer.apple.com/reference/appkit/nsapplication/1428468-activateignoringotherapps) that we can use to force our app to be activated. But if we use it, then we'd be trampling over Launch Services, which already knows whether it should activate or not. Therefore, libui does not step over Launch Services, at the cost of requiring an extra user step if running directly from the command line. + +See also [this](https://github.com/andlabs/libui/pull/20#issuecomment-211381971) and [this](http://stackoverflow.com/questions/25318524/what-exactly-should-i-pass-to-nsapp-activateignoringotherapps-to-get-my-appl). + +## Contributing + +See `CONTRIBUTING.md`. ## Screenshots diff --git a/TODO.md b/TODO.md index 8f9fdc1d..528a658d 100644 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,13 @@ +- make sure the last line of text layouts include leading + +- documentation notes: + - static binaries do not link system libraries, meaning apps still depend on shared GTK+, etc. + - ui*Buttons are NOT compatible with uiButton functions + +- more robust layout handling + - uiFormTie() for ensuring multiple uiForms have the same label area widths + - uiSizeGroup for size groups (GtkSizeGroup on GTK+, auto layout constraints on OS X; consider adding after 10.8 is gone) + - windows: should the initial hwndInsertAfter be HWND_BOTTOM for what we want? - windows: document the rules for controls and containers @@ -6,9 +16,9 @@ - provisions for controls that cannot be grown? especiailly for windows -- LC_VERSION_MIN_MACOSX has the 10.11 SDK; see if we can knock it down to 10.7 too; OS version is fine +- LC_VERSION_MIN_MACOSX has the 10.11 SDK; see if we can knock it down to 10.8 too; OS version is fine - apply the OS version stuff to the test program and examples too - - what about micro versions (10.7.x)? force 10.7.0? + - what about micro versions (10.8.x)? force 10.8.0? - go through ALL the objective-c objects we create and make sure we are using the proper ownership (alloc/init and new are owned by us, all class method constructors are autoreleased - thanks mikeash) @@ -19,14 +29,6 @@ - provide a way to get the currently selected uiTab page? set? -- add uiPi for portability; compare against: - - M_PI on all systems with different requirements - - _GNU_SOURCE on unix - - _USE_MATH_DEFINES on windows - - G_PI on GLib - - XM_PI from DirectX - - Go math.Pi - - make it so that the windows cntrols only register a resize if their new minimum size is larger than their current size to easen the effect of flicker - it won't remove that outright, but it'll help @@ -40,7 +42,6 @@ - DPI awareness on windows -- consider calling setAppleMenu: for the application menu; it doesn't seem to make much of a difference but - http://stackoverflow.com/questions/4543087/applicationwillterminate-and-the-dock-but-wanting-to-cancel-this-action ultimately: @@ -72,6 +73,8 @@ notes to self - test RTL - automate RTL +- now that stock items are deprecated, I have to maintain translations of the Cancel, Open, and Save buttons on GTK+ myself (thanks baedert in irc.gimp.net/#gtk+) + - either that or keep using stock items - http://blogs.msdn.com/b/oldnewthing/archive/2014/02/26/10503148.aspx @@ -89,6 +92,171 @@ notes to self +don't forget LONGTERMs as well notes - http://blogs.msdn.com/b/oldnewthing/archive/2004/03/29/101121.aspx on accelerators + +- group and tab should act as if they have no child if the child is hidden +on windows + + + +- a way to do recursive main loops + - how do we handle 0 returns from non-recursive uiMainStep() calls that aren't the main loop? (event handlers, for instance) +- should repeated calls to uiMainStep() after uiQuit() return 0 reliably? this will be needed for non-recursive loops + +http://stackoverflow.com/questions/38338426/meaning-of-ampersand-in-rc-files/38338841?noredirect=1#comment64093084_38338841 + +label shortcut keys + +- remove whining from source code + +[01:41:47] Hi. does pango support "fgalpha". I see that foreground="112233xx" works ( alpha=xx ), but fgalpha is a no-op +[01:52:29] pango_attr_foreground_alpha_new (32767) seems to be called in either case, but only the "foreground" attr works +[01:56:09] lolek (lolek@ip-91-244-230-76.simant.pl) joined the channel +[01:57:48] ok. seems like "foreground" is mandatory attr, 1. "foreground-without-alpha" + "alpha" works 2. "foreground-with-alpha" works. 3. "alpha" alone doesn +[01:57:52] 't work +[01:58:29] Is there a way to just specify alpha on the current foreground color ? +[02:00:23] lolek (lolek@ip-91-244-230-76.simant.pl) left the channel +[02:07:41] mjog (mjog@uniwide-pat-pool-129-94-8-98.gw.unsw.edu.au) left IRC (Quit: mjog) +[02:08:10] seb128 (seb128@53542B83.cm-6-5a.dynamic.ziggo.nl) joined the channel +[02:12:37] huh +[02:12:41] what version of pango? +[02:13:05] the latest . +[02:15:00] 1.40.3 +[02:20:46] I'll ahve to keep this in mind then, thanks +[02:20:59] if only there was a cairo-specific attribute for alpha... + +FONT LOADING + +[00:10:08] andlabs: is there API yet to load from memory? last i checked i only found from file (which we use in builder). https://git.gnome.org/browse/gnome-builder/tree/libide/editor/ide-editor-map-bin.c#n115 +[00:13:12] mrmcq2u_ (mrmcq2u@109.79.53.90) joined the channel +[00:14:59] mrmcq2u (mrmcq2u@109.79.73.102) left IRC (Ping timeout: 181 seconds) +[00:15:19] hergertme: no, which is why I was asking =P +[00:15:30] I would have dug down if I could ensure at least something about the backends a GTK+ 3 program uses +[00:15:39] on all platforms except windows and os x +[00:16:11] to the best of my (partially outdated, given pace of foss) knowledge there isn't an api to load from memory +[00:16:28] you can possibly make a tmpdir and put a temp file in there +[00:16:52] and load that as your font dir in your FcConfig, so any PangoFontDescription would point to that one font, no matter what +[00:17:18] (using the API layed out in that link) +[00:18:18] dsr1014__ (dsr1014@c-73-72-102-18.hsd1.il.comcast.net) joined the channel +[00:35:18] simukis_ (simukis@78-60-58-6.static.zebra.lt) left IRC (Quit: simukis_) +[00:35:48] dreamon_ (dreamon@ppp-188-174-49-41.dynamic.mnet-online.de) joined the channel +[00:40:09] samtoday_ (samtoday@114-198-116-132.dyn.iinet.net.au) joined the channel +[00:40:32] mjog (mjog@120.18.225.46) joined the channel +[00:40:38] hergertme: not necessarily fontconfig +[00:40:45] it can be with ft2 or xft I guess +[00:40:55] especially since I want the API NOT to make the font part of the font panel +[00:42:07] what sort of deprecated code are you trying to support? +[00:42:35] both of those are deprecated in pango fwiw +[00:43:06] on Linux im pretty sure we use FC everywhere these days +[00:44:46] (and gtk_widget_set_font_map() is how you get your custom font into a widget without affecting the global font lists, as layed out in that link) +[00:49:14] vasaikar (vasaikar@125.16.97.121) joined the channel +[00:50:14] karlt (karl@2400:e780:801:224:f121:e611:d139:e70e) left IRC (Client exited) +[00:50:49] karlt (karl@2400:e780:801:224:f121:e611:d139:e70e) joined the channel +[00:51:43] PioneerAxon (PioneerAxo@122.171.61.146) left IRC (Ping timeout: 180 seconds) +[00:57:47] PioneerAxon (PioneerAxo@106.201.37.181) joined the channel +[01:03:01] karlt (karl@2400:e780:801:224:f121:e611:d139:e70e) left IRC (Ping timeout: 181 seconds) +[01:05:49] muhannad (muhannad@95.218.26.152) left IRC (Quit: muhannad) +[01:07:51] hergertme: hm +[01:07:54] all right, thanks +[01:08:05] hergertme: fwiw right now my requirement is 3.10 +[01:10:47] ah, well you'll probably be missing the neccesary font API on gtk_widget +[01:11:04] but pango should be fine even back as far as https://developer.gnome.org/pango/1.28/PangoFcFontMap.html +[01:11:56] good +[01:12:04] because this is for custom drawing into a DrawingArea +[01:14:12] presumably just create your PangoContext as normal, but call pango_context_set_font_map() with the map you've setup. now, the load a font from a file i dont think was added to FontConfig until later though (not sure what release) +[01:15:53] FcConfigAppFontAddFile() <-- that API +[01:16:30] great, and they don't say what version the API was added in teh docs +function: ide_editor_map_bin_add() + +- Mouse ClickLock: do we need to do anything special? *should* we? https://msdn.microsoft.com/en-us/library/windows/desktop/ms724947(v=vs.85).aspx +- consider a uiAnticipateDoubleClick() or uiDoubleClickTime() (for a uiQueueTimer()) or something: https://blogs.msdn.microsoft.com/oldnewthing/20041015-00/?p=37553 + +- determine whether MSGF_USER is for and if it's correct for our uiArea message filter (if we have one) + +- source file encoding and MSVC compiler itself? https://stackoverflow.com/questions/20518040/how-can-i-get-the-directwrite-padwrite-sample-to-work + - also need to worry about object file and output encoding... + - this also names the author of the padwrite sample + +- OpenType features TODOs + - https://stackoverflow.com/questions/32545675/what-are-the-default-typography-settings-used-by-idwritetextlayout + - feature/shaping interaction rules for arabic: https://www.microsoft.com/typography/OpenTypeDev/arabic/intro.htm + - other stuff, mostly about UIs and what users expect to be able to set + - https://klim.co.nz/blog/towards-an-ideal-opentype-user-interface/ + - https://libregraphicsmeeting.org/2016/designing-for-many-applications-opentype-features-ui/ + - https://www.youtube.com/watch?v=wEyDhsH076Y + - https://twitter.com/peter_works + - http://ilovetypography.com/2014/10/22/better-ui-for-better-typography-adobe-petition/ + - http://silgraphite.sourceforge.net/ui/studynote.html + +- add NXCOMPAT (DEP awareness) to the Windows builds + - and ASLR too? or is that not a linker setting + +OS X: embedding an Info.plist into a binary directly +https://www.objc.io/issues/6-build-tools/mach-o-executables/ +TODO will this let Dictation work? +TODO investigate ad-hoc codesigning + +https://blogs.msdn.microsoft.com/oldnewthing/20040112-00/?p=41083 def files for decoration (I forget if I said this earlier) + +TODO ClipCursor() stuff; probably not useful for libui but still +https://blogs.msdn.microsoft.com/oldnewthing/20140102-00/?p=2183 +https://blogs.msdn.microsoft.com/oldnewthing/20061117-03/?p=28973 +https://msdn.microsoft.com/en-us/library/windows/desktop/ms648383(v=vs.85).aspx + +https://cmake.org/Wiki/CMake_Useful_Variables +set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-undefined") +On Unix systems, this will make linker report any unresolved symbols from object files (which is quite typical when you compile many targets in CMake projects, but do not bother with linking target dependencies in proper order). +(I used to have something like this back when I used makefiles; did it convert in? I forget) + +look into these for the os x port +https://developer.apple.com/documentation/appkit/view_management/nseditor?language=objc +https://developer.apple.com/documentation/appkit/view_management/nseditorregistration?language=objc + +for future versions of the os x port +https://developer.apple.com/documentation/appkit/nslayoutguide?language=objc and anchors +https://developer.apple.com/documentation/appkit/nsuserinterfacecompression?language=objc https://developer.apple.com/documentation/appkit/nsuserinterfacecompressionoptions?language=objc +though at some point we'll be able to use NSStackView and NSGridView directly, so... + +Cocoa PDFs +https://developer.apple.com/documentation/appkit/nspdfimagerep?language=objc +https://developer.apple.com/documentation/coregraphics?language=objc +https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_pagination/osxp_pagination.html#//apple_ref/doc/uid/20001051-119037 +https://developer.apple.com/documentation/appkit/nsprintoperation/1529269-pdfoperationwithview?language=objc +https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_printapps/osxp_printapps.html#//apple_ref/doc/uid/20000861-BAJBFGED +https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_printingapi/osxp_printingapi.html#//apple_ref/doc/uid/10000083i-CH2-SW2 +https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_printinfo/osxp_printinfo.html#//apple_ref/doc/uid/20000864-BAJBFGED +https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_printlayoutpanel/osxp_printlayoutpanel.html#//apple_ref/doc/uid/20000863-BAJBFGED +https://developer.apple.com/documentation/appkit/nspagelayout?language=objc +https://developer.apple.com/documentation/appkit/nsprintinfo?language=objc +https://developer.apple.com/documentation/applicationservices/core_printing?language=objc +https://developer.apple.com/documentation/applicationservices/1463247-pmcreatesession?language=objc +https://developer.apple.com/documentation/applicationservices/pmprintsession?language=objc +https://developer.apple.com/documentation/applicationservices/1460101-pmsessionbegincgdocumentnodialog?language=objc +https://developer.apple.com/documentation/applicationservices/1463416-pmsessionbeginpagenodialog?language=objc +https://developer.apple.com/documentation/applicationservices/1506831-anonymous/kpmdestinationprocesspdf?language=objc +https://developer.apple.com/documentation/applicationservices/1461960-pmcreategenericprinter?language=objc +https://developer.apple.com/documentation/applicationservices/1460101-pmsessionbegincgdocumentnodialog?language=objc +https://developer.apple.com/documentation/applicationservices/1464527-pmsessionenddocumentnodialog?language=objc +https://developer.apple.com/documentation/applicationservices/1461952-pmsessiongetcggraphicscontext?language=objc +https://developer.apple.com/library/content/technotes/tn2248/_index.html +https://developer.apple.com/library/content/samplecode/PMPrinterPrintWithFile/Introduction/Intro.html +https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_aboutprinting/osxp_aboutprt.html + +- run os x code with `OBJC_DEBUG_MISSING_POOLS=YES` and other `OBJC_HELP=YES` options + - turn off the autorelease pool to make sure we're not autoreleasing improperly + +TODO investigate -Weverything in clang alongside -Wall in MSVC (and in gcc too maybe...) + +mac os x accessibility +- https://developer.apple.com/documentation/appkit/nsworkspace/1524656-accessibilitydisplayshoulddiffer?language=objc +- https://developer.apple.com/documentation/appkit/nsworkspace/1526290-accessibilitydisplayshouldincrea?language=objc +- https://developer.apple.com/documentation/appkit/nsworkspace/1533006-accessibilitydisplayshouldreduce?language=objc + +uiEntry disabling bugs http://www.cocoabuilder.com/archive/cocoa/215525-nstextfield-bug-can-be.html +uiMultilineEntry disabling https://developer.apple.com/library/content/qa/qa1461/_index.html + +more TODOs: +- make no guarantee about buildability of feature branches diff --git a/oldhaiku.tgz b/_abort/oldhaiku.tgz similarity index 100% rename from oldhaiku.tgz rename to _abort/oldhaiku.tgz diff --git a/_abort/windowevents/darwin_window.m b/_abort/windowevents/darwin_window.m new file mode 100644 index 00000000..76180d84 --- /dev/null +++ b/_abort/windowevents/darwin_window.m @@ -0,0 +1,90 @@ +struct uiWindow { + // constraints + void (*onPositionChanged)(uiWindow *, void *); + void *onPositionChangedData; + BOOL suppressPositionChanged; + // onContentSizeChanged +}; + +@interface windowDelegateClass : NSObject { +// windowShouldClose: +- (void)windowDidMove:(NSNotification *)note; +// windowDidResize: +@end + +@implementation windowDelegateClass + +// - (BOOL)windowShouldClose:(id)sender + +// TODO doesn't happen live +- (void)windowDidMove:(NSNotification *)note +{ + uiWindow *w; + + w = [self lookupWindow:((NSWindow *) [note object])]; + if (!w->suppressPositionChanged) + (*(w->onPositionChanged))(w, w->onPositionChangedData); +} + +// - (void)windowDidResize:(NSNotification *)note + +// void uiWindowSetTitle(uiWindow *w, const char *title) + +void uiWindowPosition(uiWindow *w, int *x, int *y) +{ + NSScreen *screen; + NSRect r; + + r = [w->window frame]; + *x = r.origin.x; + // this is the right screen to use; thanks mikeash in irc.freenode.net/#macdev + // -mainScreen is useless for positioning (it's just the key window's screen) + // and we use -frame, not -visibleFrame, for dealing with absolute positions + screen = (NSScreen *) [[NSScreen screens] objectAtIndex:0]; + *y = ([screen frame].size.height - r.origin.y) - r.size.height; +} + +void uiWindowSetPosition(uiWindow *w, int x, int y) +{ + // -[NSWindow setFrameTopLeftPoint:] is acting weird so... + NSRect r; + NSScreen *screen; + + // this fires windowDidMove: + w->suppressPositionChanged = YES; + r = [w->window frame]; + r.origin.x = x; + screen = (NSScreen *) [[NSScreen screens] objectAtIndex:0]; + r.origin.y = [screen frame].size.height - (y + r.size.height); + [w->window setFrameOrigin:r.origin]; + w->suppressPositionChanged = NO; +} + +void uiWindowCenter(uiWindow *w) +{ + w->suppressPositionChanged = YES; + [w->window center]; + w->suppressPositionChanged = NO; +} + +void uiWindowOnPositionChanged(uiWindow *w, void (*f)(uiWindow *, void *), void *data) +{ + w->onPositionChanged = f; + w->onPositionChangedData = data; +} + +// void uiWindowContentSize(uiWindow *w, int *width, int *height) + +// static int defaultOnClosing(uiWindow *w, void *data) + +static void defaultOnPositionContentSizeChanged(uiWindow *w, void *data) +{ + // do nothing +} + +uiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar) +{ +// uiWindowOnClosing(w, defaultOnClosing, NULL); + uiWindowOnPositionChanged(w, defaultOnPositionContentSizeChanged, NULL); +// uiWindowOnContentSizeChanged(w, defaultOnPositionContentSizeChanged, NULL); +} diff --git a/_abort/windowevents/page15.c b/_abort/windowevents/page15.c new file mode 100644 index 00000000..0cceba86 --- /dev/null +++ b/_abort/windowevents/page15.c @@ -0,0 +1,65 @@ +static uiSpinbox *x, *y; + +static void moveX(uiSpinbox *s, void *data) +{ + uiWindow *w = uiWindow(data); + int xp, yp; + + uiWindowPosition(w, &xp, &yp); + xp = uiSpinboxValue(x); + uiWindowSetPosition(w, xp, yp); +} + +static void moveY(uiSpinbox *s, void *data) +{ + uiWindow *w = uiWindow(data); + int xp, yp; + + uiWindowPosition(w, &xp, &yp); + yp = uiSpinboxValue(y); + uiWindowSetPosition(w, xp, yp); +} + +static void updatepos(uiWindow *w) +{ + int xp, yp; + + uiWindowPosition(w, &xp, &yp); + uiSpinboxSetValue(x, xp); + uiSpinboxSetValue(y, yp); +} + +static void center(uiButton *b, void *data) +{ + uiWindow *w = uiWindow(data); + + uiWindowCenter(w); + updatepos(w); +} + +void onMove(uiWindow *w, void *data) +{ + printf("move\n"); + updatepos(w); +} + +uiBox *makePage15(uiWindow *w) +{ + hbox = newHorizontalBox(); + // TODO if I make this 1 and not add anything else AND not call uiWindowOnPositionChanged(), on OS X the box won't be able to grow vertically + uiBoxAppend(page15, uiControl(hbox), 0); + + uiBoxAppend(hbox, uiControl(uiNewLabel("Position")), 0); + x = uiNewSpinbox(INT_MIN, INT_MAX); + uiBoxAppend(hbox, uiControl(x), 1); + y = uiNewSpinbox(INT_MIN, INT_MAX); + uiBoxAppend(hbox, uiControl(y), 1); + button = uiNewButton("Center"); + uiBoxAppend(hbox, uiControl(button), 0); + + uiSpinboxOnChanged(x, moveX, w); + uiSpinboxOnChanged(y, moveY, w); + uiButtonOnClicked(button, center, w); + uiWindowOnPositionChanged(w, onMove, NULL); + updatepos(w); +} diff --git a/_abort/windowevents/ui.h b/_abort/windowevents/ui.h new file mode 100644 index 00000000..e0d24c7f --- /dev/null +++ b/_abort/windowevents/ui.h @@ -0,0 +1,6 @@ +// uiWindowSetTitle +_UI_EXTERN void uiWindowPosition(uiWindow *w, int *x, int *y); +_UI_EXTERN void uiWindowSetPosition(uiWindow *w, int x, int y); +_UI_EXTERN void uiWindowCenter(uiWindow *w); +_UI_EXTERN void uiWindowOnPositionChanged(uiWindow *w, void (*f)(uiWindow *, void *), void *data); +// uiWindowContentSize diff --git a/_abort/windowevents/unix_window.c b/_abort/windowevents/unix_window.c new file mode 100644 index 00000000..96af26aa --- /dev/null +++ b/_abort/windowevents/unix_window.c @@ -0,0 +1,97 @@ +struct uiWindow { +// void *onClosingData; + void (*onPositionChanged)(uiWindow *, void *); + void *onPositionChangedData; + gboolean changingPosition; +// void (*onContentSizeChanged)(uiWindow *, void *); +}; + +// static gboolean onClosing(GtkWidget *win, GdkEvent *e, gpointer data) + +static gboolean onConfigure(GtkWidget *win, GdkEvent *e, gpointer data) +{ + uiWindow *w = uiWindow(data); + + // there doesn't seem to be a way to determine if only moving or only resizing is happening :/ + if (w->changingPosition) + w->changingPosition = FALSE; + else + (*(w->onPositionChanged))(w, w->onPositionChangedData); + // always continue handling + return FALSE; +} + +// static void onSizeAllocate(GtkWidget *widget, GdkRectangle *allocation, gpointer data) + +// static int defaultOnClosing(uiWindow *w, void *data) + +static void defaultOnPositionContentSizeChanged(uiWindow *w, void *data) +{ + // do nothing +} + +// static void uiWindowDestroy(uiControl *c) + +// void uiWindowSetTitle(uiWindow *w, const char *title) + +// TODO allow specifying either as NULL on all platforms +void uiWindowPosition(uiWindow *w, int *x, int *y) +{ + gint rx, ry; + + gtk_window_get_position(w->window, &rx, &ry); + *x = rx; + *y = ry; +} + +void uiWindowSetPosition(uiWindow *w, int x, int y) +{ + w->changingPosition = TRUE; + gtk_window_move(w->window, x, y); + // gtk_window_move() is asynchronous + // we need to wait for a configure-event + // thanks to hergertme in irc.gimp.net/#gtk+ + while (w->changingPosition) + if (!uiMainStep(1)) + break; // stop early if uiQuit() called +} + +void uiWindowCenter(uiWindow *w) +{ + gint x, y; + GtkAllocation winalloc; + GdkWindow *gdkwin; + GdkScreen *screen; + GdkRectangle workarea; + + gtk_widget_get_allocation(w->widget, &winalloc); + gdkwin = gtk_widget_get_window(w->widget); + screen = gdk_window_get_screen(gdkwin); + gdk_screen_get_monitor_workarea(screen, + gdk_screen_get_monitor_at_window(screen, gdkwin), + &workarea); + + x = (workarea.width - winalloc.width) / 2; + y = (workarea.height - winalloc.height) / 2; + // TODO move up slightly? see what Mutter or GNOME Shell or GNOME Terminal do(es)? + uiWindowSetPosition(w, x, y); +} + +// TODO this and size changed get set during uiWindowDestroy +void uiWindowOnPositionChanged(uiWindow *w, void (*f)(uiWindow *, void *), void *data) +{ + w->onPositionChanged = f; + w->onPositionChangedData = data; +} + +// void uiWindowContentSize(uiWindow *w, int *width, int *height) + +uiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar) +{ +// g_signal_connect(w->widget, "delete-event", G_CALLBACK(onClosing), w); + g_signal_connect(w->widget, "configure-event", G_CALLBACK(onConfigure), w); +// g_signal_connect(w->childHolderWidget, "size-allocate", G_CALLBACK(onSizeAllocate), w); +// uiWindowOnClosing(w, defaultOnClosing, NULL); + uiWindowOnPositionChanged(w, defaultOnPositionContentSizeChanged, NULL); +// uiWindowOnContentSizeChanged(w, defaultOnPositionContentSizeChanged, NULL); +} diff --git a/_abort/windowevents/windows_window.cpp b/_abort/windowevents/windows_window.cpp new file mode 100644 index 00000000..769c6db9 --- /dev/null +++ b/_abort/windowevents/windows_window.cpp @@ -0,0 +1,86 @@ +struct uiWindow { +// BOOL hasMenubar; + void (*onPositionChanged)(uiWindow *, void *); + void *onPositionChangedData; + BOOL changingPosition; // to avoid triggering the above when programmatically doing this +// void (*onContentSizeChanged)(uiWindow *, void *); +}; + +static LRESULT CALLBACK windowWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + case WM_WINDOWPOSCHANGED: + if ((wp->flags & SWP_NOMOVE) == 0) + if (!w->changingPosition) + (*(w->onPositionChanged))(w, w->onPositionChangedData); + // and continue anyway +// if ((wp->flags & SWP_NOSIZE) != 0) +} + +// static int defaultOnClosing(uiWindow *w, void *data) + +static void defaultOnPositionContentSizeChanged(uiWindow *w, void *data) +{ + // do nothing +} + +// static std::map windows; + +// void uiWindowSetTitle(uiWindow *w, const char *title) + +void uiWindowPosition(uiWindow *w, int *x, int *y) +{ + RECT r; + + uiWindowsEnsureGetWindowRect(w->hwnd, &r); + *x = r.left; + *y = r.top; +} + +void uiWindowSetPosition(uiWindow *w, int x, int y) +{ + w->changingPosition = TRUE; + if (SetWindowPos(w->hwnd, NULL, x, y, 0, 0, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER) == 0) + logLastError(L"error moving window"); + w->changingPosition = FALSE; +} + +// static void windowMonitorRect(HWND hwnd, RECT *r) + +// TODO use the work rect instead? +void uiWindowCenter(uiWindow *w) +{ + RECT wr, mr; + int x, y; + LONG wwid, mwid; + LONG wht, mht; + + uiWindowsEnsureGetWindowRect(w->hwnd, &wr); + windowMonitorRect(w->hwnd, &mr); + wwid = wr.right - wr.left; + mwid = mr.right - mr.left; + x = (mwid - wwid) / 2; + wht = wr.bottom - wr.top; + mht = mr.bottom - mr.top; + y = (mht - wht) / 2; + // y is now evenly divided, however https://msdn.microsoft.com/en-us/library/windows/desktop/dn742502(v=vs.85).aspx says that 45% should go above and 55% should go below + // so just move 5% of the way up + // TODO should this be on the work area? + // TODO is this calculation correct? + y -= y / 20; + uiWindowSetPosition(w, x, y); +} + +void uiWindowOnPositionChanged(uiWindow *w, void (*f)(uiWindow *, void *), void *data) +{ + w->onPositionChanged = f; + w->onPositionChangedData = data; +} + +// void uiWindowContentSize(uiWindow *w, int *width, int *height) + +uiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar) +{ +// uiWindowOnClosing(w, defaultOnClosing, NULL); + uiWindowOnPositionChanged(w, defaultOnPositionContentSizeChanged, NULL); +// uiWindowOnContentSizeChanged(w, defaultOnPositionContentSizeChanged, NULL); +} diff --git a/_future/otherattributes/ui.h b/_future/otherattributes/ui.h new file mode 100644 index 00000000..697f09bc --- /dev/null +++ b/_future/otherattributes/ui.h @@ -0,0 +1,190 @@ +_UI_ENUM(uiAttribute) { + uiAttributeFamily, + uiAttributeSize, // use Double + uiAttributeWeight, + uiAttributeItalic, + uiAttributeStretch, + uiAttributeColor, // use R, G, B, A + uiAttributeBackground, // use R, G, B, A + + // TODO kerning amount + // OS X: kCTKernAttributeName + // > 0: farther (TODO from advance or standard kerning?) + // == 0: no kerning + // < 0: closer (TODO same) + // undefined: standard kerning + // Pango: pango_attr_letter_spacing_new() + // parameter meaning unspecified + // Windows: requires Platform Update, SetLetterSpacing() + // parameter meaning unspecified + + uiAttributeUnderline, // enum uiDrawUnderlineStyle + // TODO what is the color in the case we don't specify it, black or the text color? + uiAttributeUnderlineColor, // enum uiDrawUnderlineColor + + // TODO kCTSuperscriptAttributeName vs below + // all it does is set the below attribute so + + // TODO kCTBaselineClassAttributeName, kCTBaselineInfoAttributeName, kCTBaselineReferenceInfoAttributeName + + // TODO strikethroughs? (pango yes, directwrite yes, os x no) + // TODO baseline offsets? (pango yes) + // TODO size scales? (pango yes) + // TODO fallbacks (pango: enable or disable) + + // TODO document that this will also enable language-specific font features (TODO on DirectWrite too?) + // TODO document that this should be strict BCP 47 form (A-Z, a-z, 0-9, and -) for maximum compatibility + uiAttributeLanguage, // BCP 47 string + + // These attributes represent typographic features. Each feature + // is a separate attribute, to make composition easier. The + // availability of for each attribute are defined by the font; the + // default values are defined by the font and/or by the OS. + // + // A note about features whose parameter is an enumeration: + // OS X defines typographic features using the AAT specification + // and converts to OpenType internally when needed, whereas + // other platforms use OpenType directly. OpenType is less + // precise about what each enumeration value means than AAT + // is, so enumeration values do not necessarily represent what + // OS X expects with all fonts. In cases where they do, libui + // provides an enumeration type to use. Otherwise, the AAT + // enumeration values are provided in comments for + // documentation purposes. + + // TODO kAllTypographicFeaturesType + + // AAT calls these "common ligatures" + uiAttributeStandardLigatures, // 0 = off, 1 = on + uiAttributeRequiredLigatures, // 0 = off, 1 = on + // AAT calls these "rare ligatures" + uiAttributeDiscretionaryLigatures, // 0 = off, 1 = on + uiAttributeContextualLigatures, // 0 = off, 1 = on + uiAttributeHistoricalLigatures, // 0 = off, 1 = on + + // TODO uiAttributeCursiveConnection, // 0 = none, 1 = some, 2 = all + + uiAttributeUnicase, // 0 = off, 1 = on + + // TODO uiAttributeLinguisticRearrangement, // 0 = off, 1 = on + + // TODO rename this + uiAttributeNumberSpacings, // enum uiAttributeNumberSpacing + + // TODO kSmartSwashType, falt and jalt + + // TODO kDiacriticsType + + uiAttributeSuperscripts, // enum uiAttributeSuperscript + + uiAttributeFractionForms, // enum uiAttributeFractionForm + + uiAttributeSlashedZero, // 0 = off, 1 = on + + uiAttributeMathematicalGreek, // 0 = off, 1 = on + + // AAT defines the following values: + // 0 = none + // 1 = dingbats + // 2 = pi characters + // 3 = fleurons + // 4 = decorative borders + // 5 = international symbols + // 6 = mathematical symbols + // OpenType says alphanumeric characters must(? TODO) have one form each and the bullet character U+2022 (•) can have many + uiAttributeOrnamentalForms, // an integer from 0 to a font-specified upper bound + // TODO provide a function to get the upper bound? + + // AAT calls this "character alternatives" and defines the + // following values: + // 0 = none + // OpenType calls this "access all alternates". + // TODO doesn't OpenType do the same about 0? + uiAttributeSpecificCharacterForm, // an integer from 0 to a font-specified upper bound + // TODO provide a function to get the upper bound? + + uiAttributeTitlingCapitalForms, // 0 = off, 1 = on + + // AAT calls these "character shapes" + uiAttributeHanCharacterForms, // enum uiAttributeHanCharacterForm + + // OpenType calls these "old-style" + uiAttributeLowercaseNumbers, // 0 = off, 1 = on + + // TODO kTextSpacingType + // see kKanaSpacingType below + + uiAttributeHanjaToHangul, // 0 = off, 1 = on + + // AAT defines the following values: + // 0 = none + // 1 = box + // 2 = rounded box + // 3 = circle + // 4 = inverted circle + // 5 = parentheses + // 6 = period + // 7 = roman numeral + // 8 = diamond + // 9 = inverted box + // 10 = inverted rounded box + // TODO rename to AnnotatedForms? + uiAttributeAnnotatedGlyphForms, // an integer from 0 to a font-specified upper bound + // TODO provide a function to get the upper bound? + + // TODO kKanaSpacingType + // TODO kIdeographicSpacingType + // can they be provided independently of kTextSpacingType? Core Text doesn't seem to + + // TODO kUnicodeDecompositionType + + uiAttributeRubyKanaForms, // 0 = off, 1 = on + + // TODO kCJKVerticalRomanPlacementType + // this is 'valt' in OpenType but I don't know if I want to make it selectable or not + + uiAttributeCJKRomansToItalics, // 0 = off, 1 = on + + // AAT calls this "case-sensitive layout" + uiAttributeCaseSensitiveForms, // 0 = off, 1 = on + // AAT: this is called "case-sensitive spacing" + uiAttributeCapitalSpacing, // 0 = off, 1 = on + + uiAttributeAlternateHorizontalKana, // 0 = off, 1 = on + uiAttributeAlternateVerticalKana, // 0 = off, 1 = on + + // TODO "Alternate"? unify all this + // TODO document that these are guaranteed to be consecutive + uiAttributeStylisticAlternate1, // 0 = off, 1 = on + uiAttributeStylisticAlternate2, // 0 = off, 1 = on + uiAttributeStylisticAlternate3, // 0 = off, 1 = on + uiAttributeStylisticAlternate4, // 0 = off, 1 = on + uiAttributeStylisticAlternate5, // 0 = off, 1 = on + uiAttributeStylisticAlternate6, // 0 = off, 1 = on + uiAttributeStylisticAlternate7, // 0 = off, 1 = on + uiAttributeStylisticAlternate8, // 0 = off, 1 = on + uiAttributeStylisticAlternate9, // 0 = off, 1 = on + uiAttributeStylisticAlternate10, // 0 = off, 1 = on + uiAttributeStylisticAlternate11, // 0 = off, 1 = on + uiAttributeStylisticAlternate12, // 0 = off, 1 = on + uiAttributeStylisticAlternate13, // 0 = off, 1 = on + uiAttributeStylisticAlternate14, // 0 = off, 1 = on + uiAttributeStylisticAlternate15, // 0 = off, 1 = on + uiAttributeStylisticAlternate16, // 0 = off, 1 = on + uiAttributeStylisticAlternate17, // 0 = off, 1 = on + uiAttributeStylisticAlternate18, // 0 = off, 1 = on + uiAttributeStylisticAlternate19, // 0 = off, 1 = on + uiAttributeStylisticAlternate20, // 0 = off, 1 = on + + uiAttributeContextualAlternates, // 0 = off, 1 = on + uiAttributeSwashes, // 0 = off, 1 = on + uiAttributeContextualSwashes, // 0 = off, 1 = on + + uiAttributeLowercaseCapForms, // enum uiAttributeCapForm + uiAttributeUppercaseCapForms, // enum uiAttributeCapForm + + // TODO kCJKRomanSpacingType + + // TODO uiAttributeSystem, (this might not be doable with DirectWrite) + // TODO uiAttributeCustom, +}; diff --git a/_future/textlanguageattr/README b/_future/textlanguageattr/README new file mode 100644 index 00000000..f3bb4916 --- /dev/null +++ b/_future/textlanguageattr/README @@ -0,0 +1 @@ +Removed because proper support on OS X doesn't come until 10.9 unless we use a font with an ltag table; none of the fonts I have come with ltag tables (none of the fonts on OS X do, or at least don't come with a sr entry in their ltag table, and OpenType has replaced ltag with what appears to be custom sub-tables of the GPOS and GSUB tables.) diff --git a/_future/textlanguageattr/attrstr_darwin.m b/_future/textlanguageattr/attrstr_darwin.m new file mode 100644 index 00000000..1717d875 --- /dev/null +++ b/_future/textlanguageattr/attrstr_darwin.m @@ -0,0 +1,19 @@ +struct fontParams { + uiDrawFontDescriptor desc; + uint16_t featureTypes[maxFeatures]; + uint16_t featureSelectors[maxFeatures]; + size_t nFeatures; + const char *language; +}; + + + // locale identifiers are specified as BCP 47: https://developer.apple.com/reference/corefoundation/cflocale?language=objc + case uiAttributeLanguage: + // LONGTERM FUTURE when we move to 10.9, switch to using kCTLanguageAttributeName + ensureFontInRange(p, start, end); + adjustFontInRange(p, start, end, ^(struct fontParams *fp) { + fp->language = (const char *) (spec->Value); + }); + break; + + desc = fontdescAppendFeatures(desc, fp->featureTypes, fp->featureSelectors, fp->nFeatures, fp->language); diff --git a/_future/textlanguageattr/attrstr_unix.c b/_future/textlanguageattr/attrstr_unix.c new file mode 100644 index 00000000..47d22b15 --- /dev/null +++ b/_future/textlanguageattr/attrstr_unix.c @@ -0,0 +1,9 @@ + PangoLanguage *lang; + + // language strings are specified as BCP 47: https://developer.gnome.org/pango/1.30/pango-Scripts-and-Languages.html#pango-language-from-string https://www.ietf.org/rfc/rfc3066.txt + case uiAttributeLanguage: + lang = pango_language_from_string((const char *) (spec->Value)); + addattr(p, start, end, + pango_attr_language_new(lang)); + // lang *cannot* be freed + break; diff --git a/_future/textlanguageattr/attrstr_windows.cpp b/_future/textlanguageattr/attrstr_windows.cpp new file mode 100644 index 00000000..f8238c7f --- /dev/null +++ b/_future/textlanguageattr/attrstr_windows.cpp @@ -0,0 +1,10 @@ + WCHAR *localeName; + + // locale names are specified as BCP 47: https://msdn.microsoft.com/en-us/library/windows/desktop/dd373814(v=vs.85).aspx https://www.ietf.org/rfc/rfc4646.txt + case uiAttributeLanguage: + localeName = toUTF16((char *) (spec->Value)); + hr = p->layout->SetLocaleName(localeName, range); + if (hr != S_OK) + logHRESULT(L"error applying locale name attribute", hr); + uiFree(localeName); + break; \ No newline at end of file diff --git a/_future/textlanguageattr/common_attrlist.c b/_future/textlanguageattr/common_attrlist.c new file mode 100644 index 00000000..680a2d09 --- /dev/null +++ b/_future/textlanguageattr/common_attrlist.c @@ -0,0 +1,2 @@ + case uiAttributeLanguage: + return asciiStringsEqualCaseFold((char *) (attr->spec.Value), (char *) (spec->Value)); diff --git a/_future/textlanguageattr/drawtext_example.c b/_future/textlanguageattr/drawtext_example.c new file mode 100644 index 00000000..e006e7e1 --- /dev/null +++ b/_future/textlanguageattr/drawtext_example.c @@ -0,0 +1,27 @@ +before "or any combination of the above" + + // thanks to https://twitter.com/codeman38/status/831924064012886017 + next = "\xD0\xB1\xD0\xB3\xD0\xB4\xD0\xBF\xD1\x82"; + uiAttributedStringAppendUnattributed(attrstr, "multiple languages (compare "); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeItalic; + spec.Value = uiDrawTextItalicItalic; + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + spec.Type = uiAttributeLanguage; + spec.Value = (uintptr_t) "ru"; + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, " to "); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeItalic; + spec.Value = uiDrawTextItalicItalic; + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + spec.Type = uiAttributeLanguage; + spec.Value = (uintptr_t) "sr"; + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, " \xE2\x80\x94 may require changing the font)"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); diff --git a/_future/textlanguageattr/fontmatch_darwin.m b/_future/textlanguageattr/fontmatch_darwin.m new file mode 100644 index 00000000..cd4df7a1 --- /dev/null +++ b/_future/textlanguageattr/fontmatch_darwin.m @@ -0,0 +1,112 @@ +// note: this doesn't work for languages; we have to parse the ltag table + +// fortunately features that aren't supported are simply ignored, so we can copy them all in +// LONGTERM FUTURE when we switch to 10.9, the language parameter won't be needed anymore +// LONGTERM FUTURE and on 10.10 we can use OpenType tags directly! +CTFontDescriptorRef fontdescAppendFeatures(CTFontDescriptorRef desc, const uint16_t *types, const uint16_t *selectors, size_t n, const char *language) +{ + CTFontDescriptorRef new; + CFMutableArrayRef outerArray; + CFDictionaryRef innerDict; + CFNumberRef numType, numSelector; + const void *keys[2], *values[2]; + size_t i; + CFArrayRef languages; + CFIndex il, nl; + CFStringRef curlang; + char d[2]; + + outerArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + if (outerArray == NULL) { + // TODO + } + keys[0] = kCTFontFeatureTypeIdentifierKey; + keys[1] = kCTFontFeatureSelectorIdentifierKey; + for (i = 0; i < n; i++) { + numType = CFNumberCreate(NULL, kCFNumberSInt16Type, + (const SInt16 *) (types + i)); + numSelector = CFNumberCreate(NULL, kCFNumberSInt16Type, + (const SInt16 *) (selectors + i)); + values[0] = numType; + values[1] = numSelector; + innerDict = CFDictionaryCreate(NULL, + keys, values, 2, + // TODO are these correct? + &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + if (innerDict == NULL) { + // TODO + } + CFArrayAppendValue(outerArray, innerDict); + CFRelease(innerDict); + CFRelease(numSelector); + CFRelease(numType); + } + + // now we have to take care of the language + if (language != NULL) { + languages = CTFontDescriptorCopyAttribute(desc, kCTFontLanguagesAttribute); + if (languages != NULL) { + nl = CFArrayGetCount(languages); + d[0] = language[0]; + if (d[0] >= 'A' && d[0] <= 'Z') + d[0] += 'a' - 'A'; + d[1] = language[1]; + if (d[1] >= 'A' && d[1] <= 'Z') + d[1] += 'a' - 'A'; + for (il = 0; il < nl; il++) { + char c[2]; + + curlang = (CFStringRef) CFArrayGetValueAtIndex(languages, il); + // TODO check for failure + CFStringGetBytes(curlang, CFRangeMake(0, 2), + kCFStringEncodingUTF8, 0, false, + (UInt8 *) c, 2, NULL); + if (c[0] >= 'A' && c[0] <= 'Z') + c[0] += 'a' - 'A'; + if (c[1] >= 'A' && c[1] <= 'Z') + c[1] += 'a' - 'A'; + if (c[0] == d[0] && c[1] == d[1]) + break; + } + if (il != nl) { + uint16_t typ; + + typ = kLanguageTagType; + il++; + numType = CFNumberCreate(NULL, kCFNumberSInt16Type, + (const SInt16 *) (&typ)); + numSelector = CFNumberCreate(NULL, kCFNumberCFIndexType, + &il); + values[0] = numType; + values[1] = numSelector; + innerDict = CFDictionaryCreate(NULL, + keys, values, 2, + // TODO are these correct? + &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + if (innerDict == NULL) { + // TODO + } + CFArrayAppendValue(outerArray, innerDict); + CFRelease(innerDict); + CFRelease(numSelector); + CFRelease(numType); + } + CFRelease(languages); + } + } + + keys[0] = kCTFontFeatureSettingsAttribute; + values[0] = outerArray; + innerDict = CFDictionaryCreate(NULL, + keys, values, 1, + // TODO are these correct? + &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CFRelease(outerArray); + new = CTFontDescriptorCreateCopyWithAttributes(desc, innerDict); + CFRelease(desc); + CFRelease(innerDict); + return new; +} diff --git a/_future/textlanguageattr/ui.h b/_future/textlanguageattr/ui.h new file mode 100644 index 00000000..2c3c9ec7 --- /dev/null +++ b/_future/textlanguageattr/ui.h @@ -0,0 +1,5 @@ +after UnderlineColor, before feature tags + + // TODO document that this will also enable language-specific font features (TODO on DirectWrite too?) + // TODO document that this should be strict BCP 47 form (A-Z, a-z, 0-9, and -) for maximum compatibility + uiAttributeLanguage, // BCP 47 string diff --git a/_future/unittest/checklist_attrstr b/_future/unittest/checklist_attrstr new file mode 100644 index 00000000..7ba0f244 --- /dev/null +++ b/_future/unittest/checklist_attrstr @@ -0,0 +1,25 @@ += attributed strings +attribute lengths are rounded to complete unicode codepoints +zero-length attributes are elided +consecutive attributes of the same type and value are merged +overlapping attributes of different types do not split each other +overlapping attributes of the same type but different values do split +empty string is allowed +empty string cannot have attributes +font family names are case-insensitive both in attributes and in descriptors +attributes are unique throughout a Unicode codepoint, not just to UTF-8 bytes +define what "it is an error" means in the case of uiFreeAttribute() and all uiAttributeValue() functions and constructors +does uiAttributeFamily() return a normalized string +should uiNewAttributeBackground() be renamed to uiNewAttributeBackgroundColor() and likewise for the type constant +should underline colors just ignore non-custom component arguments +should any color getter function accept a NULL pointer +what should uiAttributeUnderlineColor() do if the color type isn't Custom but the other pointers are non-NULL +should uiOpenTypeFeaturesGet() accept a NULL value pointer +what happens if uiOpenTypeFeaturesForEach() is given a NULl function pointer +should FeaturesAttribute be changed to OpenTypeFeaturesAttribute and likewise for the type enum +should uiNewFeaturesAttribute() accept NULL +should uiNewFamilyAttribute() accept NULL +it is an error in ForEach too +invalid values for uiDrawTextAlign +empty text layouts have one line +TODO figure out what to do if any field (particularly the font family name) in uiFontDescriptor is unset diff --git a/_future/unittest/opentype_test.c b/_future/unittest/opentype_test.c new file mode 100644 index 00000000..0c96108c --- /dev/null +++ b/_future/unittest/opentype_test.c @@ -0,0 +1,115 @@ +// 27 february 2018 +#ifndef TODO_TEST +#error TODO this is where libui itself goes +#endif +#include +#include "testing.h" + +#include +#include +#include +#include +typedef struct uiOpenTypeFeatures uiOpenTypeFeatures; +typedef int uiForEach; +enum { uiForEachContinue, uiForEachStop }; +typedef uiForEach (*uiOpenTypeFeaturesForEachFunc)(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value, void *data); +#define uiprivNew(x) ((x *) malloc(sizeof (x))) +#define uiprivAlloc(x,y) malloc(x) +#define uiprivRealloc(x,y,z) realloc(x,y) +#define uiprivFree free +#include "opentype.c" + +static void freeOpenType(void *otf) +{ + uiFreeOpenTypeFeatures((uiOpenTypeFeatures *) otf); +} + +testingTest(OpenTypeFeaturesAddGet) +{ + uiOpenTypeFeatures *otf; + int got; + uint32_t value; + + otf = uiNewOpenTypeFeatures(); + testingTDefer(t, freeOpenType, otf); + uiOpenTypeFeaturesAdd(otf, 'a', 'b', 'c', 'd', 12345); + got = uiOpenTypeFeaturesGet(otf, 'a', 'b', 'c', 'd', &value); + + if (!got) + testingTErrorf(t, "uiOpenTypeFeaturesGet() failed to get feature we added"); + else if (value != 12345) + testingTErrorf(t, "feature abcd: got %" PRIu32 ", want 12345", value); +} + +testingTest(OpenTypeFeaturesRemove) +{ + uiOpenTypeFeatures *otf; + uint32_t value; + + otf = uiNewOpenTypeFeatures(); + testingTDefer(t, freeOpenType, otf); + uiOpenTypeFeaturesAdd(otf, 'a', 'b', 'c', 'd', 12345); + uiOpenTypeFeaturesRemove(otf, 'a', 'b', 'c', 'd'); + + if (uiOpenTypeFeaturesGet(otf, 'a', 'b', 'c', 'd', &value)) + testingTErrorf(t, "uiOpenTypeFeaturesGet() succeeded in getting deleted feature; value %" PRIu32, value); +} + +testingTest(OpenTypeFeaturesCloneAdd) +{ + uiOpenTypeFeatures *otf, *otf2; + uint32_t value; + + otf = uiNewOpenTypeFeatures(); + testingTDefer(t, freeOpenType, otf); + uiOpenTypeFeaturesAdd(otf, 'a', 'b', 'c', 'd', 12345); + otf2 = uiOpenTypeFeaturesClone(otf); + testingTDefer(t, freeOpenType, otf2); + uiOpenTypeFeaturesAdd(otf2, 'q', 'w', 'e', 'r', 56789); + + if (uiOpenTypeFeaturesGet(otf, 'q', 'w', 'e', 'r', &value)) + testingTErrorf(t, "uiOpenTypeFeaturesGet() on original succeeded in getting feature added to clone; value %" PRIu32, value); +} + +testingTest(OpenTypeFeaturesCloneModify) +{ + uiOpenTypeFeatures *otf, *otf2; + uint32_t value; + + otf = uiNewOpenTypeFeatures(); + testingTDefer(t, freeOpenType, otf); + uiOpenTypeFeaturesAdd(otf, 'a', 'b', 'c', 'd', 12345); + otf2 = uiOpenTypeFeaturesClone(otf); + testingTDefer(t, freeOpenType, otf2); + uiOpenTypeFeaturesAdd(otf2, 'a', 'b', 'c', 'd', 56789); + + uiOpenTypeFeaturesGet(otf, 'a', 'b', 'c', 'd', &value); + if (value != 12345) + testingTErrorf(t, "uiOpenTypeFeaturesGet() on original: got %" PRIu32 ", want 12345", value); + uiOpenTypeFeaturesGet(otf2, 'a', 'b', 'c', 'd', &value); + if (value != 56789) + testingTErrorf(t, "uiOpenTypeFeaturesGet() on clone: got %" PRIu32 ", want 56789", value); +} + +testingTest(OpenTypeFeaturesCloneRemove) +{ + uiOpenTypeFeatures *otf, *otf2; + uint32_t value; + + otf = uiNewOpenTypeFeatures(); + testingTDefer(t, freeOpenType, otf); + uiOpenTypeFeaturesAdd(otf, 'a', 'b', 'c', 'd', 12345); + otf2 = uiOpenTypeFeaturesClone(otf); + testingTDefer(t, freeOpenType, otf2); + uiOpenTypeFeaturesRemove(otf2, 'a', 'b', 'c', 'd'); + + if (uiOpenTypeFeaturesGet(otf2, 'a', 'b', 'c', 'd', &value)) + testingTErrorf(t, "uiOpenTypeFeaturesGet() on clone succeeded in getting feature removed from clone; value %" PRIu32, value); + if (!uiOpenTypeFeaturesGet(otf, 'a', 'b', 'c', 'd', &value)) + testingTErrorf(t, "uiOpenTypeFeaturesGet() on original failed to get feature removed from clone"); +} + +int main(void) +{ + return testingMain(); +} diff --git a/_future/unittest/testing.h b/_future/unittest/testing.h new file mode 100644 index 00000000..e9aee7ee --- /dev/null +++ b/_future/unittest/testing.h @@ -0,0 +1,137 @@ +// 27 february 2018 + +// TODO +// - https://blogs.msdn.microsoft.com/oldnewthing/20181107-00/?p=100155 https://blogs.msdn.microsoft.com/oldnewthing/20181108-00/?p=100165 https://blogs.msdn.microsoft.com/oldnewthing/20181109-00/?p=100175 +// - also in the above: note the unspecified order of data in the sub-segments... + +#ifndef testingprivIncludeGuard_testing_h +#define testingprivIncludeGuard_testing_h + +#include + +#undef testingprivBadLanguageVersion +#ifdef __cplusplus +// TODO https://stackoverflow.com/questions/2324658/how-to-determine-the-version-of-the-c-standard-used-by-the-compiler implies this won't do with C++0x-era compilers, and https://wiki.apache.org/stdcxx/C++0xCompilerSupport doesn't talk about va_copy() so a simple version check for the C99 preprocessor may be wrong... +// TODO what if __cplusplus is blank (maybe only in that case, since IIRC C++98 requires __cplusplus to have a value)? +#if __cplusplus < 201103L +#define testingprivBadLanguageVersion +#endif +#elif !defined(__STDC_VERSION__) +#define testingprivBadLanguageVersion +#elif __STDC_VERSION__ < 199901L +#define testingprivBadLanguageVersion +#endif +#ifdef testingprivBadLanguageVersion +#error sorry, TODO requires either C99 or C++11; cannot continue +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +#define testingprivMkScaffold(name) \ + static inline void testingprivScaffold ## name(testingT *t) \ + { \ + bool failedNow = false, skippedNow = false; \ + try { name(t); } \ + catch (testingprivFailNowException e) { failedNow = true; } \ + catch (testingprivSkipNowException e) { skippedNow = true; } \ + /* TODO see if we should catch other exceptions too */ \ + /* don't call these in the catch blocks as they call longjmp() */ \ + if (failedNow) testingprivTDoFailNow(t); \ + if (skippedNow) testingprivTDoSkipNow(t); \ + } +#else +#define testingprivMkScaffold(name) \ + static inline void testingprivScaffold ## name(testingT *t) { name(t); } +#endif + +// references: +// - https://gitlab.gnome.org/GNOME/glib/blob/master/glib/gconstructor.h +// - https://gitlab.gnome.org/GNOME/glib/blob/master/gio/glib-compile-resources.c +// - https://msdn.microsoft.com/en-us/library/bb918180.aspx +#if defined(__cplusplus) +#define testingprivMkCtor(name, reg) \ + static reg ## Class testingprivCtor ## name(#name, testingprivScaffold ## name); +#elif defined(__GNUC__) +#define testingprivMkCtor(name, reg) \ + __attribute__((constructor)) static void testingprivCtor ## name(void) { reg(#name, testingprivScaffold ## name); } +#elif defined(_MSC_VER) +#define testingprivMkCtorPrototype(name, reg) \ + static int name(void) testingprivCtor ## name(void) { reg(#name, testingprivScaffold ## name); return 0; } \ + __pragma(section(".CRT$XCU",read)) \ + __declspec(allocate(".CRT$XCU")) static int (*testingprivCtorPtr ## name)(void) = testingprivCtor ## name; +#elif defined(__SUNPRO_C) +#define testingprivMkCtor(name, reg) \ + _Pragma("init(testingprivCtor" #name ")") static void testingprivCtor ## name(void) { reg(#name, testingprivScaffold ## name); } +#else +#error unknown compiler for making constructors in C; cannot continue +#endif + +#define testingTest(Name) \ + void Test ## Name(testingT *t); \ + testingprivMkScaffold(Test ## Name) \ + testingprivMkCtor(Test ## Name, testingprivRegisterTest) \ + void Test ## Name(testingT *t) + +extern int testingMain(void); + +typedef struct testingT testingT; +#define testingTLogf(t, ...) \ + testingprivExpand(testingprivTLogfThen((void), t, __VA_ARGS__)) +#define testingTLogvf(t, format, ap) \ + testingprivTLogvfThen((void), t, format, ap) +#define testingTErrorf(t, ...) \ + testingprivExpand(testingprivTLogfThen(testingTFail, t, __VA_ARGS__)) +#define testingTErrorvf(t, format, ap) \ + testingprivTLogvfThen(testingTFail, t, format, ap) +#define testingTFatalf(t, ...) \ + testingprivExpand(testingprivTLogfThen(testingTFailNow, t, __VA_ARGS__)) +#define testingTFatalvf(t, format, ap) \ + testingprivTLogvfThen(testingTFailNow, t, format, ap) +#define testingTSkipf(t, ...) \ + testingprivExpand(testingprivTLogfThen(testingTSkipNow, t, __VA_ARGS__)) +#define testingTSkipvf(t, format, ap) \ + testingprivTLogvfThen(testingTSkipNow, t, format, ap) +extern void testingTFail(testingT *t); +#ifdef __cplusplus +#define testingTFailNow(t) (throw testingprivFailNowException()) +#define testingTSkipNow(t) (throw testingprivSkipNowException()) +#else +#define testingTFailNow(t) (testingprivTDoFailNow(t)) +#define testingTSkipNow(t) (testingprivTDoSkipNow(t)) +#endif +// TODO should the defered function also have t passed to it? +extern void testingTDefer(testingT *t, void (*f)(void *data), void *data); + +// TODO IEEE 754 helpers +// references: +// - https://www.sourceware.org/ml/libc-alpha/2009-04/msg00005.html +// - https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html +// - https://stackoverflow.com/questions/5085533/is-a-c-preprocessor-identical-to-a-c-preprocessor + +// TODO should __LINE__ arguments use intmax_t or uintmax_t instead of int? +extern void testingprivRegisterTest(const char *, void (*)(testingT *)); +// see https://stackoverflow.com/questions/32399191/va-args-expansion-using-msvc +#define testingprivExpand(x) x +#define testingprivTLogfThen(then, t, ...) ((testingprivTLogfFull(t, __FILE__, __LINE__, __VA_ARGS__)), (then(t))) +#define testingprivTLogvfThen(then, t, format, ap) ((testingprivTLogvfFull(t, __FILE__, __LINE__, format, ap)), (then(t))) +extern void testingprivTLogfFull(testingT *, const char *, int, const char *, ...); +extern void testingprivTLogvfFull(testingT *, const char *, int, const char *, va_list); +extern void testingprivTDoFailNow(testingT *); +extern void testingprivTDoSkipNow(testingT *); + +#ifdef __cplusplus +} +namespace { + class testingprivFailNowException {}; + class testingprivSkipNowException {}; + class testingprivRegisterTestClass { + public: + testingprivRegisterTestClass(const char *name, void (*f)(testingT *)) { testingprivRegisterTest(name, f); } + }; +} +#endif + +#endif diff --git a/_future/unittest/testing_testing.c b/_future/unittest/testing_testing.c new file mode 100644 index 00000000..eb334648 --- /dev/null +++ b/_future/unittest/testing_testing.c @@ -0,0 +1,144 @@ +// 27 february 2018 +#include +#include +#include +#include "testing.h" + +#define testingprivNew(T) ((T *) malloc(sizeof (T))) + +struct defer { + void (*f)(void *); + void *data; + struct defer *next; +}; + +struct testingT { + const char *name; + void (*f)(testingT *); + int failed; + int skipped; + jmp_buf returnNowBuf; + struct defer *defers; + int defersRun; + testingT *next; +}; + +static testingT *tests = NULL; + +void testingprivRegisterTest(const char *name, void (*f)(testingT *)) +{ + testingT *t; + + t = testingprivNew(testingT); + t->name = name; + t->f = f; + t->failed = 0; + t->skipped = 0; + t->defers = NULL; + t->defersRun = 0; + // TODO add in the order called + t->next = tests; + tests = t; +} + +static void runDefers(testingT *t) +{ + struct defer *d; + + if (t->defersRun) + return; + t->defersRun = 1; + for (d = t->defers; d != NULL; d = d->next) + (*(d->f))(d->data); +} + +int testingMain(void) +{ + testingT *t; + int anyFailed; + const char *status; + + // TODO see if this should run if all tests are skipped + if (tests == NULL) { + fprintf(stderr, "warning: no tests to run\n"); + // imitate Go here (TODO confirm this) + return 0; + } + + anyFailed = 0; + for (t = tests; t != NULL; t = t->next) { + printf("=== RUN %s\n", t->name); + if (setjmp(t->returnNowBuf) == 0) + (*(t->f))(t); + runDefers(t); + status = "PASS"; + if (t->failed) { + status = "FAIL"; + anyFailed = 1; + } else if (t->skipped) + // note that failed overrides skipped + status = "SKIP"; + printf("--- %s: %s (%s)\n", status, t->name, "TODO"); + } + + if (anyFailed) { + printf("FAIL\n"); + return 1; + } + printf("PASS\n"); + return 0; +} + +void testingprivTLogfFull(testingT *t, const char *file, int line, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + testingprivTLogvfFull(t, file, line, format, ap); + va_end(ap); +} + +void testingprivTLogvfFull(testingT *t, const char *file, int line, const char *format, va_list ap) +{ + // TODO extract filename from file + printf("\t%s:%d: ", file, line); + // TODO split into lines separated by \n\t\t and trimming trailing empty lines + vprintf(format, ap); + printf("\n"); +} + +void testingTFail(testingT *t) +{ + t->failed = 1; +} + +static void returnNow(testingT *t) +{ + // run defers before calling longjmp() just to be safe + runDefers(t); + longjmp(t->returnNowBuf, 1); +} + +void testingprivTDoFailNow(testingT *t) +{ + testingTFail(t); + returnNow(t); +} + +void testingprivTDoSkipNow(testingT *t) +{ + t->skipped = 1; + returnNow(t); +} + +void testingTDefer(testingT *t, void (*f)(void *data), void *data) +{ + struct defer *d; + + d = testingprivNew(struct defer); + d->f = f; + d->data = data; + // add to the head of the list so defers are run in reverse order of how they were added + d->next = t->defers; + t->defers = d; +} diff --git a/_future/verticaltext/README b/_future/verticaltext/README new file mode 100644 index 00000000..99310823 --- /dev/null +++ b/_future/verticaltext/README @@ -0,0 +1,19 @@ +Proper vertical text support in uiDrawTextLayout was removed because DirectWrite doesn't add this until Windows 8.1 (unless I drop IDWriteTextLayout and do the script analysis myself; TODO consider this possibility). + +On OS X, setting the vertical forms attribute stacks non-vertical scripts in vertical text (rotates each individual glyph) with Core Text, whereas everything else — including Cocoa's text system — rotates entire non-vertical strings. Not sure what to do about this except manually detect which characters to apply the attribute to: +http://www.unicode.org/notes/tn22/RobustVerticalLayout.pdf +http://www.unicode.org/Public/vertical/revision-17/VerticalOrientation-17.txt + +In addition, with Core Text, the vertical forms attribute vertically centers the vertical glyphs on the bhorizontal baseline, rather than flush with the text. Using the baseline class attribute doesn't seem to work. +TODO investigate kCJKVerticalRomanPlacementType + +If readded, this will need to be a layout-wide setting, not a per-character setting. Pango works right this way; the current Pango code doesn't seem to work. + +More links: +https://www.w3.org/TR/2012/NOTE-jlreq-20120403/#line-composition +https://www.w3.org/TR/REC-CSS2/notes.html + +TODO indicate where in the attributes.c file that block of code should go (or drop it entirely for the reasons listed above) +TODO same for ui.h + +TODO vertical carets diff --git a/_future/verticaltext/attrstr_darwin.m b/_future/verticaltext/attrstr_darwin.m new file mode 100644 index 00000000..f56adc7f --- /dev/null +++ b/_future/verticaltext/attrstr_darwin.m @@ -0,0 +1,19 @@ + case uiAttributeVerticalForms: + if (spec->Value != 0) { + CFAttributedStringSetAttribute(p->mas, range, kCTVerticalFormsAttributeName, kCFBooleanTrue); +// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassRoman); +// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassIdeographicCentered); +// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassIdeographicLow); +// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassIdeographicHigh); +// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassHanging); +// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassMath); + } else { + CFAttributedStringSetAttribute(p->mas, range, kCTVerticalFormsAttributeName, kCFBooleanFalse); +// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassRoman); +// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassIdeographicCentered); +// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassIdeographicLow); +// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassIdeographicHigh); +// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassHanging); +// CFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassMath); + } + break; diff --git a/_future/verticaltext/attrstr_unix.c b/_future/verticaltext/attrstr_unix.c new file mode 100644 index 00000000..e5e13617 --- /dev/null +++ b/_future/verticaltext/attrstr_unix.c @@ -0,0 +1,9 @@ + PangoGravity gravity; + + case uiAttributeVerticalForms: + gravity = PANGO_GRAVITY_SOUTH; + if (spec->Value != 0) + gravity = PANGO_GRAVITY_EAST; + addattr(p, start, end, + pango_attr_gravity_new(gravity)); + break; diff --git a/_future/verticaltext/attrstr_windows.cpp b/_future/verticaltext/attrstr_windows.cpp new file mode 100644 index 00000000..88369217 --- /dev/null +++ b/_future/verticaltext/attrstr_windows.cpp @@ -0,0 +1,15 @@ + uint32_t vertval; + + case uiAttributeVerticalForms: + // LONGTERM 8 and/or 8.1 add other methods for vertical text + op.p = p; + op.start = start; + op.end = end; + vertval = 0; + if (spec->Value != 0) + vertval = 1; + doOpenType("vert", vertval, &op); + doOpenType("vrt2", vertval, &op); + doOpenType("vkrn", vertval, &op); + doOpenType("vrtr", vertval, &op); + break; diff --git a/_future/verticaltext/common_attrlist.c b/_future/verticaltext/common_attrlist.c new file mode 100644 index 00000000..0b9ea921 --- /dev/null +++ b/_future/verticaltext/common_attrlist.c @@ -0,0 +1,2 @@ + case uiAttributeVerticalForms: + return boolsEqual(attr, spec); diff --git a/_future/verticaltext/drawtext_example.c b/_future/verticaltext/drawtext_example.c new file mode 100644 index 00000000..669bdaa8 --- /dev/null +++ b/_future/verticaltext/drawtext_example.c @@ -0,0 +1,18 @@ + next = "vertical glyph forms"; + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeVerticalForms; + spec.Value = 1; + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, " (which you can draw rotated for proper vertical text; for instance, "); + next = "\xE3\x81\x82\xE3\x81\x84\xE3\x81\x86\xE3\x81\x88\xE3\x81\x8A"; + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeVerticalForms; + spec.Value = 1; + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); diff --git a/_future/verticaltext/ui.h b/_future/verticaltext/ui.h new file mode 100644 index 00000000..98903ee9 --- /dev/null +++ b/_future/verticaltext/ui.h @@ -0,0 +1,2 @@ + // TODO rename to uiAttributeVertical? + uiAttributeVerticalForms, // 0 = off, 1 = on diff --git a/_notes/OS2 b/_notes/OS2 new file mode 100644 index 00000000..e5aa04e4 --- /dev/null +++ b/_notes/OS2 @@ -0,0 +1,25 @@ +https://twitter.com/OS2World/status/983822011389620224 + Hi. I recommend you today to start with OS/2 Warp 4.52 or ArcaOS ( The tools are almost the same of Warp 3). EDM/2 is a good place to start: http://www.edm2.com +https://twitter.com/OS2World/status/983822594465034240 + There is also an RPM with OS/2 Software (https://www.arcanoae.com/resources/downloadables/arca-noae-package-manager/ …), and you can get from it some parts of the OS/2 Toolkit. If you want to develop drivers you require the OS/2 Device Driver Kit. (that is more complex). You can also develop in Qt4 and compile things with gcc. +http://www.edm2.com/index.php/Main_Page +https://www.arcanoae.com/resources/downloadables/arca-noae-package-manager/ +http://www.edm2.com/index.php/IBM_OS/2_Toolkit_Documentation +https://www.os2world.com/forum/index.php?topic=953.0 +https://www.google.com/search?client=firefox-b-1&ei=QIPPWurPLs7OwAK65K24BA&q=%22OS%2F2+Toolkit%22+site%3Aamazon.com&oq=%22OS%2F2+Toolkit%22+site%3AAmazon.com&gs_l=psy-ab.3...160814.161293.0.161540.2.2.0.0.0.0.128.252.0j2.2.0....0...1c.1.64.psy-ab..0.0.0....0.itT9Og6hC5c +http://www.edm2.com/index.php/List_of_Presentation_Manager_Articles +https://www.ecsoft2.org/ +http://www.edm2.com/index.php/Cairo + http://www.edm2.com/index.php/Doodle +http://www.edm2.com/index.php/Workplace_Shell_Toolkit +http://wpstk.netlabs.org/en/site/index.xml +https://en.wikipedia.org/wiki/Workplace_Shell +https://www.google.com/search?q=OS2+alphablending&ie=utf-8&oe=utf-8&client=firefox-b-1 +http://www.edm2.com/index.php/List_of_Multimedia_Articles +alphablending: + http://www.osnews.com/story/369/Review-eComStation-OS2-1.0/page3/ + http://halfos.ru/documentation/33-os2-api-documentation/67-opengl-os2-developer-reference-guide.html + http://www.altools.com/ALTools/ALSee/ALSee-Image-Viewer.aspx + http://www.os2voice.org/vnewsarc/bn2007122.html + http://www.mozillazine.org/talkback.html?article=194 + https://books.google.com/books?id=9cpU5uYCzq4C&pg=PA202&lpg=PA202&dq=%22OS/2%22+alphablending&source=bl&ots=uatEop2jAL&sig=HAa_ofQSKsk6-8tBR6YZ6MRJG_0&hl=en&sa=X&ved=0ahUKEwiDq5HukLbaAhUk8IMKHR7aCw4Q6AEIWTAI#v=onepage&q=%22OS%2F2%22%20alphablending&f=false diff --git a/_notes/caretWidths b/_notes/caretWidths new file mode 100644 index 00000000..e9eef62b --- /dev/null +++ b/_notes/caretWidths @@ -0,0 +1 @@ +UWP has this (TODO check its implementation to see if it matches ours) https://docs.microsoft.com/en-us/uwp/api/windows.ui.viewmanagement.uisettings#Windows_UI_ViewManagement_UISettings_CaretWidth diff --git a/_notes/cplusplus b/_notes/cplusplus new file mode 100644 index 00000000..0481a948 --- /dev/null +++ b/_notes/cplusplus @@ -0,0 +1 @@ +https://blogs.msdn.microsoft.com/oldnewthing/20181226-00/?p=100565 diff --git a/_notes/darwinAutoLayout b/_notes/darwinAutoLayout new file mode 100644 index 00000000..5a521fc0 --- /dev/null +++ b/_notes/darwinAutoLayout @@ -0,0 +1,6 @@ +https://developer.apple.com/library/mac/documentation/AppKit/Reference/NSLayoutConstraint_Class/ + https://developer.apple.com/documentation/uikit/nslayoutconstraint?language=objc +https://developer.apple.com/library/mac/documentation/UserExperience/Conceptual/AutolayoutPG/ProgrammaticallyCreatingConstraints.html#//apple_ref/doc/uid/TP40010853-CH16-SW1 + https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/ProgrammaticallyCreatingConstraints.html#//apple_ref/doc/uid/TP40010853-CH16-SW1 (Listing 13-3) +https://developer.apple.com/library/mac/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithScrollViews.html#//apple_ref/doc/uid/TP40010853-CH24-SW1 + https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithScrollViews.html#//apple_ref/doc/uid/TP40010853-CH24-SW1 ("IMPORTANT Your layout must fully define..." large box) diff --git a/_notes/darwinNSAlertIcons b/_notes/darwinNSAlertIcons new file mode 100644 index 00000000..5e8acfed --- /dev/null +++ b/_notes/darwinNSAlertIcons @@ -0,0 +1,8 @@ +https://www.google.com/search?q=nsalert+error+icon&client=firefox-b&tbm=isch&source=iu&ictx=1&fir=2iRctS5fJByN0M%253A%252Cw324MTzjHa1bAM%252C_&usg=__x3wpwdNN1L8VI2kHtkKAXFMtpj4%3D&sa=X&ved=0ahUKEwjJzpjN2qDZAhVjw1kKHfOHDoQQ9QEIMTAB#imgrc=2iRctS5fJByN0M: +http://0xced.blogspot.com/2009/11/clalert-nsalert-done-right.html +https://gist.github.com/0xced/228140 +http://editra.org/uploads/code/artmac.html +http://mirror.informatimago.com/next/developer.apple.com/documentation/Carbon/Reference/IconServices/index.html +http://www.cocoabuilder.com/archive/cocoa/15427-iconref-to-nsimage.html +https://github.com/lukakerr/Swift-NSUserNotificationPrivate +https://stackoverflow.com/questions/32943220/the-sidebar-icon-image-name-in-osx diff --git a/_notes/dialogs b/_notes/dialogs new file mode 100644 index 00000000..a6ce3397 --- /dev/null +++ b/_notes/dialogs @@ -0,0 +1,2 @@ +https://github.com/kusti8/proton-native/issues/47#issuecomment-373068947 +https://blogs.kde.org/2009/03/26/how-crash-almost-every-qtkde-application-and-how-fix-it-0 diff --git a/_notes/highDPI b/_notes/highDPI new file mode 100644 index 00000000..99e9af82 --- /dev/null +++ b/_notes/highDPI @@ -0,0 +1 @@ +High DPI Displays | Qt 5.5 http://doc.qt.io/qt-5/highdpi.html bottom of page(?) diff --git a/_notes/i18n b/_notes/i18n new file mode 100644 index 00000000..fb3b6b8f --- /dev/null +++ b/_notes/i18n @@ -0,0 +1,16 @@ +https://msdn.microsoft.com/en-us/library/windows/desktop/dd319079(v=vs.85).aspx +https://msdn.microsoft.com/en-us/library/windows/desktop/dd318103(v=vs.85).aspx +https://stackoverflow.com/questions/4663855/is-there-a-repository-for-localized-common-text-in-winforms +https://stackoverflow.com/questions/2502375/find-localized-windows-strings +https://docs.microsoft.com/en-us/windows-hardware/customize/mobile/mcsf/create-a-resource-only-dll-for-localized-strings +https://msdn.microsoft.com/en-us/library/windows/desktop/ee845043(v=vs.85).aspx +https://msdn.microsoft.com/en-us/library/cc194807.aspx +https://www.codeproject.com/Articles/10542/Easily-Load-and-Format-Strings-from-the-String-Tab +https://www.codeproject.com/Tips/431045/The-inner-working-of-FindResource-and-LoadString-W +https://mihai-nita.net/2007/05/03/how-to-localize-an-rc-file/ +https://www.microsoft.com/en-us/language +https://www.microsoft.com/en-us/language/Terminology +https://www.microsoft.com/en-us/language/LicenseAgreement +https://www.microsoft.com/en-us/language/Translations +http://www.ttt.org/oscarstandards/tbx/ +https://blogs.msdn.microsoft.com/oldnewthing/20181122-00/?p=100295 diff --git a/_notes/misc b/_notes/misc new file mode 100644 index 00000000..7eaf7c5c --- /dev/null +++ b/_notes/misc @@ -0,0 +1,206 @@ +windows data types, "open specifications" on msdn https://msdn.microsoft.com/en-us/library/cc230321.aspx + (I usually use https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx for windows data types) + +windows platform update for windows 7 +https://msdn.microsoft.com/en-us/library/windows/desktop/jj863687(v=vs.85).aspx +https://msdn.microsoft.com/en-us/library/windows/desktop/hh802478(v=vs.85).aspx +https://msdn.microsoft.com/en-us/library/windows/desktop/hh802480(v=vs.85).aspx + +DWM, header bars, toolbars +https://stackoverflow.com/questions/41106347/why-is-my-dwmextendframeintoclientaread-window-not-drawing-the-dwm-borders/41125616#41125616 +https://developer.gnome.org/hig/stable/header-bars.html.en +https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/OSXHIGuidelines/WindowTitleBarToolbar.html#//apple_ref/doc/uid/20000957-CH39-SW1 +https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/OSXHIGuidelines/ControlsAll.html#//apple_ref/doc/uid/20000957-CH46-SW2 +https://msdn.microsoft.com/en-us/library/windows/desktop/bb787329(v=vs.85).aspx +https://msdn.microsoft.com/en-us/library/windows/desktop/bb787334(v=vs.85).aspx +https://msdn.microsoft.com/en-us/library/windows/desktop/bb787337(v=vs.85).aspx +https://msdn.microsoft.com/en-us/library/windows/desktop/cc835034(v=vs.85).aspx +https://msdn.microsoft.com/en-us/library/windows/desktop/bb787345(v=vs.85).aspx + +rendering +https://www.youtube.com/watch?v=UUfXWzp0-DU + +text input on windows +https://blogs.msdn.microsoft.com/oldnewthing/20121025-00/?p=6253 +https://www.google.com/search?q=translatemessage+site%3Ahttp%3A%2F%2Farchives.miloush.net%2Fmichkap%2Farchive%2F&ie=utf-8&oe=utf-8 +http://archives.miloush.net/michkap/archive/2008/04/22/8415843.html +http://archives.miloush.net/michkap/archive/2007/03/25/1948887.html +http://archives.miloush.net/michkap/archive/2006/09/10/748775.html +http://archives.miloush.net/michkap/archive/2004/11/27/270931.html +https://stackoverflow.com/questions/41334851/since-translatemessage-returns-nonzero-unconditionally-how-can-i-tell-either +http://stackoverflow.com/questions/41334851/since-translatemessage-returns-nonzero-unconditionally-how-can-i-tell-either?noredirect=1#comment70107257_41334851 + +text layouts +https://developer.apple.com/reference/coretext/2110184-ctframe?language=objc +https://msdn.microsoft.com/en-us/library/windows/desktop/dd368203(v=vs.85).aspx +https://msdn.microsoft.com/en-us/library/windows/desktop/dd368205(v=vs.85).aspx +https://developer.gnome.org/pango/1.30/pango-Layout-Objects.html#pango-layout-set-font-description +https://developer.gnome.org/pango/1.30/pango-Fonts.html#PangoWeight-enum +https://git.gnome.org/browse/pango/tree/pango/pangocoretext-fontmap.c (code for small caps) +https://bugzilla.gnome.org/show_bug.cgi?id=766148 +https://developer.apple.com/documentation/coretext/ctline?preferredLanguage=occ +https://developer.apple.com/reference/coretext/1508876-ctlinegetstringindexforposition?language=objc +https://developer.apple.com/library/content/documentation/StringsTextFonts/Conceptual/CoreText_Programming/Introduction/Introduction.html#//apple_ref/doc/uid/TP40005533-CH1-SW1 +https://developer.apple.com/reference/coretext/2110184-ctframe?language=objc +https://developer.apple.com/reference/coretext/2168885-ctline?language=objc +https://developer.apple.com/library/content/documentation/StringsTextFonts/Conceptual/CoreText_Programming/LayoutOperations/LayoutOperations.html +http://asciiwwdc.com/2013/sessions/205 +https://developer.apple.com/reference/coretext/1511332-ctlinegetboundswithoptions?language=objc +https://stackoverflow.com/questions/19709751/ctlinegetboundswithoptions-what-does-the-returned-frame-origin-y-value-mean?rq=1 +https://imgur.com/a/lqhzC +https://imgur.com/a/hYeOL +https://www.google.com/search?q=ctline+%22paragraph+spacing%22&ie=utf-8&oe=utf-8 +http://stackoverflow.com/questions/8010615/how-do-i-determine-the-of-a-ctline-following-a-ctline +https://imgur.com/a/dlpm2 +https://stackoverflow.com/questions/41601845/am-i-missing-something-in-core-text-that-will-let-me-see-which-line-a-certain-po +https://www.google.com/search?q=%22core+text%22+%22combining+character%22&oq=%22core+text%22+%22combining+character%22&gs_l=serp.3...2526898.2528158.0.2528485.21.10.0.0.0.0.216.997.5j3j1.9.0....0...1c.1.64.serp..12.8.904...33i21k1j33i160k1.J69jsB9g0N0 +https://github.com/macvim-dev/macvim/issues/400#issuecomment-274292877 +https://bugs.webkit.org/show_bug.cgi?id=68287 +https://trac.webkit.org/changeset/95391 +https://trac.webkit.org/changeset/95391/trunk/Source/WebCore/platform/graphics/mac/ComplexTextControllerCoreText.cpp +http://stackoverflow.com/questions/41857213/is-there-any-way-i-can-get-precise-metrics-line-ascent-line-descent-etc-of +http://pawlan.com/monica/articles/texttutorial/int.html + +enter in entry fields, possibly other text layout issues, possibly keyboard shortcuts +https://developer.gnome.org/gtk3/3.10/GtkEntry.html "activate" signal +https://gitlab.gnome.org/GNOME/gtk/blob/gtk-3-10/gtk/gtkentry.c (not sure where) +https://developer.gnome.org/gtk3/3.10/GtkTextView.html signals section of contents +https://gitlab.gnome.org/GNOME/gtk/blob/gtk-3-10/gtk/gtktextview.c (not sure where; closed before I could find out again though gitlab might wreck it anyway) +https://developer.gnome.org/gtk3/3.10/gtk3-Bindings.html +https://gitlab.gnome.org/GNOME/gtk/blob/gtk-3-10/gtk/gtkwidget.c (not sure where) +https://gitlab.gnome.org/GNOME/gtk/blob/gtk-3-10/gtk/gtkbindings.c (not sure where) +https://gitlab.gnome.org/GNOME/gnome-builder/tree/master/libide/keybindings/ide-keybindings.c 404; TODO find the original cgit link in the chat logs to see what hergertme wanted me to see +https://gitlab.gnome.org/GNOME/gnome-builder/tree/master/libide/sourceview/ide-source-view.c likewise +https://gitlab.gnome.org/GNOME/gnome-builder/tree/master/libide/sourceview/ide-source-view-mode.c#n323 likewise + +rgb int->float conversion +https://stackoverflow.com/questions/41348339/how-to-convert-rgb-to-hexadecimal-using-gtk?noredirect=1#comment69903262_41348339 + +windows fonts, hi-dpi +https://stackoverflow.com/questions/41448320/dlgtemplateex-and-ds-shellfont-what-about-point-size + +windows default fonts +https://stackoverflow.com/questions/41505151/how-to-draw-text-with-the-default-ui-font-in-directwrite#41505750 + +uwp stuff +https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/inking-controls +https://msdn.microsoft.com/en-us/windows/uwp/controls-and-patterns/master-details + +cmake stuff +https://public.kitware.com/pipermail/cmake/2010-January/034669.html +https://cmake.org/cmake/help/v3.0/command/string.html +https://cmake.org/pipermail/cmake/2003-April/003599.html + +opentype features and locales +https://www.microsoft.com/typography/otspec/featurelist.htm +https://en.wikipedia.org/wiki/List_of_typographic_features +https://www.microsoft.com/typography/otspec160/scripttags.htm +https://msdn.microsoft.com/en-us/library/windows/desktop/dd371503(v=vs.85).aspx + +gspell (TODO figure out what I wanted from this; possibly spelling checking) +https://git.gnome.org/browse/gspell/tree/gspell/gspell-inline-checker-text-buffer.c +https://git.gnome.org/browse/gtk+/tree/gtk/gtktextview.c?h=gtk-3-10 + +other assorted notes on text +https://mail.gnome.org/archives/gtk-devel-list/2016-March/msg00037.html +https://mail.gnome.org/archives/gtk-devel-list/2016-June/msg00007.html +https://blogs.msdn.microsoft.com/tsfaware/ +https://stackoverflow.com/questions/40453186/what-is-the-correct-modern-way-to-handle-arbitrary-text-input-in-a-custom-contr +https://www.microsoft.com/typography/fonts/family.aspx + +windows ribbon +https://twitter.com/_Ninji/status/814918189708668929 (C08rw9TWgAMpLkC.jpg:large) + +https://developer.apple.com/library/content/samplecode/CocoaTipsAndTricks/Introduction/Intro.html#//apple_ref/doc/uid/DTS40010039 BlurryView and StaticObjcMethods in particular +printing https://developer.apple.com/library/content/samplecode/PMPrinterPrintWithFile/Introduction/Intro.html#//apple_ref/doc/uid/DTS10003958 +auto layout https://developer.apple.com/library/content/releasenotes/UserExperience/RNAutomaticLayout/index.html#//apple_ref/doc/uid/TP40010631 + +other stuff, mostly custom draw on windows +https://github.com/pauldotknopf/WindowsSDK7-Samples/blob/master/multimedia/DirectWrite/ChooseFont/ChooseFont.cpp at ChooseFontDialog::OnFontSizeNameEdit() definition +https://developer.gnome.org/gtk3/3.10/GtkColorButton.html +https://msdn.microsoft.com/en-us/library/windows/desktop/bb775951(v=vs.85).aspx + https://msdn.microsoft.com/en-us/library/windows/desktop/bb775951(v=vs.85).aspx between BS_LEFTTEXT and BS_RADIOBUTTON +https://msdn.microsoft.com/en-us/library/windows/desktop/bb775923(v=vs.85).aspx + https://msdn.microsoft.com/en-us/library/windows/desktop/bb775923(v=vs.85).aspx +https://msdn.microsoft.com/en-us/library/windows/desktop/bb775802(v=vs.85).aspx + https://msdn.microsoft.com/en-us/library/windows/desktop/bb775802(v=vs.85).aspx +https://msdn.microsoft.com/en-us/library/windows/desktop/bb761837(v=vs.85).aspx + https://msdn.microsoft.com/en-us/library/windows/desktop/bb761837(v=vs.85).aspx +https://msdn.microsoft.com/en-us/library/windows/desktop/bb761847(v=vs.85).aspx + https://msdn.microsoft.com/en-us/library/windows/desktop/bb761847(v=vs.85).aspx description + parameters +https://msdn.microsoft.com/en-us/library/windows/desktop/bb775483(v=vs.85).aspx + https://msdn.microsoft.com/en-us/library/windows/desktop/bb775483(v=vs.85).aspx dwItemSpec parameter +https://msdn.microsoft.com/en-us/library/windows/desktop/bb775951(v=vs.85).aspx#BS_NOTIFY + https://msdn.microsoft.com/en-us/library/windows/desktop/bb775951(v=vs.85).aspx#BS_NOTIFY BS_NOTIFY to BS_RIGHT +http://stackoverflow.com/questions/17678261/how-to-change-color-of-a-button-while-keeping-buttons-functions-in-win32 +https://msdn.microsoft.com/en-us/library/windows/desktop/ff919569(v=vs.85).aspx + https://msdn.microsoft.com/en-us/library/windows/desktop/ff919569(v=vs.85).aspx +https://msdn.microsoft.com/en-us/library/windows/desktop/ff919573(v=vs.85).aspx + https://msdn.microsoft.com/en-us/library/windows/desktop/ff919573(v=vs.85).aspx switch (pnm->hdr.code){ +http://stackoverflow.com/questions/36756094/will-the-device-context-of-a-child-window-have-the-same-text-metrics-and-text-ex +actually I think most if not all of these were from when I was trying to implement uiColorButton on Windows, so I'm not sure if I still need these, but meh... + +more miscellaneous +https://blogs.msdn.microsoft.com/oldnewthing/20160328-00/?p=93204 from "One is virtual memory" to "occured to them that their" +https://blogs.msdn.microsoft.com/oldnewthing/20160331-00/?p=93231 + https://msdn.microsoft.com/en-us/library/windows/desktop/aa373631(v=vs.85).aspx +https://msdn.microsoft.com/en-us/library/windows/desktop/ms648395(v=vs.85).aspx + https://msdn.microsoft.com/en-us/library/windows/desktop/ms648395(v=vs.85).aspx +https://blogs.msdn.microsoft.com/oldnewthing/20101118-00/?p=12253#comment-875273 "MTA implies poor service and fare hikes." + +from #gtk+ +[18:06:48] if i am doing an animation that needs to be updated at every duration(say every 50ms), should i use get_frame_time() from the frameclock to do that? (from add_tick_callback()) +[18:12:47] <~Company> zorcon: yes + +windows stuff: mostly aero tricks, sdk version macro notes, ribbon stuff, scrollbar stuff too +https://tools.stefankueng.com/SendMessage.html +https://sourceforge.net/p/sendmessage/code/HEAD/tree/trunk/src/SendMessage.vcxproj +https://sourceforge.net/p/sendmessage/code/HEAD/tree/trunk/src/stdafx.h +https://sourceforge.net/p/sendmessage/code/HEAD/tree/trunk/src/MainDlg.h +https://sourceforge.net/u/steveking/profile/ +https://github.com/stefankueng/BowPad/tree/master/src + sourceforge original +https://github.com/stefankueng/sktoolslib/blob/master/AeroControls.cpp + sourceforge original +https://tools.stefankueng.com/BowPad.html +https://www.codeproject.com/Articles/1084/Custom-Scrollbar-Library-version-1-1#_articleTop +https://www.google.com/search?client=firefox-b-1&biw=1080&bih=600&ei=gUHRWsrdMYayggf30bfoAw&q=%22aerocontrols.h%22&oq=%22aerocontrols.h%22&gs_l=psy-ab.3..35i39k1l6.1816.3367.0.3413.4.2.0.0.0.0.0.0..1.0....0...1c.1.64.psy-ab..3.1.211.6...211.0Ppw_OREAk0 + https://searchcode.com/codesearch/view/7295148/ + https://github.com/TortoiseGit/tortoisesvn/blob/master/src/Utils/MiscUI/AeroBaseDlg.h + https://github.com/TortoiseGit/tortoisesvn/blob/master/src/Utils/MiscUI/AeroControls.cpp + https://github.com/gavioto/stexbar/blob/master/Misc/FileTool/src/CleanVerifyDlg.h + +windows ribbon colors and dwm customization +https://github.com/stefankueng/BowPad/blob/master/src/MainWindow.cpp +https://msdn.microsoft.com/en-us/library/windows/desktop/dd940487(v=vs.85).aspx +https://github.com/yohei-yoshihara/GameOfLife3D/blob/master/GameOfLife3DLib/gameoflife3d/Ribbon.cpp +https://msdn.microsoft.com/en-us/library/windows/desktop/dd940404(v=vs.85).aspx +https://github.com/stefankueng/sktoolslib/blob/master/AeroColors.cpp +https://gist.github.com/emoacht/bfa852ccc16bdb5465bd +https://stackoverflow.com/questions/4258295/aero-how-to-draw-solid-opaque-colors-on-glass +https://github.com/ComponentFactory/Krypton +https://www.codeproject.com/Articles/620045/Custom-Controls-in-Win-API-Visual-Styles +https://blogs.msdn.microsoft.com/wpf/2010/11/30/systemcolors-reference/ +https://stackoverflow.com/questions/25639621/check-when-a-user-changes-windows-glass-brush-theme-color +https://msdn.microsoft.com/en-us/library/windows/desktop/aa969513(v=vs.85).aspx +https://msdn.microsoft.com/en-us/library/windows/desktop/aa969527(v=vs.85).aspx +https://msdn.microsoft.com/en-us/library/windows/desktop/dd388198(v=vs.85).aspx +I hope the MFC ribbon can have customizable colors too, otherwise I'll have to do some serious image processing... + +windows debugging +https://msdn.microsoft.com/en-us/library/windows/desktop/hh780351(v=vs.85).aspx + https://msdn.microsoft.com/en-us/library/windows/desktop/ff476881(v=vs.85).aspx#Debug + https://msdn.microsoft.com/en-us/library/windows/desktop/hh780343(v=vs.85).aspx + https://msdn.microsoft.com/en-us/library/windows/desktop/dn457937(v=vs.85).aspx + +more OS2 stuff +https://www.google.com/search?q=%22ibm+graphics+development+toolkit%22&ie=utf-8&oe=utf-8&client=firefox-b-1 +https://www.google.com/search?q=%22os%2F2+graphics+development+toolkit%22&ie=utf-8&oe=utf-8&client=firefox-b-1 +http://www.edm2.com/index.php/IBM_OS/2_Developer%27s_Packages + +[17:48:52] andlabs: I got splitView and NSWindow size restoration working btw +[17:49:09] andlabs: see https://github.com/eintw1ck/mail/blob/master/Sources/mail/MainViewController.swift for splitView + +old stuff +font1.gif (GIF Image, 424 × 475 pixels) http://www.functionx.com/win32/controls/dlgboxes/font1.gif +Inskcape’s Hidden Little Feature: Mesh Gradients | OCS-Mag http://www.ocsmag.com/2016/02/27/inskcapes-hidden-little-feature-mesh-gradients/ (near "When you’re done colouring in all the nodes, you may want to drag") + +https://msdn.microsoft.com/en-us/library/windows/desktop/ms632615(v=vs.85).aspx we need to handle this alongisde WM_CAPTURECHANGED diff --git a/_notes/rebarstuff b/_notes/rebarstuff new file mode 100644 index 00000000..e2675b17 --- /dev/null +++ b/_notes/rebarstuff @@ -0,0 +1,9 @@ +https://docs.microsoft.com/en-us/windows/desktop/uxguide/cmd-toolbars +https://docs.microsoft.com/en-us/windows/desktop/controls/cc-faq-iemenubar +https://docs.microsoft.com/en-us/windows/desktop/controls/cc-faq-ietoolbar +https://docs.microsoft.com/en-us/windows/desktop/controls/create-rebar-controls +https://docs.microsoft.com/en-us/windows/desktop/controls/create-toolbars +https://docs.microsoft.com/en-us/windows/desktop/controls/handle-drop-down-buttons +https://docs.microsoft.com/en-us/windows/desktop/controls/tb-buttonstructsize + https://www.google.com/search?q=winapi+toolbar+dropdown+position&ie=utf-8&oe=utf-8&client=firefox-b-1 +https://www.google.com/search?q=winapi+toolbar+dropdown+arrow+position&ie=utf-8&oe=utf-8&client=firefox-b-1 diff --git a/_notes/tableNotes b/_notes/tableNotes new file mode 100644 index 00000000..b894d5a3 --- /dev/null +++ b/_notes/tableNotes @@ -0,0 +1,4 @@ +https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Protocols/NSOutlineViewDataSource_Protocol/index.html#//apple_ref/occ/intfm/NSOutlineViewDataSource/outlineView:child:ofItem: +https://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSOutlineViewDelegate_Protocol/index.html#//apple_ref/occ/intfm/NSOutlineViewDelegate/outlineView:didAddRowView:forRow: + https://developer.apple.com/documentation/appkit/nsoutlineviewdelegate/1528320-outlineview?language=objc +https://github.com/mity/mctrl/blob/master/mctrl/treelist.c around treelist_set_subitem() diff --git a/_notes/textSelections b/_notes/textSelections new file mode 100644 index 00000000..a1bccba6 --- /dev/null +++ b/_notes/textSelections @@ -0,0 +1,3 @@ +http://www.catch22.net/tuts/transparent-text +https://msdn.microsoft.com/en-us/library/windows/desktop/ms724371(v=vs.85).aspx + TODO which color did I want from this list diff --git a/_notes/winARM64 b/_notes/winARM64 new file mode 100644 index 00000000..c22d5593 --- /dev/null +++ b/_notes/winARM64 @@ -0,0 +1,3 @@ +http://www.os2museum.com/wp/windows-10-arm64-on-qemu/ +https://docs.microsoft.com/en-us/windows/uwp/porting/apps-on-arm +http://pete.akeo.ie/2017/05/compiling-desktop-arm-applications-with.html diff --git a/_notes/windowsHighDPI b/_notes/windowsHighDPI new file mode 100644 index 00000000..0f69a507 --- /dev/null +++ b/_notes/windowsHighDPI @@ -0,0 +1,18 @@ +https://msdn.microsoft.com/en-us/library/windows/desktop/dn469266(v=vs.85).aspx +https://msdn.microsoft.com/en-us/library/windows/desktop/mt744321(v=vs.85).aspx +!!!! http://stackoverflow.com/questions/41917279/do-child-windows-have-the-same-dpi-as-their-parents-in-a-per-monitor-aware-appli +https://msdn.microsoft.com/en-us/library/windows/desktop/mt843498(v=vs.85).aspx +https://msdn.microsoft.com/library/windows/desktop/mt791579(v=vs.85).aspx +https://blogs.windows.com/buildingapps/2017/04/04/high-dpi-scaling-improvements-desktop-applications-windows-10-creators-update/ +https://channel9.msdn.com/Events/Windows/Windows-Developer-Day-Creators-Update/High-DPI-Improvements-for-Desktop-Developers +https://social.msdn.microsoft.com/Forums/vstudio/en-US/31d2a89f-3518-403e-b1e4-bbe37ac1b0f0/per-monitor-high-dpi-awareness-wmdpichanged?forum=vcgeneral +https://stackoverflow.com/questions/36864894/scaling-the-non-client-area-title-bar-menu-bar-for-per-monitor-high-dpi-suppo/37624363 +https://stackoverflow.com/questions/41448320/dlgtemplateex-and-ds-shellfont-what-about-point-size +http://stackoverflow.com/questions/41917279/do-child-windows-have-the-same-dpi-as-their-parents-in-a-per-monitor-aware-appli + +https://msdn.microsoft.com/en-us/library/windows/desktop/dn469266(v=vs.85).aspx + https://msdn.microsoft.com/library/windows/desktop/mt843498(v=vs.85).aspx(d=robot) +https://msdn.microsoft.com/en-us/library/windows/desktop/dn469266(v=vs.85).aspx#appendix_c_common_high_dpi_issues + https://msdn.microsoft.com/library/windows/desktop/mt843498(v=vs.85).aspx(d=robot)#appendix_c_common_high_dpi_issues +https://msdn.microsoft.com/en-us/library/windows/desktop/dn469266(v=vs.85).aspx#addressing_high_dpi_issues + https://msdn.microsoft.com/library/windows/desktop/mt843498(v=vs.85).aspx(d=robot)#addressing_high_dpi_issues diff --git a/_notes/windowsPrinting b/_notes/windowsPrinting new file mode 100644 index 00000000..c5d0b6c7 --- /dev/null +++ b/_notes/windowsPrinting @@ -0,0 +1,7 @@ +https://msdn.microsoft.com/en-us/library/windows/desktop/ff686805(v=vs.85).aspx +https://msdn.microsoft.com/en-us/library/windows/desktop/dn495653(v=vs.85).aspx +https://msdn.microsoft.com/en-us/library/windows/desktop/hh448422(v=vs.85).aspx +https://msdn.microsoft.com/en-us/library/windows/desktop/dd316975(v=vs.85).aspx +https://msdn.microsoft.com/en-us/library/windows/desktop/dd372919(v=vs.85).aspx +https://msdn.microsoft.com/en-us/library/windows/desktop/ee264335(v=vs.85).aspx +https://msdn.microsoft.com/en-us/library/windows/desktop/dd145198(v=vs.85).aspx diff --git a/_notes/windowsUWPGlass b/_notes/windowsUWPGlass new file mode 100644 index 00000000..8293e295 --- /dev/null +++ b/_notes/windowsUWPGlass @@ -0,0 +1,46 @@ +https://twitter.com/omgubuntu/status/962765197109841922 +https://github.com/DominicMaas/SoundByte/blob/master/SoundByte.UWP/Resources/Brushes.xaml +https://docs.microsoft.com/en-us/windows/uwp/design/style/acrylic +https://www.google.com/search?q=uwp+acrylic+winapi&ie=utf-8&oe=utf-8&client=firefox-b-1 +https://stackoverflow.com/questions/43931709/acrylic-material-in-win32-app +https://stackoverflow.com/questions/44000217/mimicking-acrylic-in-a-win32-app +https://stackoverflow.com/questions/43699256/how-to-use-acrylic-accent-in-windows-10-creators-update +https://stackoverflow.com/questions/39135834/how-to-call-uwp-api-from-converted-win32-app-desktop-app-converter +https://stackoverflow.com/questions/32724187/how-do-you-set-the-glass-blend-colour-on-windows-10 +https://channel9.msdn.com/Events/Build/2017/B8034 +https://www.reddit.com/r/Windows10/comments/7lzyzc/since_the_taskbar_is_still_win32_and_it_can_have/ +https://thenextweb.com/microsoft/2017/05/15/microsoft-fluent-design-system-breaking-windows-10s-new-look/ +https://github.com/bbougot/AcrylicWPF +http://vhanla.codigobit.info/2015/07/enable-windows-10-aero-glass-aka-blur.html +http://undoc.airesoft.co.uk/user32.dll/SetWindowCompositionAttribute.php +http://undoc.airesoft.co.uk/user32.dll/GetWindowCompositionAttribute.php +https://msdn.microsoft.com/en-us/library/windows/desktop/aa969530(v=vs.85).aspx +https://github.com/bbougot/AcrylicWPF/blob/master/MainWindow.xaml.cs +https://www.google.com/search?q=%22WCA_ACCENT_POLICY%22&client=firefox-b-1&source=lnt&tbs=cdr%3A1%2Ccd_min%3A1%2F1%2F2001%2Ccd_max%3A12%2F31%2F2013&tbm= +https://github.com/sgothel/jogl/blob/master/src/nativewindow/native/win32/WindowsDWM.c +http://www.brandonfa.lk/win8/win8_devrel_head_x86/uxtheme.h +http://www.brandonfa.lk/win8/ +https://github.com/gamozolabs/pdblister +https://github.com/wbenny/pdbex +https://github.com/wbenny/pdbex/releases + +https://stackoverflow.com/questions/44000217/mimicking-acrylic-in-a-win32-app +https://github.com/riverar/sample-win32-acrylicblur +https://github.com/riverar/sample-win10-aeroglass +https://guerra24blog.wordpress.com/2017/12/20/windows-10-use-acrylic-in-win32-apps/ +https://news.ycombinator.com/item?id=14432951 +https://gist.github.com/ethanhs/0e157e4003812e99bf5bc7cb6f73459f ACCENTPOLICY +https://github.com/TranslucentTB/Tools +https://withinrafael.com/2015/07/08/adding-the-aero-glass-blur-to-your-windows-10-apps/ +https://withinrafael.com/2018/02/01/adding-acrylic-blur-to-your-windows-10-apps-redstone-4-desktop-apps/ + +diversion: DwmEnableBlurBehindWindow { +https://docs.microsoft.com/en-us/windows/desktop/api/dwmapi/nf-dwmapi-dwmenableblurbehindwindow +https://docs.microsoft.com/en-us/windows/desktop/api/dwmapi/ns-dwmapi-_dwm_blurbehind +https://docs.microsoft.com/en-us/windows/desktop/api/dwmapi/nf-dwmapi-dwmenableblurbehindwindow +http://www.danielmoth.com/Blog/Vista-Glass-Answers-And-DwmEnableBlurBehindWindow.aspx +http://www.danielmoth.com/Blog/Glass-In-C-An-Alternative-Approach.aspx +http://www.danielmoth.com/Blog/Vista-Glass-In-C.aspx +http://www.danielmoth.com/Blog/Glass-In-C-An-Alternative-Approach.aspx +https://weblogs.asp.net/kennykerr/Windows-Vista-for-Developers-_1320_-Part-3-_1320_-The-Desktop-Window-Manager +} diff --git a/_wip/areatext/osxweights b/_wip/areatext/osxweights deleted file mode 100644 index 34e1cc65..00000000 --- a/_wip/areatext/osxweights +++ /dev/null @@ -1,9 +0,0 @@ -NSFontWeightUltraLight -0.800000 -NSFontWeightThin -0.600000 -NSFontWeightLight -0.400000 -NSFontWeightRegular 0.000000 -NSFontWeightMedium 0.230000 -NSFontWeightSemibold 0.300000 -NSFontWeightBold 0.400000 -NSFontWeightHeavy 0.560000 -NSFontWeightBlack 0.620000 diff --git a/_wip/attrstr_metrics/common_OLD_drawtext.c b/_wip/attrstr_metrics/common_OLD_drawtext.c new file mode 100644 index 00000000..ca66796a --- /dev/null +++ b/_wip/attrstr_metrics/common_OLD_drawtext.c @@ -0,0 +1,92 @@ +// 10 february 2017 +#include "../ui.h" +#include "uipriv.h" + +// TODO this doesn't handle the case where nLines == 0 +// TODO this should never happen even if there are no characters?? +// TODO figure out how to make this work on GTK+ +void uiDrawCaret(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout, size_t pos, int *line) +{ + double xoff; + uiDrawTextLayoutLineMetrics m; + struct caretDrawParams cdp; + uiDrawPath *path; + uiDrawBrush brush; + + if (*line < 0) + *line = 0; + if (*line > (uiDrawTextLayoutNumLines(layout) - 1)) + *line = (uiDrawTextLayoutNumLines(layout) - 1); + // TODO cap pos + xoff = uiDrawTextLayoutByteLocationInLine(layout, pos, *line); + while (xoff < 0) { + size_t start, end; + + uiDrawTextLayoutLineByteRange(layout, *line, &start, &end); + if (end < pos) // too far up + (*line)++; + else + (*line)--; + xoff = uiDrawTextLayoutByteLocationInLine(layout, pos, *line); + } + uiDrawTextLayoutLineGetMetrics(layout, *line, &m); + + caretDrawParams(c, m.Height, &cdp); + + uiDrawSave(c); + + path = uiDrawNewPath(uiDrawFillModeWinding); + uiDrawPathAddRectangle(path, + // TODO add m.X? + x + xoff - cdp.xoff, y + m.Y, + cdp.width, m.Height); + uiDrawPathEnd(path); + brush.Type = uiDrawBrushTypeSolid; + brush.R = cdp.r; + brush.G = cdp.g; + brush.B = cdp.b; + brush.A = cdp.a; + uiDrawFill(c, path, &brush); + uiDrawFreePath(path); + + uiDrawRestore(c); +} + +void drawTextBackground(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout, size_t start, size_t end, uiDrawBrush *brush, int isSelection) +{ + int line, nLines; + size_t lstart, lend; + double layoutwid, layoutht; + + uiDrawTextLayoutExtents(layout, &layoutwid, &layoutht); + nLines = uiDrawTextLayoutNumLines(layout); + for (line = 0; line < nLines; line++) { + uiDrawTextLayoutLineByteRange(layout, line, &lstart, &lend); + if (start >= lstart && start < lend) + break; + } + while (end - start > 0) { + uiDrawTextLayoutLineMetrics m; + double startx, endx; + uiDrawPath *path; + + uiDrawTextLayoutLineByteRange(layout, line, &lstart, &lend); + if (lend > end) // don't cross lines + lend = end; + startx = uiDrawTextLayoutByteLocationInLine(layout, start, line); + // TODO explain this; note the use of start with lend + endx = layoutwid; + if (!isSelection || end <= lend) + endx = uiDrawTextLayoutByteLocationInLine(layout, lend, line); + uiDrawTextLayoutLineGetMetrics(layout, line, &m); + path = uiDrawNewPath(uiDrawFillModeWinding); + uiDrawPathAddRectangle(path, + x + startx, y + m.Y, + endx - startx, m.Height); + uiDrawPathEnd(path); + uiDrawFill(c, path, brush); + uiDrawFreePath(path); + line++; + start = lend; + } +} diff --git a/_wip/attrstr_metrics/common_OLD_uipriv_attrstr.h b/_wip/attrstr_metrics/common_OLD_uipriv_attrstr.h new file mode 100644 index 00000000..ec920954 --- /dev/null +++ b/_wip/attrstr_metrics/common_OLD_uipriv_attrstr.h @@ -0,0 +1,15 @@ + + +// TODO split these into a separate header file? + +// drawtext.c +struct caretDrawParams { + double r; + double g; + double b; + double a; + double xoff; + double width; +}; +extern void caretDrawParams(uiDrawContext *c, double height, struct caretDrawParams *p); +extern void drawTextBackground(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout, size_t start, size_t end, uiDrawBrush *brush, int isSelection); diff --git a/_wip/attrstr_metrics/darwin_OLD__appkit_drawtext.m b/_wip/attrstr_metrics/darwin_OLD__appkit_drawtext.m new file mode 100644 index 00000000..644a2bdf --- /dev/null +++ b/_wip/attrstr_metrics/darwin_OLD__appkit_drawtext.m @@ -0,0 +1,347 @@ +// 2 january 2017 +#import "uipriv_darwin.h" +#import "draw.h" + +@interface lineInfo : NSObject +@property NSRange glyphRange; +@property NSRange characterRange; +@property NSRect lineRect; +@property NSRect lineUsedRect; +@property NSRect glyphBoundingRect; +@property CGFloat baselineOffset; +@property double ascent; +@property double descent; +@property double leading; +@end + +@implementation lineInfo +@end + +struct uiDrawTextLayout { + // NSTextStorage is subclassed from NSMutableAttributedString + NSTextStorage *attrstr; + NSTextContainer *container; + NSLayoutManager *layoutManager; + + // the width as passed into uiDrawTextLayout constructors + double width; + +#if 0 /* TODO */ + // the *actual* size of the frame + // note: technically, metrics returned from frame are relative to CGPathGetPathBoundingBox(tl->path) + // however, from what I can gather, for a path created by CGPathCreateWithRect(), like we do (with a NULL transform), CGPathGetPathBoundingBox() seems to just return the standardized form of the rect used to create the path + // (this I confirmed through experimentation) + // so we can just use tl->size for adjustments + // we don't need to adjust coordinates by any origin since our rect origin is (0, 0) + CGSize size; +#endif + + NSMutableArray *lineInfo; + + // for converting CFAttributedString indices to byte offsets + size_t *u16tou8; + size_t nu16tou8; // TODO I don't like the casing of this name +}; + +static NSFont *fontdescToNSFont(uiDrawFontDescriptor *fd) +{ + NSFontDescriptor *desc; + NSFont *font; + + desc = fontdescToNSFontDescriptor(fd); + font = [NSFont fontWithDescriptor:desc size:fd->Size]; + [desc release]; + return font; +} + +static NSTextStorage *attrstrToTextStorage(uiAttributedString *s, uiDrawFontDescriptor *defaultFont) +{ + NSString *nsstr; + NSMutableDictionary *defaultAttrs; + NSTextStorage *attrstr; + + nsstr = [[NSString alloc] initWithCharacters:attrstrUTF16(s) + length:attrstrUTF16Len(s)]; + + defaultAttrs = [NSMutableDictionary new]; + [defaultAttrs setObject:fontdescToNSFont(defaultFont) + forKey:NSFontAttributeName]; + + attrstr = [[NSTextStorage alloc] initWithString:nsstr + attributes:defaultAttrs]; + [defaultAttrs release]; + [nsstr release]; + + [attrstr beginEditing]; + // TODO copy in the attributes + [attrstr endEditing]; + + return attrstr; +} + +// TODO fine-tune all the properties +uiDrawTextLayout *uiDrawNewTextLayout(uiAttributedString *s, uiDrawFontDescriptor *defaultFont, double width) +{ + uiDrawTextLayout *tl; + CGFloat cgwidth; + // TODO correct type? + NSUInteger index; + + tl = uiNew(uiDrawTextLayout); + tl->attrstr = attrstrToTextStorage(s, defaultFont); + tl->width = width; + + // TODO the documentation on the size property implies this might not be necessary? + cgwidth = (CGFloat) width; + if (cgwidth < 0) + cgwidth = CGFLOAT_MAX; + // TODO rename to tl->textContainer + tl->container = [[NSTextContainer alloc] initWithSize:NSMakeSize(cgwidth, CGFLOAT_MAX)]; + // TODO pull the reference for this + [tl->container setLineFragmentPadding:0]; + + tl->layoutManager = [[NSLayoutManager alloc] init]; + [tl->layoutManager setTypesetterBehavior:NSTypesetterLatestBehavior]; + + [tl->layoutManager addTextContainer:tl->container]; + [tl->attrstr addLayoutManager:tl->layoutManager]; + // and force a re-layout (TODO get source + [tl->layoutManager glyphRangeForTextContainer:tl->container]; + + // TODO equivalent of CTFrameProgression for RTL/LTR? + + // now collect line information; see https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/TextLayout/Tasks/CountLines.html + tl->lineInfo = [NSMutableArray new]; + index = 0; + while (index < [tl->layoutManager numberOfGlyphs]) { + NSRange glyphRange; + __block lineInfo *li; + + li = [lineInfo new]; + li.lineRect = [tl->layoutManager lineFragmentRectForGlyphAtIndex:index effectiveRange:&glyphRange]; + li.glyphRange = glyphRange; + li.characterRange = [tl->layoutManager characterRangeForGlyphRange:li.glyphRange actualGlyphRange:NULL]; + li.lineUsedRect = [tl->layoutManager lineFragmentUsedRectForGlyphAtIndex:index effectiveRange:NULL]; + li.glyphBoundingRect = [tl->layoutManager boundingRectForGlyphRange:li.glyphRange inTextContainer:tl->container]; + // and this is from http://www.cocoabuilder.com/archive/cocoa/308568-how-to-get-baseline-info.html and http://www.cocoabuilder.com/archive/cocoa/199283-height-and-location-of-text-within-line-in-nslayoutmanager-ignoring-spacing.html + li.baselineOffset = [[tl->layoutManager typesetter] baselineOffsetInLayoutManager:tl->layoutManager glyphIndex:index]; + li.ascent = 0; + li.descent = 0; + li.leading = 0; + // imitate what AppKit actually does (or seems to) + [tl->attrstr enumerateAttributesInRange:li.characterRange options:0 usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) { + NSFont *f; + NSRect boundingRect; + double v, realAscent, realDescent, realLeading; + BOOL skipAdjust, doAdjust; + + f = (NSFont *) [attrs objectForKey:NSFontAttributeName]; + if (f == nil) { + f = [NSFont fontWithName:@"Helvetica" size:12.0]; + if (f == nil) + f = [NSFont systemFontOfSize:12.0]; + } + + boundingRect = [f boundingRectForFont]; + realAscent = [f ascender]; + realDescent = -[f descender]; + realLeading = [f leading]; + skipAdjust = NO; + doAdjust = NO; + if (NSMaxY(boundingRect) <= realAscent) { + // ascent entirely within bounding box + // don't do anything if there's leading; I'm guessing it's a combination of both of the reasons to skip below... (least sure of this one) + if (realLeading != 0) + skipAdjust = YES; + // does the descent slip outside the bounding box? + if (-realDescent <= NSMinY(boundingRect)) + // yes — I guess we should assume accents don't collide with the previous line's descent, though I'm not as sure of that as I am about the else clause below... + skipAdjust = YES; + } else { + // ascent outside bounding box — ascent does not include accents + // only skip adjustment if there isn't leading (apparently some fonts use the previous line's leading for accents? :/ ) + if (realLeading != 0) + skipAdjust = YES; + } + if (!skipAdjust) { + UniChar ch = 0xC0; + CGGlyph glyph; + + // there does not seem to be an AppKit API for this... + if (CTFontGetGlyphsForCharacters((CTFontRef) f, &ch, &glyph, 1) != false) { + NSRect bbox; + + bbox = [f boundingRectForGlyph:glyph]; + if (NSMaxY(bbox) > realAscent) + doAdjust = YES; + if (-realDescent > NSMinY(bbox)) + doAdjust = YES; + } + } + // TODO vertical + + v = [f ascender]; + // TODO get this one back out + if (doAdjust) + v += 0.2 * ([f ascender] + [f descender]); + //v = floor(v + 0.5); + if (li.ascent < v) + li.ascent = v; + + v = -[f descender];// floor(-[f descender] + 0.5); + if (li.descent < v) + li.descent = v; + + v = [f leading];//floor([f leading] + 0.5); + if (li.leading < v) + li.leading = v; + }]; + li.ascent = floor(li.ascent + 0.5); + li.descent = floor(li.descent + 0.5); + li.leading = floor(li.leading + 0.5); + [tl->lineInfo addObject:li]; + [li release]; + index = glyphRange.location + glyphRange.length; + } + + // and finally copy the UTF-16 to UTF-8 index conversion table + tl->u16tou8 = attrstrCopyUTF16ToUTF8(s, &(tl->nu16tou8)); + + return tl; +} + +void uiDrawFreeTextLayout(uiDrawTextLayout *tl) +{ + uiFree(tl->u16tou8); + [tl->lineInfo release]; + [tl->layoutManager release]; + [tl->container release]; + [tl->attrstr release]; + uiFree(tl); +} + +// TODO document that (x,y) is the top-left corner of the *entire frame* +void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y) +{ + NSGraphicsContext *gc; + + CGContextFlush(c->c); // just to be safe + [NSGraphicsContext saveGraphicsState]; + gc = [NSGraphicsContext graphicsContextWithGraphicsPort:c->c flipped:YES]; + [NSGraphicsContext setCurrentContext:gc]; + + // TODO is this the right point? + // TODO does this draw with the proper default styles? + [tl->layoutManager drawGlyphsForGlyphRange:[tl->layoutManager glyphRangeForTextContainer:tl->container] + atPoint:NSMakePoint(x, y)]; + + [gc flushGraphics]; // just to be safe + [NSGraphicsContext restoreGraphicsState]; + // TODO release gc? +} + +// TODO update all of these { +// TODO document that the width and height of a layout is not necessarily the sum of the widths and heights of its constituent lines; this is definitely untrue on OS X, where lines are placed in such a way that the distance between baselines is always integral +// TODO width doesn't include trailing whitespace... +// TODO figure out how paragraph spacing should play into this +// } +void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height) +{ + NSRect r; + + // see https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/TextLayout/Tasks/StringHeight.html + r = [tl->layoutManager usedRectForTextContainer:tl->container]; + *width = r.size.width; + *height = r.size.height; +} + +int uiDrawTextLayoutNumLines(uiDrawTextLayout *tl) +{ + return [tl->lineInfo count]; +} + +void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end) +{ + lineInfo *li; + + li = (lineInfo *) [tl->lineInfo objectAtIndex:line]; + *start = tl->u16tou8[li.characterRange.location]; + *end = tl->u16tou8[li.characterRange.location + li.characterRange.length]; +} + +void uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m) +{ + lineInfo *li; + + li = (lineInfo *) [tl->lineInfo objectAtIndex:line]; + + m->X = li.lineRect.origin.x; + m->Y = li.lineRect.origin.y; + // if we use li.lineRect here we get the whole line, not just the part with stuff in it + m->Width = li.lineUsedRect.size.width; + m->Height = li.lineRect.size.height; + + // TODO is this correct? + m->BaselineY = (m->Y + m->Height) - li.baselineOffset; + m->Ascent = li.ascent; + m->Descent = li.descent; + m->Leading = li.leading; + + // TODO + m->ParagraphSpacingBefore = 0; + m->LineHeightSpace = 0; + m->LineSpacing = 0; + m->ParagraphSpacing = 0; +} + +void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, uiDrawTextLayoutHitTestResult *result) +{ +#if 0 /* TODO */ + CFIndex i; + CTLineRef line; + CFIndex pos; + + if (y >= 0) { + for (i = 0; i < tl->nLines; i++) { + double ltop, lbottom; + + ltop = tl->lineMetrics[i].Y; + lbottom = ltop + tl->lineMetrics[i].Height; + if (y >= ltop && y < lbottom) + break; + } + result->YPosition = uiDrawTextLayoutHitTestPositionInside; + if (i == tl->nLines) { + i--; + result->YPosition = uiDrawTextLayoutHitTestPositionAfter; + } + } else { + i = 0; + result->YPosition = uiDrawTextLayoutHitTestPositionBefore; + } + result->Line = i; + + result->XPosition = uiDrawTextLayoutHitTestPositionInside; + if (x < tl->lineMetrics[i].X) { + result->XPosition = uiDrawTextLayoutHitTestPositionBefore; + // and forcibly return the first character + x = tl->lineMetrics[i].X; + } else if (x > (tl->lineMetrics[i].X + tl->lineMetrics[i].Width)) { + result->XPosition = uiDrawTextLayoutHitTestPositionAfter; + // and forcibly return the last character + x = tl->lineMetrics[i].X + tl->lineMetrics[i].Width; + } + + line = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, i); + // TODO copy the part from the docs about this point + pos = CTLineGetStringIndexForPosition(line, CGPointMake(x, 0)); + if (pos == kCFNotFound) { + // TODO + } + result->Pos = tl->u16tou8[pos]; +#endif +} + +void uiDrawTextLayoutByteRangeToRectangle(uiDrawTextLayout *tl, size_t start, size_t end, uiDrawTextLayoutByteRangeRectangle *r) +{ +} diff --git a/_wip/attrstr_metrics/darwin_OLD__appkit_fontmatch.m b/_wip/attrstr_metrics/darwin_OLD__appkit_fontmatch.m new file mode 100644 index 00000000..8e81400f --- /dev/null +++ b/_wip/attrstr_metrics/darwin_OLD__appkit_fontmatch.m @@ -0,0 +1,223 @@ +// 3 january 2017 +#import "uipriv_darwin.h" + +// Stupidity: Core Text requires an **exact match for the entire traits dictionary**, otherwise it will **drop ALL the traits**. +// This seems to be true for every function in Core Text that advertises that it performs font matching; I can confirm at the time of writing this is the case for +// - CTFontDescriptorCreateMatchingFontDescriptors() (though the conditions here seem odd) +// - CTFontCreateWithFontDescriptor() +// - CTFontCreateCopyWithAttributes() +// And as a bonus prize, this also applies to Cocoa's NSFontDescriptor methods as well! +// We have to implement the closest match ourselves. +// This needs to be done early in the matching process; in particular, we have to do this before adding any features (such as small caps), because the matching descriptors won't have those. + +struct closeness { + NSUInteger index; + double weight; + double italic; + double stretch; + double distance; +}; + +static double doubleAttr(NSDictionary *traits, NSString *attr) +{ + NSNumber *n; + + n = (NSNumber *) [traits objectForKey:attr]; + return [n doubleValue]; +} + +struct italicCloseness { + double normal; + double oblique; + double italic; +}; + +// remember that in closeness, 0 means exact +// in this case, since we define the range, we use 0.5 to mean "close enough" (oblique for italic and italic for oblique) and 1 to mean "not a match" +static const struct italicCloseness italicClosenesses[] = { + [uiDrawTextItalicNormal] = { 0, 1, 1 }, + [uiDrawTextItalicOblique] = { 1, 0, 0.5 }, + [uiDrawTextItalicItalic] = { 1, 0.5, 0 }, +}; + +// Italics are hard because Core Text does NOT distinguish between italic and oblique. +// All Core Text provides is a slant value and the italic bit of the symbolic traits mask. +// However, Core Text does seem to guarantee (from experimentation; see below) that the slant will be nonzero if and only if the italic bit is set, so we don't need to use the slant value. +// Core Text also seems to guarantee that if a font lists itself as Italic or Oblique by name (font subfamily name, font style name, whatever), it will also have that bit set, so testing this bit does cover all fonts that name themselves as Italic and Oblique. (Again, this is from the below experimentation.) +// TODO there is still one catch that might matter from a user's POV: the reverse is not true — the italic bit can be set even if the style of the font face/subfamily/style isn't named as Italic (for example, script typefaces like Adobe's Palace Script MT Std); I don't know what to do about this... I know how to start: find a script font that has an italic form (Adobe's Palace Script MT Std does not; only Regular and Semibold) +// TODO see if the above applies to Cocoa as well +static double italicCloseness(NSFontDescriptor *desc, NSDictionary *traits, uiDrawTextItalic italic) +{ + const struct italicCloseness *ic = &(italicClosenesses[italic]); + NSNumber *num; + NSFontSymbolicTraits symbolic; + NSString *styleName; + NSRange range; + BOOL isOblique; + + num = (NSNumber *) [traits objectForKey:NSFontSymbolicTrait]; + // TODO this should really be a uint32_t-specific one + symbolic = (NSFontSymbolicTraits) [num unsignedIntegerValue]; + if ((symbolic & NSFontItalicTrait) == 0) + return ic->normal; + + // Okay, now we know it's either Italic or Oblique + // Pango's Core Text code just does a g_strrstr() (backwards case-sensitive search) for "Oblique" in the font's style name (see https://git.gnome.org/browse/pango/tree/pango/pangocoretext-fontmap.c); let's do that too I guess + // note that NSFontFaceAttribute is the Cocoa equivalent of the style name + isOblique = NO; // default value + styleName = (NSString *) [desc objectForKey:NSFontFaceAttribute]; + // TODO is styleName guaranteed? + // TODO NSLiteralSearch? + range = [styleName rangeOfString:@"Oblique" options:NSCaseInsensitiveSearch]; + if (range.location != NSNotFound) + return ic->oblique; + return ic->italic; +} + +static NSFontDescriptor *matchTraits(NSFontDescriptor *against, double targetWeight, uiDrawTextItalic targetItalic, double targetStretch) +{ + NSArray *matching; + NSUInteger i, n; + struct closeness *closeness; + NSFontDescriptor *current; + NSFontDescriptor *out; + + matching = [against matchingFontDescriptorsWithMandatoryKeys:nil]; + if (matching == nil) + // no matches; give the original back and hope for the best + return against; + n = [matching count]; + if (n == 0) { + // likewise +//TODO [matching release]; + return against; + } + + closeness = (struct closeness *) uiAlloc(n * sizeof (struct closeness), "struct closeness[]"); + for (i = 0; i < n; i++) { + NSDictionary *traits; + + closeness[i].index = i; + current = (NSFontDescriptor *) [matching objectAtIndex:i]; + traits = (NSDictionary *) [current objectForKey:NSFontTraitsAttribute]; + if (traits == nil) { + // couldn't get traits; be safe by ranking it lowest + // LONGTERM figure out what the longest possible distances are + closeness[i].weight = 3; + closeness[i].italic = 2; + closeness[i].stretch = 3; + continue; + } + closeness[i].weight = doubleAttr(traits, NSFontWeightTrait) - targetWeight; + closeness[i].italic = italicCloseness(current, traits, targetItalic); + closeness[i].stretch = doubleAttr(traits, NSFontWidthTrait) - targetStretch; + // TODO release traits? + } + + // now figure out the 3-space difference between the three and sort by that + // TODO merge this loop with the previous loop? + for (i = 0; i < n; i++) { + double weight, italic, stretch; + + weight = closeness[i].weight; + weight *= weight; + italic = closeness[i].italic; + italic *= italic; + stretch = closeness[i].stretch; + stretch *= stretch; + closeness[i].distance = sqrt(weight + italic + stretch); + } + qsort_b(closeness, n, sizeof (struct closeness), ^(const void *aa, const void *bb) { + const struct closeness *a = (const struct closeness *) aa; + const struct closeness *b = (const struct closeness *) bb; + + // via http://www.gnu.org/software/libc/manual/html_node/Comparison-Functions.html#Comparison-Functions + // LONGTERM is this really the best way? isn't it the same as if (*a < *b) return -1; if (*a > *b) return 1; return 0; ? + return (a->distance > b->distance) - (a->distance < b->distance); + }); + // and the first element of the sorted array is what we want + out = (NSFontDescriptor *) [matching objectAtIndex:closeness[0].index]; + // TODO is this correct? + [out retain]; // get rule + + // release everything + uiFree(closeness); +//TODO [matching release]; + // and release the original descriptor since we no longer need it + [against release]; + + return out; +} + +// since uiDrawTextWeight effectively corresponds to OS/2 weights (which roughly correspond to GDI, Pango, and DirectWrite weights, and to a lesser(? TODO) degree, CSS weights), let's just do what Core Text does with OS/2 weights +// TODO this will not be correct for system fonts, which use cached values that have no relation to the OS/2 weights; we need to figure out how to reconcile these +// for more information, see https://bugzilla.gnome.org/show_bug.cgi?id=766148 and TODO_put_blog_post_here_once_I_write_it (TODO keep this line when resolving the above TODO) +static const double weightsToCTWeights[] = { + -1.0, // 0..99 + -0.7, // 100..199 + -0.5, // 200..299 + -0.23, // 300..399 + 0.0, // 400..499 + 0.2, // 500..599 + 0.3, // 600..699 + 0.4, // 700..799 + 0.6, // 800..899 + 0.8, // 900..999 + 1.0, // 1000 +}; + +static double weightToCTWeight(uiDrawTextWeight weight) +{ + int weightClass; + double ctclass; + double rest, weightFloor, nextFloor; + + if (weight <= 0) + return -1.0; + if (weight >= 1000) + return 1.0; + + weightClass = weight / 100; + rest = (double) weight; + weightFloor = (double) (weightClass * 100); + nextFloor = (double) ((weightClass + 1) * 100); + rest = (rest - weightFloor) / (nextFloor - weightFloor); + + ctclass = weightsToCTWeights[weightClass]; + return fma(rest, + weightsToCTWeights[weightClass + 1] - ctclass, + ctclass); +} + +// based on what Core Text says about actual fonts (system fonts, system fonts in another folder to avoid using cached values, Adobe Font Folio 11, Google Fonts archive, fonts in Windows 7/8.1/10) +static const double stretchesToCTWidths[] = { + [uiDrawTextStretchUltraCondensed] = -0.400000, + [uiDrawTextStretchExtraCondensed] = -0.300000, + [uiDrawTextStretchCondensed] = -0.200000, + [uiDrawTextStretchSemiCondensed] = -0.100000, + [uiDrawTextStretchNormal] = 0.000000, + [uiDrawTextStretchSemiExpanded] = 0.100000, + [uiDrawTextStretchExpanded] = 0.200000, + [uiDrawTextStretchExtraExpanded] = 0.300000, + // this one isn't present in any of the fonts I tested, but it follows naturally from the pattern of the rest, so... (TODO verify by checking the font files directly) + [uiDrawTextStretchUltraExpanded] = 0.400000, +}; + +NSFontDescriptor *fontdescToNSFontDescriptor(uiDrawFontDescriptor *fd) +{ + NSMutableDictionary *attrs; + NSFontDescriptor *basedesc; + + attrs = [NSMutableDictionary new]; + [attrs setObject:[NSString stringWithUTF8String:fd->Family] + forKey:NSFontFamilyAttribute]; + [attrs setObject:[NSNumber numberWithDouble:fd->Size] + forKey:NSFontSizeAttribute]; + + basedesc = [[NSFontDescriptor alloc] initWithFontAttributes:attrs]; + [attrs release]; + return matchTraits(basedesc, + weightToCTWeight(fd->Weight), + fd->Italic, + stretchesToCTWidths[fd->Stretch]); +} diff --git a/_wip/attrstr_metrics/darwin_OLD__old_drawtext.m b/_wip/attrstr_metrics/darwin_OLD__old_drawtext.m new file mode 100644 index 00000000..72a8d2e6 --- /dev/null +++ b/_wip/attrstr_metrics/darwin_OLD__old_drawtext.m @@ -0,0 +1,268 @@ +// 6 september 2015 +#import "uipriv_darwin.h" + +// TODO double-check that we are properly handling allocation failures (or just toll free bridge from cocoa) +struct uiDrawFontFamilies { + CFArrayRef fonts; +}; + +uiDrawFontFamilies *uiDrawListFontFamilies(void) +{ + uiDrawFontFamilies *ff; + + ff = uiNew(uiDrawFontFamilies); + ff->fonts = CTFontManagerCopyAvailableFontFamilyNames(); + if (ff->fonts == NULL) + implbug("error getting available font names (no reason specified) (TODO)"); + return ff; +} + +int uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff) +{ + return CFArrayGetCount(ff->fonts); +} + +char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, int n) +{ + CFStringRef familystr; + char *family; + + familystr = (CFStringRef) CFArrayGetValueAtIndex(ff->fonts, n); + // toll-free bridge + family = uiDarwinNSStringToText((NSString *) familystr); + // Get Rule means we do not free familystr + return family; +} + +void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff) +{ + CFRelease(ff->fonts); + uiFree(ff); +} + +struct uiDrawTextFont { + CTFontRef f; +}; + +uiDrawTextFont *mkTextFont(CTFontRef f, BOOL retain) +{ + uiDrawTextFont *font; + + font = uiNew(uiDrawTextFont); + font->f = f; + if (retain) + CFRetain(font->f); + return font; +} + +uiDrawTextFont *mkTextFontFromNSFont(NSFont *f) +{ + // toll-free bridging; we do retain, though + return mkTextFont((CTFontRef) f, YES); +} + +static CFMutableDictionaryRef newAttrList(void) +{ + CFMutableDictionaryRef attr; + + attr = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + if (attr == NULL) + complain("error creating attribute dictionary in newAttrList()()"); + return attr; +} + +static void addFontFamilyAttr(CFMutableDictionaryRef attr, const char *family) +{ + CFStringRef cfstr; + + cfstr = CFStringCreateWithCString(NULL, family, kCFStringEncodingUTF8); + if (cfstr == NULL) + complain("error creating font family name CFStringRef in addFontFamilyAttr()"); + CFDictionaryAddValue(attr, kCTFontFamilyNameAttribute, cfstr); + CFRelease(cfstr); // dictionary holds its own reference +} + +static void addFontSizeAttr(CFMutableDictionaryRef attr, double size) +{ + CFNumberRef n; + + n = CFNumberCreate(NULL, kCFNumberDoubleType, &size); + CFDictionaryAddValue(attr, kCTFontSizeAttribute, n); + CFRelease(n); +} + +#if 0 +TODO +// See http://stackoverflow.com/questions/4810409/does-coretext-support-small-caps/4811371#4811371 and https://git.gnome.org/browse/pango/tree/pango/pangocoretext-fontmap.c for what these do +// And fortunately, unlike the traits (see below), unmatched features are simply ignored without affecting the other features :D +static void addFontSmallCapsAttr(CFMutableDictionaryRef attr) +{ + CFMutableArrayRef outerArray; + CFMutableDictionaryRef innerDict; + CFNumberRef numType, numSelector; + int num; + + outerArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + if (outerArray == NULL) + complain("error creating outer CFArray for adding small caps attributes in addFontSmallCapsAttr()"); + + // Apple's headers say these are deprecated, but a few fonts still rely on them + num = kLetterCaseType; + numType = CFNumberCreate(NULL, kCFNumberIntType, &num); + num = kSmallCapsSelector; + numSelector = CFNumberCreate(NULL, kCFNumberIntType, &num); + innerDict = newAttrList(); + CFDictionaryAddValue(innerDict, kCTFontFeatureTypeIdentifierKey, numType); + CFRelease(numType); + CFDictionaryAddValue(innerDict, kCTFontFeatureSelectorIdentifierKey, numSelector); + CFRelease(numSelector); + CFArrayAppendValue(outerArray, innerDict); + CFRelease(innerDict); // and likewise for CFArray + + // these are the non-deprecated versions of the above; some fonts have these instead + num = kLowerCaseType; + numType = CFNumberCreate(NULL, kCFNumberIntType, &num); + num = kLowerCaseSmallCapsSelector; + numSelector = CFNumberCreate(NULL, kCFNumberIntType, &num); + innerDict = newAttrList(); + CFDictionaryAddValue(innerDict, kCTFontFeatureTypeIdentifierKey, numType); + CFRelease(numType); + CFDictionaryAddValue(innerDict, kCTFontFeatureSelectorIdentifierKey, numSelector); + CFRelease(numSelector); + CFArrayAppendValue(outerArray, innerDict); + CFRelease(innerDict); // and likewise for CFArray + + CFDictionaryAddValue(attr, kCTFontFeatureSettingsAttribute, outerArray); + CFRelease(outerArray); +} +#endif + +#if 0 +// Named constants for these were NOT added until 10.11, and even then they were added as external symbols instead of macros, so we can't use them directly :( +// kode54 got these for me before I had access to El Capitan; thanks to him. +#define ourNSFontWeightUltraLight -0.800000 +#define ourNSFontWeightThin -0.600000 +#define ourNSFontWeightLight -0.400000 +#define ourNSFontWeightRegular 0.000000 +#define ourNSFontWeightMedium 0.230000 +#define ourNSFontWeightSemibold 0.300000 +#define ourNSFontWeightBold 0.400000 +#define ourNSFontWeightHeavy 0.560000 +#define ourNSFontWeightBlack 0.620000 +#endif + +// Now remember what I said earlier about having to add the small caps traits after calling the above? This gets a dictionary back so we can do so. +CFMutableDictionaryRef extractAttributes(CTFontDescriptorRef desc) +{ + CFDictionaryRef dict; + CFMutableDictionaryRef mdict; + + dict = CTFontDescriptorCopyAttributes(desc); + // this might not be mutable, so make a mutable copy + mdict = CFDictionaryCreateMutableCopy(NULL, 0, dict); + CFRelease(dict); + return mdict; +} + +uiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc) +{ + CTFontRef f; + CFMutableDictionaryRef attr; + CTFontDescriptorRef cfdesc; + + attr = newAttrList(); + addFontFamilyAttr(attr, desc->Family); + addFontSizeAttr(attr, desc->Size); + + // now we have to do the traits matching, so create a descriptor, match the traits, and then get the attributes back + cfdesc = CTFontDescriptorCreateWithAttributes(attr); + // TODO release attr? + cfdesc = matchTraits(cfdesc, desc->Weight, desc->Italic, desc->Stretch); + + // specify the initial size again just to be safe + f = CTFontCreateWithFontDescriptor(cfdesc, desc->Size, NULL); + // TODO release cfdesc? + + return mkTextFont(f, NO); // we hold the initial reference; no need to retain again +} + +void uiDrawFreeTextFont(uiDrawTextFont *font) +{ + CFRelease(font->f); + uiFree(font); +} + +uintptr_t uiDrawTextFontHandle(uiDrawTextFont *font) +{ + return (uintptr_t) (font->f); +} + +void uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc) +{ + // TODO +} + +// text sizes and user space points are identical: +// - https://developer.apple.com/library/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/TypoFeatures/TextSystemFeatures.html#//apple_ref/doc/uid/TP40009459-CH6-51627-BBCCHIFF text points are 72 per inch +// - https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Transforms/Transforms.html#//apple_ref/doc/uid/TP40003290-CH204-SW5 user space points are 72 per inch +void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics) +{ + metrics->Ascent = CTFontGetAscent(font->f); + metrics->Descent = CTFontGetDescent(font->f); + metrics->Leading = CTFontGetLeading(font->f); + metrics->UnderlinePos = CTFontGetUnderlinePosition(font->f); + metrics->UnderlineThickness = CTFontGetUnderlineThickness(font->f); +} + +// LONGTERM allow line separation and leading to be factored into a wrapping text layout + +// TODO reconcile differences in character wrapping on platforms +void uiDrawTextLayoutExtents(uiDrawTextLayout *layout, double *width, double *height) +{ + struct framesetter fs; + + mkFramesetter(layout, &fs); + *width = fs.extents.width; + *height = fs.extents.height; + freeFramesetter(&fs); +} + +// LONGTERM provide an equivalent to CTLineGetTypographicBounds() on uiDrawTextLayout? + +// LONGTERM keep this for later features and documentation purposes +#if 0 + + // LONGTERM provide a way to get the image bounds as a separate function later + bounds = CTLineGetImageBounds(line, c); + // though CTLineGetImageBounds() returns CGRectNull on error, it also returns CGRectNull on an empty string, so we can't reasonably check for error + + // CGContextSetTextPosition() positions at the baseline in the case of CTLineDraw(); we need the top-left corner instead + CTLineGetTypographicBounds(line, &yoff, NULL, NULL); + // remember that we're flipped, so we subtract + y -= yoff; + CGContextSetTextPosition(c, x, y); +#endif + +#if 0 +void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, int startChar, int endChar, double r, double g, double b, double a) +{ + CGColorSpaceRef colorspace; + CGFloat components[4]; + CGColorRef color; + + // for consistency with windows, use sRGB + colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); + components[0] = r; + components[1] = g; + components[2] = b; + components[3] = a; + color = CGColorCreate(colorspace, components); + CGColorSpaceRelease(colorspace); + + CFAttributedStringSetAttribute(layout->mas, + rangeToCFRange(), + kCTForegroundColorAttributeName, + color); + CGColorRelease(color); // TODO safe? +} +#endif diff --git a/_wip/attrstr_metrics/darwin_OLD_drawtext.m b/_wip/attrstr_metrics/darwin_OLD_drawtext.m new file mode 100644 index 00000000..1fa5920e --- /dev/null +++ b/_wip/attrstr_metrics/darwin_OLD_drawtext.m @@ -0,0 +1,314 @@ +// 2 january 2017 +#import "uipriv_darwin.h" +#import "draw.h" + +// TODO on an empty string nLines == 0 +// we must prevent this somehow +// TODO in general, every function could be more robust, but we cannot have a situation where there are zero lines +// TODO what happens to extents if only whitespace? + +struct uiDrawTextLayout { + CFAttributedStringRef attrstr; + + // the width as passed into uiDrawTextLayout constructors + double width; + + CTFramesetterRef framesetter; + + // the *actual* size of the frame + // note: technically, metrics returned from frame are relative to CGPathGetPathBoundingBox(tl->path) + // however, from what I can gather, for a path created by CGPathCreateWithRect(), like we do (with a NULL transform), CGPathGetPathBoundingBox() seems to just return the standardized form of the rect used to create the path + // (this I confirmed through experimentation) + // so we can just use tl->size for adjustments + // we don't need to adjust coordinates by any origin since our rect origin is (0, 0) + CGSize size; + + CGPathRef path; + CTFrameRef frame; + + CFArrayRef lines; + CFIndex nLines; + // we compute this once when first creating the layout + uiDrawTextLayoutLineMetrics *lineMetrics; + + NSArray *backgroundBlocks; + + // for converting CFAttributedString indices from/to byte offsets + size_t *u8tou16; + size_t nUTF8; + size_t *u16tou8; + size_t nUTF16; +}; + +// TODO document that lines may or may not overlap because ours do in the case of multiple combining characters +static uiDrawTextLayoutLineMetrics *computeLineMetrics(CTFrameRef frame, CGSize size) +{ + uiDrawTextLayoutLineMetrics *metrics; + CFArrayRef lines; + CTLineRef line; + CFIndex i, n; + CGFloat ypos; + CGRect bounds, boundsNoLeading; + CGFloat ascent, descent, leading; + CGPoint *origins; + + lines = CTFrameGetLines(frame); + n = CFArrayGetCount(lines); + metrics = (uiDrawTextLayoutLineMetrics *) uiAlloc(n * sizeof (uiDrawTextLayoutLineMetrics), "uiDrawTextLayoutLineMetrics[] (text layout)"); + + origins = (CGPoint *) uiAlloc(n * sizeof (CGPoint), "CGPoint[] (text layout)"); + CTFrameGetLineOrigins(frame, CFRangeMake(0, n), origins); + + ypos = size.height; + for (i = 0; i < n; i++) { + line = (CTLineRef) CFArrayGetValueAtIndex(lines, i); + bounds = CTLineGetBoundsWithOptions(line, 0); + boundsNoLeading = CTLineGetBoundsWithOptions(line, kCTLineBoundsExcludeTypographicLeading); + + // this is equivalent to boundsNoLeading.size.height + boundsNoLeading.origin.y (manually verified) + ascent = bounds.size.height + bounds.origin.y; + descent = -boundsNoLeading.origin.y; + leading = -bounds.origin.y - descent; + + // Core Text always rounds these up for paragraph style calculations; there is a flag to control it but it's inaccessible (and this behavior is turned off for old versions of iPhoto) + ascent = floor(ascent + 0.5); + descent = floor(descent + 0.5); + if (leading > 0) + leading = floor(leading + 0.5); + + metrics[i].X = origins[i].x; + metrics[i].Y = origins[i].y - descent - leading; + metrics[i].Width = bounds.size.width; + metrics[i].Height = ascent + descent + leading; + + metrics[i].BaselineY = origins[i].y; + metrics[i].Ascent = ascent; + metrics[i].Descent = descent; + metrics[i].Leading = leading; + + // TODO + metrics[i].ParagraphSpacingBefore = 0; + metrics[i].LineHeightSpace = 0; + metrics[i].LineSpacing = 0; + metrics[i].ParagraphSpacing = 0; + + // and finally advance to the next line + ypos += metrics[i].Height; + } + + // okay, but now all these metrics are unflipped + // we need to flip them + for (i = 0; i < n; i++) { + metrics[i].Y = size.height - metrics[i].Y; + // go from bottom-left corner to top-left + metrics[i].Y -= metrics[i].Height; + metrics[i].BaselineY = size.height - metrics[i].BaselineY; + } + + uiFree(origins); + return metrics; +} + +uiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *p) +{ + uiDrawTextLayout *tl; + CGFloat cgwidth; + CFRange range, unused; + CGRect rect; + + tl = uiNew(uiDrawTextLayout); + tl->attrstr = attrstrToCoreFoundation(p, &(tl->backgroundBlocks)); + range.location = 0; + range.length = CFAttributedStringGetLength(tl->attrstr); + tl->width = p->Width; + + // TODO kCTParagraphStyleSpecifierMaximumLineSpacing, kCTParagraphStyleSpecifierMinimumLineSpacing, kCTParagraphStyleSpecifierLineSpacingAdjustment for line spacing + tl->framesetter = CTFramesetterCreateWithAttributedString(tl->attrstr); + if (tl->framesetter == NULL) { + // TODO + } + + cgwidth = (CGFloat) (tl->width); + if (cgwidth < 0) + cgwidth = CGFLOAT_MAX; + tl->size = CTFramesetterSuggestFrameSizeWithConstraints(tl->framesetter, + range, + // TODO kCTFramePathWidthAttributeName? + NULL, + CGSizeMake(cgwidth, CGFLOAT_MAX), + &unused); // not documented as accepting NULL (TODO really?) + + rect.origin = CGPointZero; + rect.size = tl->size; + tl->path = CGPathCreateWithRect(rect, NULL); + tl->frame = CTFramesetterCreateFrame(tl->framesetter, + range, + tl->path, + // TODO kCTFramePathWidthAttributeName? + NULL); + if (tl->frame == NULL) { + // TODO + } + + tl->lines = CTFrameGetLines(tl->frame); + tl->nLines = CFArrayGetCount(tl->lines); + tl->lineMetrics = computeLineMetrics(tl->frame, tl->size); + + // and finally copy the UTF-8/UTF-16 conversion tables + tl->u8tou16 = attrstrCopyUTF8ToUTF16(p->String, &(tl->nUTF8)); + tl->u16tou8 = attrstrCopyUTF16ToUTF8(p->String, &(tl->nUTF16)); + + return tl; +} + +void uiDrawFreeTextLayout(uiDrawTextLayout *tl) +{ + uiFree(tl->u16tou8); + uiFree(tl->u8tou16); + [tl->backgroundBlocks release]; + uiFree(tl->lineMetrics); + CFRelease(tl->frame); + CFRelease(tl->path); + CFRelease(tl->framesetter); + CFRelease(tl->attrstr); + uiFree(tl); +} + +// TODO document that (x,y) is the top-left corner of the *entire frame* +void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y) +{ + backgroundBlock b; + CGAffineTransform textMatrix; + + CGContextSaveGState(c->c); + // save the text matrix because it's not part of the graphics state + textMatrix = CGContextGetTextMatrix(c->c); + + for (b in tl->backgroundBlocks) + b(c, tl, x, y); + + // Core Text doesn't draw onto a flipped view correctly; we have to pretend it was unflipped + // see the iOS bits of the first example at https://developer.apple.com/library/mac/documentation/StringsTextFonts/Conceptual/CoreText_Programming/LayoutOperations/LayoutOperations.html#//apple_ref/doc/uid/TP40005533-CH12-SW1 (iOS is naturally flipped) + // TODO how is this affected by a non-identity CTM? + CGContextTranslateCTM(c->c, 0, c->height); + CGContextScaleCTM(c->c, 1.0, -1.0); + CGContextSetTextMatrix(c->c, CGAffineTransformIdentity); + + // wait, that's not enough; we need to offset y values to account for our new flipping + // TODO explain this calculation + y = c->height - tl->size.height - y; + + // CTFrameDraw() draws in the path we specified when creating the frame + // this means that in our usage, CTFrameDraw() will draw at (0,0) + // so move the origin to be at (x,y) instead + // TODO are the signs correct? + CGContextTranslateCTM(c->c, x, y); + + CTFrameDraw(tl->frame, c->c); + + CGContextSetTextMatrix(c->c, textMatrix); + CGContextRestoreGState(c->c); +} + +// TODO document that the width and height of a layout is not necessarily the sum of the widths and heights of its constituent lines +// TODO width doesn't include trailing whitespace... +// TODO figure out how paragraph spacing should play into this +void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height) +{ + *width = tl->size.width; + *height = tl->size.height; +} + +int uiDrawTextLayoutNumLines(uiDrawTextLayout *tl) +{ + return tl->nLines; +} + +void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end) +{ + CTLineRef lr; + CFRange range; + + lr = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, line); + range = CTLineGetStringRange(lr); + *start = tl->u16tou8[range.location]; + *end = tl->u16tou8[range.location + range.length]; +} + +void uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m) +{ + *m = tl->lineMetrics[line]; +} + +// in the case of overlapping lines, we read lines first to last and use their bottommost point (Y + Height) to determine where the next line should start for hit-testing +// TODO should we document this? +void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *pos, int *line) +{ + int i; + CTLineRef ln; + CFIndex p; + + for (i = 0; i < tl->nLines; i++) { + double ltop, lbottom; + + ltop = tl->lineMetrics[i].Y; + lbottom = ltop + tl->lineMetrics[i].Height; + // y will already >= ltop at this point since the past lbottom should == (or at least >=, see above) ltop + if (y < lbottom) + break; + } + if (i == tl->nLines) + i--; + *line = i; + + ln = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, i); + // note: according to the docs, we pass a y of 0 for this since the is the baseline of that line (the point is relative to the line) + // note: x is relative to the line origin + x -= tl->lineMetrics[*line].X; + p = CTLineGetStringIndexForPosition(ln, CGPointMake(x, 0)); + if (p == kCFNotFound) { + // TODO + } + *pos = tl->u16tou8[p]; +} + +double uiDrawTextLayoutByteLocationInLine(uiDrawTextLayout *tl, size_t pos, int line) +{ + CTLineRef lr; + CFRange range; + + pos = tl->u8tou16[pos]; + if (line < 0 || line >= tl->nLines) + return -1; + lr = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, line); + range = CTLineGetStringRange(lr); + // note: >, not >=, because the position at end is valid! + if (pos < range.location || pos > (range.location + range.length)) + return -1; + // no point in checking the return; we already validated everything and 0 is a valid return for the first index :/ + // note: the result is relative to the line origin (TODO find documentation to support this) + // TODO document that these functions do this + return CTLineGetOffsetForStringIndex(lr, pos, NULL) + tl->lineMetrics[line].X; +} + +void caretDrawParams(uiDrawContext *c, double height, struct caretDrawParams *p) +{ + NSColor *cc; + CGFloat cr, cg, cb, ca; + + // Interface Builder sets this as the insertion point color for a NSTextView by default + cc = [NSColor controlTextColor]; + // the given color may not be an RGBA color, which will cause the -getRed:green:blue:alpha: call to throw an exception + cc = [cc colorUsingColorSpace:[NSColorSpace sRGBColorSpace]]; + [cc getRed:&cr green:&cg blue:&cb alpha:&ca]; + p->r = cr; + p->g = cg; + p->b = cb; + p->a = ca; + // both cc and the controlTextColor it was made from will be autoreleased since they aren't new or init calls + // TODO disabled carets have some blending applied... + + // okay there doesn't seem to be any defined metrics for these, argh... + p->width = 1; + p->xoff = 0; +} diff --git a/_wip/attrstr_metrics/numlinesbyterange b/_wip/attrstr_metrics/numlinesbyterange new file mode 100644 index 00000000..46a7ba21 --- /dev/null +++ b/_wip/attrstr_metrics/numlinesbyterange @@ -0,0 +1,60 @@ +darwin +int uiDrawTextLayoutNumLines(uiDrawTextLayout *tl) +{ + return CFArrayGetCount([tl->forLines lines]); +} + +void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end) +{ + CTLineRef lr; + CFRange range; + + lr = (CTLineRef) CFArrayGetValueAtIndex([tl->forLines lines], line); + range = CTLineGetStringRange(lr); + *start = tl->u16tou8[range.location]; + if (tl->empty) + *end = *start; + else + *end = tl->u16tou8[range.location + range.length]; +} + + +unix +int uiDrawTextLayoutNumLines(uiDrawTextLayout *tl) +{ + return pango_layout_get_line_count(tl->layout); +} + +void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end) +{ + PangoLayoutLine *pll; + + pll = pango_layout_get_line_readonly(tl->layout, line); + *start = pll->start_index; + *end = pll->start_index + pll->length; + // TODO unref pll? +} + + +windows +int uiDrawTextLayoutNumLines(uiDrawTextLayout *tl) +{ +return 0; +#if 0 +TODO + return tl->nLines; +#endif +} + +// DirectWrite doesn't provide a direct way to do this, so we have to do this manually +// TODO does that comment still apply here or to the code at the top of this file? +void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end) +{ +#if 0 +TODO + *start = tl->lineInfo[line].startPos; + *start = tl->u16tou8[*start]; + *end = tl->lineInfo[line].endPos - tl->lineInfo[line].newlineCount; + *end = tl->u16tou8[*end]; +#endif +} diff --git a/_wip/attrstr_metrics/old_ui_attrstr.h b/_wip/attrstr_metrics/old_ui_attrstr.h new file mode 100644 index 00000000..ebe9308d --- /dev/null +++ b/_wip/attrstr_metrics/old_ui_attrstr.h @@ -0,0 +1,72 @@ + +typedef struct uiDrawTextLayoutLineMetrics uiDrawTextLayoutLineMetrics; + +// Height will equal ParagraphSpacingBefore + LineHeightSpace + Ascent + Descent + Leading + LineSpacing + ParagraphSpacing. +// The above values are listed in vertical order, from top to bottom. +// Ascent + Descent + Leading will give you the typographic bounds +// of the text. BaselineY is the boundary between Ascent and Descent. +// X, Y, and BaselineY are all in the layout's coordinate system, so the +// start point of the baseline will be at (X, BaselineY). All values are +// nonnegative. +struct uiDrawTextLayoutLineMetrics { + // This describes the overall bounding box of the line. + double X; + double Y; + double Width; + double Height; + + // This describes the typographic bounds of the line. + double BaselineY; + double Ascent; + double Descent; + double Leading; + + // This describes any additional whitespace. + // TODO come up with better names for these. + double ParagraphSpacingBefore; + double LineHeightSpace; + double LineSpacing; + double ParagraphSpacing; + + // TODO trailing whitespace? +}; + +// uiDrawTextLayoutNumLines() returns the number of lines in tl. +// This number will always be greater than or equal to 1; a text +// layout with no text only has one line. +_UI_EXTERN int uiDrawTextLayoutNumLines(uiDrawTextLayout *tl); + +// uiDrawTextLayoutLineByteRange() returns the byte indices of the +// text that falls into the given line of tl as [start, end). +_UI_EXTERN void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end); + +_UI_EXTERN void uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m); + +// TODO rewrite this documentation + +// uiDrawTextLayoutHitTest() returns the byte offset and line closest +// to the given point. The point is relative to the top-left of the layout. +// If the point is outside the layout itself, the closest point is chosen; +// this allows the function to be used for cursor positioning with the +// mouse. Do keep the returned line in mind if used in this way; the +// user might click on the end of a line, at which point the cursor +// might be at the trailing edge of the last grapheme on the line +// (subject to the operating system's APIs). +_UI_EXTERN void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *pos, int *line); + +// uiDrawTextLayoutByteLocationInLine() returns the point offset +// into the given line that the given byte position stands. This is +// relative to the line's X position (as returned by +// uiDrawTextLayoutLineGetMetrics()), which in turn is relative to +// the top-left of the layout. This function can be used for cursor +// positioning: if start and end are the start and end of the line +// (as returned by uiDrawTextLayoutLineByteRange()), you will get +// the correct offset, even if pos is at the end of the line. If pos is not +// in the range [start, end], a negative value will be returned, +// indicating you need to move the cursor to another line. +// TODO make sure this works right for right-aligned and center-aligned lines and justified lines and RTL text +_UI_EXTERN double uiDrawTextLayoutByteLocationInLine(uiDrawTextLayout *tl, size_t pos, int line); + +_UI_EXTERN void uiDrawCaret(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout, size_t pos, int *line); +// TODO allow blinking +// TODO allow secondary carets diff --git a/_wip/attrstr_metrics/textDarwinEmptyLayout.diff b/_wip/attrstr_metrics/textDarwinEmptyLayout.diff new file mode 100644 index 00000000..bff4959f --- /dev/null +++ b/_wip/attrstr_metrics/textDarwinEmptyLayout.diff @@ -0,0 +1,171 @@ +diff --git a/darwin/attrstr.m b/darwin/attrstr.m +index fd45ec25..86039fad 100644 +--- a/darwin/attrstr.m ++++ b/darwin/attrstr.m +@@ -403,8 +403,13 @@ static CTParagraphStyleRef mkParagraphStyle(uiDrawTextLayoutParams *p) + return ps; + } + +-CFAttributedStringRef attrstrToCoreFoundation(uiDrawTextLayoutParams *p, NSArray **backgroundBlocks) ++static const UniChar emptyChars[] = { 0x20, 0x0 }; ++static const CFIndex emptyCharCount = 1; ++ ++CFAttributedStringRef attrstrToCoreFoundation(uiDrawTextLayoutParams *p, BOOL *isEmpty, NSArray **backgroundBlocks) + { ++ const UniChar *chars; ++ CFIndex charCount; + CFStringRef cfstr; + CFMutableDictionaryRef defaultAttrs; + CTFontRef defaultCTFont; +@@ -413,7 +418,15 @@ CFAttributedStringRef attrstrToCoreFoundation(uiDrawTextLayoutParams *p, NSArray + CFMutableAttributedStringRef mas; + struct foreachParams fep; + +- cfstr = CFStringCreateWithCharacters(NULL, attrstrUTF16(p->String), attrstrUTF16Len(p->String)); ++ *isEmpty = NO; ++ chars = attrstrUTF16(p->String); ++ charCount = attrstrUTF16Len(p->String); ++ if (charCount == 0) { ++ *isEmpty = YES; ++ chars = emptyChars; ++ charCount = emptyCharCount; ++ } ++ cfstr = CFStringCreateWithCharacters(NULL, chars, charCount); + if (cfstr == NULL) { + // TODO + } +diff --git a/darwin/drawtext.m b/darwin/drawtext.m +index 1fa5920e..65912383 100644 +--- a/darwin/drawtext.m ++++ b/darwin/drawtext.m +@@ -2,13 +2,16 @@ + #import "uipriv_darwin.h" + #import "draw.h" + +-// TODO on an empty string nLines == 0 +-// we must prevent this somehow +-// TODO in general, every function could be more robust, but we cannot have a situation where there are zero lines ++// TODO in general, every function could be more robust + // TODO what happens to extents if only whitespace? ++// TODO for empty layouts: ++// - check if alignment correct compared to other OSs, or expected behavior at all ++// - double-check if uiAttributedString allows zero-length attributes; I forget if I did + + struct uiDrawTextLayout { + CFAttributedStringRef attrstr; ++ // this is needed because Core Text will give us an empty line array on a frame made with an empty string ++ BOOL isEmpty; + + // the width as passed into uiDrawTextLayout constructors + double width; +@@ -41,7 +44,7 @@ + }; + + // TODO document that lines may or may not overlap because ours do in the case of multiple combining characters +-static uiDrawTextLayoutLineMetrics *computeLineMetrics(CTFrameRef frame, CGSize size) ++static uiDrawTextLayoutLineMetrics *computeLineMetrics(CTFrameRef frame, CGSize size, BOOL isEmpty) + { + uiDrawTextLayoutLineMetrics *metrics; + CFArrayRef lines; +@@ -79,6 +82,8 @@ + metrics[i].X = origins[i].x; + metrics[i].Y = origins[i].y - descent - leading; + metrics[i].Width = bounds.size.width; ++ if (isEmpty) ++ metrics[i].Width = 0; + metrics[i].Height = ascent + descent + leading; + + metrics[i].BaselineY = origins[i].y; +@@ -117,7 +122,7 @@ + CGRect rect; + + tl = uiNew(uiDrawTextLayout); +- tl->attrstr = attrstrToCoreFoundation(p, &(tl->backgroundBlocks)); ++ tl->attrstr = attrstrToCoreFoundation(p, &(tl->isEmpty), &(tl->backgroundBlocks)); + range.location = 0; + range.length = CFAttributedStringGetLength(tl->attrstr); + tl->width = p->Width; +@@ -152,7 +157,7 @@ + + tl->lines = CTFrameGetLines(tl->frame); + tl->nLines = CFArrayGetCount(tl->lines); +- tl->lineMetrics = computeLineMetrics(tl->frame, tl->size); ++ tl->lineMetrics = computeLineMetrics(tl->frame, tl->size, tl->isEmpty); + + // and finally copy the UTF-8/UTF-16 conversion tables + tl->u8tou16 = attrstrCopyUTF8ToUTF16(p->String, &(tl->nUTF8)); +@@ -180,6 +185,9 @@ void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y) + backgroundBlock b; + CGAffineTransform textMatrix; + ++ if (tl->isEmpty) ++ return; ++ + CGContextSaveGState(c->c); + // save the text matrix because it's not part of the graphics state + textMatrix = CGContextGetTextMatrix(c->c); +@@ -216,6 +224,8 @@ void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y) + void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height) + { + *width = tl->size.width; ++ if (tl->isEmpty) ++ *width = 0; + *height = tl->size.height; + } + +@@ -233,6 +243,8 @@ void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start + range = CTLineGetStringRange(lr); + *start = tl->u16tou8[range.location]; + *end = tl->u16tou8[range.location + range.length]; ++ if (tl->isEmpty) ++ *start = *end; + } + + void uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m) +@@ -262,14 +274,17 @@ void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *p + *line = i; + + ln = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, i); +- // note: according to the docs, we pass a y of 0 for this since the is the baseline of that line (the point is relative to the line) +- // note: x is relative to the line origin + x -= tl->lineMetrics[*line].X; +- p = CTLineGetStringIndexForPosition(ln, CGPointMake(x, 0)); +- if (p == kCFNotFound) { +- // TODO ++ *pos = 0; ++ if (!tl->isEmpty) { ++ // note: according to the docs, we pass a y of 0 for this since the is the baseline of that line (the point is relative to the line) ++ // note: x is relative to the line origin ++ p = CTLineGetStringIndexForPosition(ln, CGPointMake(x, 0)); ++ if (p == kCFNotFound) { ++ // TODO ++ } ++ *pos = tl->u16tou8[p]; + } +- *pos = tl->u16tou8[p]; + } + + double uiDrawTextLayoutByteLocationInLine(uiDrawTextLayout *tl, size_t pos, int line) +@@ -282,6 +297,9 @@ void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *p + return -1; + lr = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, line); + range = CTLineGetStringRange(lr); ++ // TODO is the behavior of this part correct? ++ if (tl->isEmpty) ++ range.length = 0; + // note: >, not >=, because the position at end is valid! + if (pos < range.location || pos > (range.location + range.length)) + return -1; +diff --git a/darwin/uipriv_darwin.h b/darwin/uipriv_darwin.h +index 0303c32c..d44ee410 100644 +--- a/darwin/uipriv_darwin.h ++++ b/darwin/uipriv_darwin.h +@@ -151,7 +151,7 @@ extern void fontdescFromCTFontDescriptor(CTFontDescriptorRef ctdesc, uiDrawFontD + extern void initUnderlineColors(void); + extern void uninitUnderlineColors(void); + typedef void (^backgroundBlock)(uiDrawContext *c, uiDrawTextLayout *layout, double x, double y); +-extern CFAttributedStringRef attrstrToCoreFoundation(uiDrawTextLayoutParams *p, NSArray **backgroundBlocks); ++extern CFAttributedStringRef attrstrToCoreFoundation(uiDrawTextLayoutParams *p, BOOL *isEmpty, NSArray **backgroundBlocks); + + // aat.m + typedef void (^aatBlock)(uint16_t type, uint16_t selector); diff --git a/_wip/attrstr_metrics/unix_OLD__old_drawtext.c b/_wip/attrstr_metrics/unix_OLD__old_drawtext.c new file mode 100644 index 00000000..7677f076 --- /dev/null +++ b/_wip/attrstr_metrics/unix_OLD__old_drawtext.c @@ -0,0 +1,217 @@ +// 6 september 2015 +#include "uipriv_unix.h" +#include "draw.h" + +struct uiDrawFontFamilies { + PangoFontFamily **f; + int n; +}; + +uiDrawFontFamilies *uiDrawListFontFamilies(void) +{ + uiDrawFontFamilies *ff; + PangoFontMap *map; + + ff = uiNew(uiDrawFontFamilies); + map = pango_cairo_font_map_get_default(); + pango_font_map_list_families(map, &(ff->f), &(ff->n)); + // do not free map; it's a shared resource + return ff; +} + +int uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff) +{ + return ff->n; +} + +char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, int n) +{ + PangoFontFamily *f; + + f = ff->f[n]; + return uiUnixStrdupText(pango_font_family_get_name(f)); +} + +void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff) +{ + g_free(ff->f); + uiFree(ff); +} + +struct uiDrawTextFont { + PangoFont *f; +}; + +uiDrawTextFont *mkTextFont(PangoFont *f, gboolean ref) +{ + uiDrawTextFont *font; + + font = uiNew(uiDrawTextFont); + font->f = f; + if (ref) + g_object_ref(font->f); + return font; +} + + + +PangoFont *pangoDescToPangoFont(PangoFontDescription *pdesc) +{ + PangoFont *f; + PangoContext *context; + + // in this case, the context is necessary for the metrics to be correct + context = mkGenericPangoCairoContext(); + f = pango_font_map_load_font(pango_cairo_font_map_get_default(), context, pdesc); + if (f == NULL) { + // LONGTERM + g_error("[libui] no match in pangoDescToPangoFont(); report to andlabs"); + } + g_object_unref(context); + return f; +} + +uiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc) +{ + PangoFont *f; + PangoFontDescription *pdesc; + + pdesc = pango_font_description_new(); + pango_font_description_set_family(pdesc, + desc->Family); + pango_font_description_set_size(pdesc, + (gint) (desc->Size * PANGO_SCALE)); + pango_font_description_set_weight(pdesc, + pangoWeights[desc->Weight]); + pango_font_description_set_style(pdesc, + pangoItalics[desc->Italic]); + pango_font_description_set_stretch(pdesc, + pangoStretches[desc->Stretch]); + f = pangoDescToPangoFont(pdesc); + pango_font_description_free(pdesc); + return mkTextFont(f, FALSE); // we hold the initial reference; no need to ref +} + +void uiDrawFreeTextFont(uiDrawTextFont *font) +{ + g_object_unref(font->f); + uiFree(font); +} + +uintptr_t uiDrawTextFontHandle(uiDrawTextFont *font) +{ + return (uintptr_t) (font->f); +} + +void uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc) +{ + PangoFontDescription *pdesc; + + // this creates a copy; we free it later + pdesc = pango_font_describe(font->f); + + // TODO + + pango_font_description_free(pdesc); +} + +// See https://developer.gnome.org/pango/1.30/pango-Cairo-Rendering.html#pango-Cairo-Rendering.description +// Note that we convert to double before dividing to make sure the floating-point stuff is right +#define pangoToCairo(pango) (((double) (pango)) / PANGO_SCALE) +#define cairoToPango(cairo) ((gint) ((cairo) * PANGO_SCALE)) + +void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics) +{ + PangoFontMetrics *pm; + + pm = pango_font_get_metrics(font->f, NULL); + metrics->Ascent = pangoToCairo(pango_font_metrics_get_ascent(pm)); + metrics->Descent = pangoToCairo(pango_font_metrics_get_descent(pm)); + // Pango doesn't seem to expose this :( Use 0 and hope for the best. + metrics->Leading = 0; + metrics->UnderlinePos = pangoToCairo(pango_font_metrics_get_underline_position(pm)); + metrics->UnderlineThickness = pangoToCairo(pango_font_metrics_get_underline_thickness(pm)); + pango_font_metrics_unref(pm); +} + +// note: PangoCairoLayouts are tied to a given cairo_t, so we can't store one in this device-independent structure +struct uiDrawTextLayout { + char *s; + ptrdiff_t *graphemes; + PangoFont *defaultFont; + double width; + PangoAttrList *attrs; +}; + +uiDrawTextLayout *uiDrawNewTextLayout(const char *text, uiDrawTextFont *defaultFont, double width) +{ + uiDrawTextLayout *layout; + PangoContext *context; + + layout = uiNew(uiDrawTextLayout); + layout->s = g_strdup(text); + context = mkGenericPangoCairoContext(); + layout->graphemes = graphemes(layout->s, context); + g_object_unref(context); + layout->defaultFont = defaultFont->f; + g_object_ref(layout->defaultFont); // retain a copy + uiDrawTextLayoutSetWidth(layout, width); + layout->attrs = pango_attr_list_new(); + return layout; +} + +void uiDrawFreeTextLayout(uiDrawTextLayout *layout) +{ + pango_attr_list_unref(layout->attrs); + g_object_unref(layout->defaultFont); + uiFree(layout->graphemes); + g_free(layout->s); + uiFree(layout); +} + +void uiDrawTextLayoutSetWidth(uiDrawTextLayout *layout, double width) +{ + layout->width = width; +} + +static void prepareLayout(uiDrawTextLayout *layout, PangoLayout *pl) +{ + // again, this makes a copy + desc = pango_font_describe(layout->defaultFont); + + pango_layout_set_attributes(pl, layout->attrs); +} + +void uiDrawText(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout) +{ + PangoLayout *pl; + + pl = pango_cairo_create_layout(c->cr); +} + +static void addAttr(uiDrawTextLayout *layout, PangoAttribute *attr, int startChar, int endChar) +{ + attr->start_index = layout->graphemes[startChar]; + attr->end_index = layout->graphemes[endChar]; + pango_attr_list_insert(layout->attrs, attr); + // pango_attr_list_insert() takes attr; we don't free it +} + +void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, int startChar, int endChar, double r, double g, double b, double a) +{ + PangoAttribute *attr; + guint16 rr, gg, bb, aa; + + rr = (guint16) (r * 65535); + gg = (guint16) (g * 65535); + bb = (guint16) (b * 65535); + aa = (guint16) (a * 65535); + + attr = pango_attr_foreground_new(rr, gg, bb); + addAttr(layout, attr, startChar, endChar); + + // TODO what if aa == 0? + attr = FUTURE_pango_attr_foreground_alpha_new(aa); + if (attr != NULL) + addAttr(layout, attr, startChar, endChar); +} diff --git a/_wip/attrstr_metrics/unix_OLD_drawtext.c b/_wip/attrstr_metrics/unix_OLD_drawtext.c new file mode 100644 index 00000000..dbd2d709 --- /dev/null +++ b/_wip/attrstr_metrics/unix_OLD_drawtext.c @@ -0,0 +1,227 @@ +// 17 january 2017 +#include "uipriv_unix.h" +#include "draw.h" + +// TODO +// - if the RTL override is at the beginning of a line, the preceding space is included? +// - nLines == 0: mostly works, except the width is wrong if the paragraph alignment is center or right... +// - TODO check whitespace and line bounds + +struct uiDrawTextLayout { + PangoLayout *layout; + GPtrArray *backgroundClosures; + uiDrawTextLayoutLineMetrics *lineMetrics; + // TODO change everything to use this + int nLines; +}; + +// TODO neither these nor the overall extents seem to include trailing whitespace... we need to figure that out too +static void computeLineMetrics(uiDrawTextLayout *tl) +{ + PangoLayoutIter *iter; + PangoLayoutLine *pll; + PangoRectangle lineStartPos, lineExtents; + int i, n; + uiDrawTextLayoutLineMetrics *m; + + n = tl->nLines; // TODO remove this variable + tl->lineMetrics = (uiDrawTextLayoutLineMetrics *) uiAlloc(n * sizeof (uiDrawTextLayoutLineMetrics), "uiDrawTextLayoutLineMetrics[] (text layout)"); + iter = pango_layout_get_iter(tl->layout); + + m = tl->lineMetrics; + for (i = 0; i < n; i++) { + int baselineY; + + // TODO we use this instead of _get_yrange() because of the block of text in that function's description about how line spacing is distributed in Pango; we have to worry about this when we start adding line spacing... + baselineY = pango_layout_iter_get_baseline(iter); + pll = pango_layout_iter_get_line_readonly(iter); + pango_layout_index_to_pos(tl->layout, pll->start_index, &lineStartPos); + pango_layout_line_get_extents(pll, NULL, &lineExtents); + // TODO unref pll? + + // TODO is this correct for RTL glyphs? + m->X = pangoToCairo(lineStartPos.x); + // TODO fix the whole combined not being updated shenanigans in the static build (here because ugh) + m->Y = pangoToCairo(baselineY - PANGO_ASCENT(lineExtents)); + // TODO this does not include the last space if any + m->Width = pangoToCairo(lineExtents.width); + m->Height = pangoToCairo(lineExtents.height); + + m->BaselineY = pangoToCairo(baselineY); + m->Ascent = pangoToCairo(PANGO_ASCENT(lineExtents)); + m->Descent = pangoToCairo(PANGO_DESCENT(lineExtents)); + m->Leading = 0; // TODO + + m->ParagraphSpacingBefore = 0; // TODO + m->LineHeightSpace = 0; // TODO + m->LineSpacing = 0; // TODO + m->ParagraphSpacing = 0; // TODO + + // don't worry about the return value; we're not using this after the last line + pango_layout_iter_next_line(iter); + m++; + } + + pango_layout_iter_free(iter); +} + +uiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *p) +{ + uiDrawTextLayout *tl; + PangoContext *context; + PangoFontDescription *desc; + PangoAttrList *attrs; + int pangoWidth; + + tl = uiNew(uiDrawTextLayout); + + // in this case, the context is necessary to create the layout + // the layout takes a ref on the context so we can unref it afterward + context = mkGenericPangoCairoContext(); + tl->layout = pango_layout_new(context); + g_object_unref(context); + + // this is safe; pango_layout_set_text() copies the string + pango_layout_set_text(tl->layout, uiAttributedStringString(p->String), -1); + + desc = pango_font_description_new(); + pango_font_description_set_family(desc, p->DefaultFont->Family); + pango_font_description_set_style(desc, pangoItalics[p->DefaultFont->Italic]); + // for the most part, pango weights correlate to ours + // the differences: + // - Book — libui: 350, Pango: 380 + // - Ultra Heavy — libui: 950, Pango: 1000 + // TODO figure out what to do about this misalignment + pango_font_description_set_weight(desc, p->DefaultFont->Weight); + pango_font_description_set_stretch(desc, pangoStretches[p->DefaultFont->Stretch]); + // see https://developer.gnome.org/pango/1.30/pango-Fonts.html#pango-font-description-set-size and https://developer.gnome.org/pango/1.30/pango-Glyph-Storage.html#pango-units-from-double + pango_font_description_set_size(desc, pango_units_from_double(p->DefaultFont->Size)); + pango_layout_set_font_description(tl->layout, desc); + // this is safe; the description is copied + pango_font_description_free(desc); + + pangoWidth = cairoToPango(p->Width); + if (p->Width < 0) + pangoWidth = -1; + pango_layout_set_width(tl->layout, pangoWidth); + + pango_layout_set_alignment(tl->layout, pangoAligns[p->Align]); + + attrs = attrstrToPangoAttrList(p, &(tl->backgroundClosures)); + pango_layout_set_attributes(tl->layout, attrs); + pango_attr_list_unref(attrs); + + tl->nLines = pango_layout_get_line_count(tl->layout); + computeLineMetrics(tl); + + return tl; +} + +void uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m) +{ + *m = tl->lineMetrics[line]; +} + +// TODO +#if 0 +{ + PangoLayoutLine *pll; + + pll = pango_layout_get_line_readonly(tl->layout, line); + // TODO unref? +} +#endif + +// note: Pango will not let us place the cursor at the end of a line the same way other OSs do; see https://git.gnome.org/browse/pango/tree/pango/pango-layout.c?id=f4cbd27f4e5bf8490ea411190d41813e14f12165#n4204 +// ideally there'd be a way to say "I don't need this hack; I'm well behaved" but GTK+ 2 and 3 AND Qt 4 and 5 all behave like this, with the behavior seeming to date back to TkTextView, so... +void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *pos, int *line) +{ + int p, trailing; + int i; + + // this is layout-global, so it takes line origins into account + pango_layout_xy_to_index(tl->layout, + cairoToPango(x), cairoToPango(y), + &p, &trailing); + // on a trailing hit, align to the nearest cluster + // fortunately Pango provides that info directly + if (trailing != 0) + p += trailing; + *pos = p; + + for (i = 0; i < tl->nLines; i++) { + double ltop, lbottom; + + ltop = tl->lineMetrics[i].Y; + lbottom = ltop + tl->lineMetrics[i].Height; + // y will already >= ltop at this point since the past lbottom should == ltop + if (y < lbottom) + break; + } + if (i == pango_layout_get_line_count(tl->layout)) + i--; + *line = i; +} + +double uiDrawTextLayoutByteLocationInLine(uiDrawTextLayout *tl, size_t pos, int line) +{ + PangoLayoutLine *pll; + gboolean trailing; + int pangox; + + if (line < 0 || line >= tl->nLines) + return -1; + pll = pango_layout_get_line_readonly(tl->layout, line); + // note: >, not >=, because the position at end is valid! + if (pos < pll->start_index || pos > (pll->start_index + pll->length)) + return -1; + // this behavior seems correct + // there's also PadWrite's TextEditor::GetCaretRect() but that requires state... + // TODO where does this fail? + // TODO optimize everything to avoid calling strlen() + trailing = 0; + if (pos != 0 && pos != strlen(pango_layout_get_text(tl->layout)) && pos == (pll->start_index + pll->length)) { + pos--; + trailing = 1; + } + pango_layout_line_index_to_x(pll, pos, trailing, &pangox); + // TODO unref pll? + // this is relative to the beginning of the line + return pangoToCairo(pangox) + tl->lineMetrics[line].X; +} + +// note: we can't use gtk_render_insertion_cursor() because that doesn't take information about what line to render on +// we'll just recreate what it does +void caretDrawParams(uiDrawContext *c, double height, struct caretDrawParams *p) +{ + GdkColor *color; + GdkRGBA rgba; + gfloat aspectRatio; + gint width, xoff; + + gtk_style_context_get_style(c->style, + "cursor-color", &color, + "cursor-aspect-ratio", &aspectRatio, + NULL); + if (color != NULL) { + p->r = ((double) (color->red)) / 65535.0; + p->g = ((double) (color->green)) / 65535.0; + p->b = ((double) (color->blue)) / 65535.0; + p->a = 1.0; + gdk_color_free(color); + } else { + gtk_style_context_get_color(c->style, GTK_STATE_FLAG_NORMAL, &rgba); + p->r = rgba.red; + p->g = rgba.green; + p->b = rgba.blue; + p->a = rgba.alpha; + } + + // GTK+ itself uses integer arithmetic here; let's do the same + width = height * aspectRatio + 1; + // TODO this is for LTR + xoff = width / 2; + + p->xoff = xoff; + p->width = width; +} diff --git a/_wip/attrstr_metrics/windows_OLD__old_drawtext.cpp b/_wip/attrstr_metrics/windows_OLD__old_drawtext.cpp new file mode 100644 index 00000000..dec0478d --- /dev/null +++ b/_wip/attrstr_metrics/windows_OLD__old_drawtext.cpp @@ -0,0 +1,311 @@ +// 22 december 2015 +#include "uipriv_windows.hpp" +#include "draw.hpp" +// TODO really migrate + +// notes: +// only available in windows 8 and newer: +// - character spacing +// - kerning control +// - justficiation (how could I possibly be making this up?!) +// - vertical text (SERIOUSLY?! WHAT THE ACTUAL FUCK, MICROSOFT?!?!?!? DID YOU NOT THINK ABOUT THIS THE FIRST TIME, TRYING TO IMPROVE THE INTERNATIONALIZATION OF WINDOWS 7?!?!?! bonus: some parts of MSDN even say 8.1 and up only!) + +struct uiDrawFontFamilies { + fontCollection *fc; +}; + +uiDrawFontFamilies *uiDrawListFontFamilies(void) +{ + struct uiDrawFontFamilies *ff; + + ff = uiNew(struct uiDrawFontFamilies); + ff->fc = loadFontCollection(); + return ff; +} + +int uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff) +{ + return ff->fc->fonts->GetFontFamilyCount(); +} + +char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, int n) +{ + IDWriteFontFamily *family; + WCHAR *wname; + char *name; + HRESULT hr; + + hr = ff->fc->fonts->GetFontFamily(n, &family); + if (hr != S_OK) + logHRESULT(L"error getting font out of collection", hr); + wname = fontCollectionFamilyName(ff->fc, family); + name = toUTF8(wname); + uiFree(wname); + family->Release(); + return name; +} + +void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff) +{ + fontCollectionFree(ff->fc); + uiFree(ff); +} + +struct uiDrawTextFont { + IDWriteFont *f; + WCHAR *family; // save for convenience in uiDrawNewTextLayout() + double size; +}; + +uiDrawTextFont *mkTextFont(IDWriteFont *df, BOOL addRef, WCHAR *family, BOOL copyFamily, double size) +{ + uiDrawTextFont *font; + WCHAR *copy; + HRESULT hr; + + font = uiNew(uiDrawTextFont); + font->f = df; + if (addRef) + font->f->AddRef(); + if (copyFamily) { + copy = (WCHAR *) uiAlloc((wcslen(family) + 1) * sizeof (WCHAR), "WCHAR[]"); + wcscpy(copy, family); + font->family = copy; + } else + font->family = family; + font->size = size; + return font; +} + +// TODO MinGW-w64 is missing this one +#define DWRITE_FONT_WEIGHT_SEMI_LIGHT (DWRITE_FONT_WEIGHT(350)) + +uiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc) +{ + uiDrawTextFont *font; + IDWriteFontCollection *collection; + UINT32 index; + BOOL exists; + struct dwriteAttr attr; + IDWriteFontFamily *family; + WCHAR *wfamily; + IDWriteFont *match; + HRESULT hr; + + // always get the latest available font information + hr = dwfactory->GetSystemFontCollection(&collection, TRUE); + if (hr != S_OK) + logHRESULT(L"error getting system font collection", hr); + + wfamily = toUTF16(desc->Family); + hr = collection->FindFamilyName(wfamily, &index, &exists); + if (hr != S_OK) + logHRESULT(L"error finding font family", hr); + if (!exists) + implbug("LONGTERM family not found in uiDrawLoadClosestFont()", hr); + hr = collection->GetFontFamily(index, &family); + if (hr != S_OK) + logHRESULT(L"error loading font family", hr); + + attr.weight = desc->Weight; + attr.italic = desc->Italic; + attr.stretch = desc->Stretch; + attrToDWriteAttr(&attr); + hr = family->GetFirstMatchingFont( + attr.dweight, + attr.dstretch, + attr.ditalic, + &match); + if (hr != S_OK) + logHRESULT(L"error loading font", hr); + + font = mkTextFont(match, + FALSE, // we own the initial reference; no need to add another one + wfamily, FALSE, // will be freed with font + desc->Size); + + family->Release(); + collection->Release(); + + return font; +} + +void uiDrawFreeTextFont(uiDrawTextFont *font) +{ + font->f->Release(); + uiFree(font->family); + uiFree(font); +} + +uintptr_t uiDrawTextFontHandle(uiDrawTextFont *font) +{ + return (uintptr_t) (font->f); +} + +void uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc) +{ + // TODO + + desc->Size = font->size; + + // TODO +} + +// text sizes are 1/72 of an inch +// points in Direct2D are 1/96 of an inch (https://msdn.microsoft.com/en-us/library/windows/desktop/ff684173%28v=vs.85%29.aspx, https://msdn.microsoft.com/en-us/library/windows/desktop/hh447022%28v=vs.85%29.aspx) +// As for the actual conversion from design units, see: +// - http://cboard.cprogramming.com/windows-programming/136733-directwrite-font-height-issues.html +// - https://sourceforge.net/p/vstgui/mailman/message/32483143/ +// - http://xboxforums.create.msdn.com/forums/t/109445.aspx +// - https://msdn.microsoft.com/en-us/library/dd183564%28v=vs.85%29.aspx +// - http://www.fontbureau.com/blog/the-em/ +// TODO make points here about how DIPs in DirectWrite == DIPs in Direct2D; if not, figure out what they really are? for the width and layout functions later +static double scaleUnits(double what, double designUnitsPerEm, double size) +{ + return (what / designUnitsPerEm) * (size * (96.0 / 72.0)); +} + +void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics) +{ + DWRITE_FONT_METRICS dm; + + font->f->GetMetrics(&dm); + metrics->Ascent = scaleUnits(dm.ascent, dm.designUnitsPerEm, font->size); + metrics->Descent = scaleUnits(dm.descent, dm.designUnitsPerEm, font->size); + // TODO what happens if dm.xxx is negative? + // TODO remember what this was for + metrics->Leading = scaleUnits(dm.lineGap, dm.designUnitsPerEm, font->size); + metrics->UnderlinePos = scaleUnits(dm.underlinePosition, dm.designUnitsPerEm, font->size); + metrics->UnderlineThickness = scaleUnits(dm.underlineThickness, dm.designUnitsPerEm, font->size); +} + +// some attributes, such as foreground color, can't be applied until after we establish a Direct2D context :/ so we have to prepare all attributes in advance +// also since there's no way to clear the attributes from a layout en masse (apart from overwriting them all), we'll play it safe by creating a new layout each time +enum layoutAttrType { + layoutAttrColor, +}; + +struct layoutAttr { + enum layoutAttrType type; + int start; + int end; + double components[4]; +}; + +struct uiDrawTextLayout { + WCHAR *text; + size_t textlen; + size_t *graphemes; + double width; + IDWriteTextFormat *format; + std::vector *attrs; +}; + +uiDrawTextLayout *uiDrawNewTextLayout(const char *text, uiDrawTextFont *defaultFont, double width) +{ + uiDrawTextLayout *layout; + HRESULT hr; + + layout = uiNew(uiDrawTextLayout); + + hr = dwfactory->CreateTextFormat(defaultFont->family, + NULL, + defaultFont->f->GetWeight(), + defaultFont->f->GetStyle(), + defaultFont->f->GetStretch(), + + if (hr != S_OK) + logHRESULT(L"error creating IDWriteTextFormat", hr); + + layout->text = toUTF16(text); + layout->textlen = wcslen(layout->text); + layout->graphemes = graphemes(layout->text); + + uiDrawTextLayoutSetWidth(layout, width); + + layout->attrs = new std::vector; + + return layout; +} + +void uiDrawFreeTextLayout(uiDrawTextLayout *layout) +{ + delete layout->attrs; + layout->format->Release(); + uiFree(layout->graphemes); + uiFree(layout->text); + uiFree(layout); +} + + +IDWriteTextLayout *prepareLayout(uiDrawTextLayout *layout, ID2D1RenderTarget *rt) +{ + IDWriteTextLayout *dl; + DWRITE_TEXT_RANGE range; + IUnknown *unkBrush; + HRESULT hr; + + for (const struct layoutAttr &attr : *(layout->attrs)) { + range.startPosition = layout->graphemes[attr.start]; + range.length = layout->graphemes[attr.end] - layout->graphemes[attr.start]; + switch (attr.type) { + case layoutAttrColor: + if (rt == NULL) // determining extents, not drawing + break; + unkBrush = mkSolidBrush(rt, + attr.components[0], + attr.components[1], + attr.components[2], + attr.components[3]); + hr = dl->SetDrawingEffect(unkBrush, range); + unkBrush->Release(); // associated with dl + break; + default: + hr = E_FAIL; + logHRESULT(L"invalid text attribute type", hr); + } + if (hr != S_OK) + logHRESULT(L"error adding attribute to text layout", hr); + } + + + + return dl; +} + + +void uiDrawText(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout) +{ + IDWriteTextLayout *dl; + D2D1_POINT_2F pt; + ID2D1Brush *black; + HRESULT hr; + + // TODO document that fully opaque black is the default text color; figure out whether this is upheld in various scenarios on other platforms + black = mkSolidBrush(c->rt, 0.0, 0.0, 0.0, 1.0); + + dl = prepareLayout(layout, c->rt); + pt.x = x; + pt.y = y; + // TODO D2D1_DRAW_TEXT_OPTIONS_NO_SNAP? + // TODO D2D1_DRAW_TEXT_OPTIONS_CLIP? + // TODO when setting 8.1 as minimum, D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT? + c->rt->DrawTextLayout(pt, dl, black, D2D1_DRAW_TEXT_OPTIONS_NONE); + dl->Release(); + + black->Release(); +} + +void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, int startChar, int endChar, double r, double g, double b, double a) +{ + struct layoutAttr attr; + + attr.type = layoutAttrColor; + attr.start = startChar; + attr.end = endChar; + attr.components[0] = r; + attr.components[1] = g; + attr.components[2] = b; + attr.components[3] = a; + layout->attrs->push_back(attr); +} diff --git a/_wip/attrstr_metrics/windows_OLD_drawtext.cpp b/_wip/attrstr_metrics/windows_OLD_drawtext.cpp new file mode 100644 index 00000000..201d3a1d --- /dev/null +++ b/_wip/attrstr_metrics/windows_OLD_drawtext.cpp @@ -0,0 +1,667 @@ +// 17 january 2017 +#include "uipriv_windows.hpp" +#include "draw.hpp" + +// TODO +// - consider the warnings about antialiasing in the PadWrite sample +// - if that's not a problem, do we have overlapping rects in the hittest sample? I can't tell... +// - empty string: nLines == 1 and all checks out except extents has x == 0 when not left aligned +// - paragraph alignment is subject to RTL mirroring; see if it is on other platforms +// - add overhang info to metrics? + +// TODO verify our renderer is correct, especially with regards to snapping + +struct uiDrawTextLayout { + IDWriteTextFormat *format; + IDWriteTextLayout *layout; + std::vector *backgroundFuncs; + UINT32 nLines; + struct lineInfo *lineInfo; + // for converting DirectWrite indices from/to byte offsets + size_t *u8tou16; + size_t nUTF8; + size_t *u16tou8; + size_t nUTF16; +}; + +// TODO copy notes about DirectWrite DIPs being equal to Direct2D DIPs here + +// typographic points are 1/72 inch; this parameter is 1/96 inch +// fortunately Microsoft does this too, in https://msdn.microsoft.com/en-us/library/windows/desktop/dd371554%28v=vs.85%29.aspx +#define pointSizeToDWriteSize(size) (size * (96.0 / 72.0)) + +struct lineInfo { + size_t startPos; // in UTF-16 points + size_t endPos; + size_t newlineCount; + double x; + double y; + double width; + double height; + double baseline; +}; + +// this function is deeply indebted to the PadWrite sample: https://github.com/Microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/multimedia/DirectWrite/PadWrite/TextEditor.cpp +static void computeLineInfo(uiDrawTextLayout *tl) +{ + DWRITE_LINE_METRICS *dlm; + size_t nextStart; + UINT32 i, j; + DWRITE_HIT_TEST_METRICS *htm; + UINT32 nFragments, unused; + HRESULT hr; + + // TODO make sure this is legal; if not, switch to GetMetrics() and use its line count field instead + hr = tl->layout->GetLineMetrics(NULL, 0, &(tl->nLines)); + // ugh, HRESULT_TO_WIN32() is an inline function and is not constexpr so we can't use switch here + if (hr == S_OK) { + // TODO what do we do here + } else if (hr != E_NOT_SUFFICIENT_BUFFER) + logHRESULT(L"error getting number of lines in IDWriteTextLayout", hr); + tl->lineInfo = (struct lineInfo *) uiAlloc(tl->nLines * sizeof (struct lineInfo), "struct lineInfo[] (text layout)"); + + dlm = new DWRITE_LINE_METRICS[tl->nLines]; + // we can't pass NULL here; it outright crashes if we do + // TODO verify the numbers haven't changed + hr = tl->layout->GetLineMetrics(dlm, tl->nLines, &unused); + if (hr != S_OK) + logHRESULT(L"error getting IDWriteTextLayout line metrics", hr); + + // assume the first line starts at position 0 and the string flow is incremental + nextStart = 0; + for (i = 0; i < tl->nLines; i++) { + tl->lineInfo[i].startPos = nextStart; + tl->lineInfo[i].endPos = nextStart + dlm[i].length; + tl->lineInfo[i].newlineCount = dlm[i].newlineLength; + nextStart = tl->lineInfo[i].endPos; + + // a line can have multiple fragments; for example, if there's a bidirectional override in the middle of a line + hr = tl->layout->HitTestTextRange(tl->lineInfo[i].startPos, (tl->lineInfo[i].endPos - tl->lineInfo[i].newlineCount) - tl->lineInfo[i].startPos, + 0, 0, + NULL, 0, &nFragments); + if (hr != S_OK && hr != E_NOT_SUFFICIENT_BUFFER) + logHRESULT(L"error getting IDWriteTextLayout line fragment count", hr); + htm = new DWRITE_HIT_TEST_METRICS[nFragments]; + // TODO verify unused == nFragments? + hr = tl->layout->HitTestTextRange(tl->lineInfo[i].startPos, (tl->lineInfo[i].endPos - tl->lineInfo[i].newlineCount) - tl->lineInfo[i].startPos, + 0, 0, + htm, nFragments, &unused); + // TODO can this return E_NOT_SUFFICIENT_BUFFER again? + if (hr != S_OK) + logHRESULT(L"error getting IDWriteTextLayout line fragment metrics", hr); + // TODO verify htm.textPosition and htm.length against dtm[i]/tl->lineInfo[i]? + tl->lineInfo[i].x = htm[0].left; + tl->lineInfo[i].y = htm[0].top; + // TODO does this not include trailing whitespace? I forget + tl->lineInfo[i].width = htm[0].width; + tl->lineInfo[i].height = htm[0].height; + for (j = 1; j < nFragments; j++) { + // this is correct even if the leftmost fragment on the line is RTL + if (tl->lineInfo[i].x > htm[j].left) + tl->lineInfo[i].x = htm[j].left; + tl->lineInfo[i].width += htm[j].width; + // TODO verify y and height haven't changed? + } + // TODO verify dlm[i].height == htm.height? + delete[] htm; + + // TODO on Windows 8.1 and/or 10 we can use DWRITE_LINE_METRICS1 to get specific info about the ascent and descent; do we have an alternative? + // TODO and even on those platforms can we somehow split tyographic leading from spacing? + // TODO and on that note, can we have both line spacing proportionally above and uniformly below? + tl->lineInfo[i].baseline = dlm[i].baseline; + } + + delete[] dlm; +} + +// TODO should be const but then I can't operator[] on it; the real solution is to find a way to do designated array initializers in C++11 but I do not know enough C++ voodoo to make it work (it is possible but no one else has actually done it before) +static std::map dwriteAligns = { + { uiDrawTextAlignLeft, DWRITE_TEXT_ALIGNMENT_LEADING }, + { uiDrawTextAlignCenter, DWRITE_TEXT_ALIGNMENT_CENTER }, + { uiDrawTextAlignRight, DWRITE_TEXT_ALIGNMENT_TRAILING }, +}; + +uiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *p) +{ + uiDrawTextLayout *tl; + WCHAR *wDefaultFamily; + DWRITE_WORD_WRAPPING wrap; + FLOAT maxWidth; + HRESULT hr; + + tl = uiNew(uiDrawTextLayout); + + wDefaultFamily = toUTF16(p->DefaultFont->Family); + hr = dwfactory->CreateTextFormat( + wDefaultFamily, NULL, + uiprivWeightToDWriteWeight(p->DefaultFont->Weight), + uiprivItalicToDWriteStyle(p->DefaultFont->Italic), + uiprivStretchToDWriteStretch(p->DefaultFont->Stretch), + pointSizeToDWriteSize(p->DefaultFont->Size), + // see http://stackoverflow.com/questions/28397971/idwritefactorycreatetextformat-failing and https://msdn.microsoft.com/en-us/library/windows/desktop/dd368203.aspx + // TODO use the current locale? + L"", + &(tl->format)); + if (hr != S_OK) + logHRESULT(L"error creating IDWriteTextFormat", hr); + hr = tl->format->SetTextAlignment(dwriteAligns[p->Align]); + if (hr != S_OK) + logHRESULT(L"error applying text layout alignment", hr); + + hr = dwfactory->CreateTextLayout( + (const WCHAR *) attrstrUTF16(p->String), attrstrUTF16Len(p->String), + tl->format, + // FLOAT is float, not double, so this should work... TODO + FLT_MAX, FLT_MAX, + &(tl->layout)); + if (hr != S_OK) + logHRESULT(L"error creating IDWriteTextLayout", hr); + + // and set the width + // this is the only wrapping mode (apart from "no wrap") available prior to Windows 8.1 (TODO verify this fact) (TODO this should be the default anyway) + wrap = DWRITE_WORD_WRAPPING_WRAP; + maxWidth = (FLOAT) (p->Width); + if (p->Width < 0) { + // TODO is this wrapping juggling even necessary? + wrap = DWRITE_WORD_WRAPPING_NO_WRAP; + // setting the max width in this case technically isn't needed since the wrap mode will simply ignore the max width, but let's do it just to be safe + maxWidth = FLT_MAX; // see TODO above + } + hr = tl->layout->SetWordWrapping(wrap); + if (hr != S_OK) + logHRESULT(L"error setting IDWriteTextLayout word wrapping mode", hr); + hr = tl->layout->SetMaxWidth(maxWidth); + if (hr != S_OK) + logHRESULT(L"error setting IDWriteTextLayout max layout width", hr); + + attrstrToIDWriteTextLayoutAttrs(p, tl->layout, &(tl->backgroundFuncs)); + + computeLineInfo(tl); + + // and finally copy the UTF-8/UTF-16 index conversion tables + tl->u8tou16 = attrstrCopyUTF8ToUTF16(p->String, &(tl->nUTF8)); + tl->u16tou8 = attrstrCopyUTF16ToUTF8(p->String, &(tl->nUTF16)); + + // TODO can/should this be moved elsewhere? + uiFree(wDefaultFamily); + return tl; +} + +void uiDrawFreeTextLayout(uiDrawTextLayout *tl) +{ + uiFree(tl->u16tou8); + uiFree(tl->u8tou16); + uiFree(tl->lineInfo); + delete tl->backgroundFuncs; + tl->layout->Release(); + tl->format->Release(); + uiFree(tl); +} + +static HRESULT mkSolidBrush(ID2D1RenderTarget *rt, double r, double g, double b, double a, ID2D1SolidColorBrush **brush) +{ + D2D1_BRUSH_PROPERTIES props; + D2D1_COLOR_F color; + + ZeroMemory(&props, sizeof (D2D1_BRUSH_PROPERTIES)); + props.opacity = 1.0; + // identity matrix + props.transform._11 = 1; + props.transform._22 = 1; + color.r = r; + color.g = g; + color.b = b; + color.a = a; + return rt->CreateSolidColorBrush( + &color, + &props, + brush); +} + +static ID2D1SolidColorBrush *mustMakeSolidBrush(ID2D1RenderTarget *rt, double r, double g, double b, double a) +{ + ID2D1SolidColorBrush *brush; + HRESULT hr; + + hr = mkSolidBrush(rt, r, g, b, a, &brush); + if (hr != S_OK) + logHRESULT(L"error creating solid brush", hr); + return brush; +} + +// some of the stuff we want to do isn't possible with what DirectWrite provides itself; we need to do it ourselves +// this is based on http://www.charlespetzold.com/blog/2014/01/Character-Formatting-Extensions-with-DirectWrite.html +class textRenderer : public IDWriteTextRenderer { + ULONG refcount; + ID2D1RenderTarget *rt; + BOOL snap; + ID2D1SolidColorBrush *black; +public: + textRenderer(ID2D1RenderTarget *rt, BOOL snap, ID2D1SolidColorBrush *black) + { + this->refcount = 1; + this->rt = rt; + this->snap = snap; + this->black = black; + } + + // IUnknown + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) + { + if (ppvObject == NULL) + return E_POINTER; + if (riid == IID_IUnknown || + riid == __uuidof (IDWritePixelSnapping) || + riid == __uuidof (IDWriteTextRenderer)) { + this->AddRef(); + *ppvObject = this; + return S_OK; + } + *ppvObject = NULL; + return E_NOINTERFACE; + } + + virtual ULONG STDMETHODCALLTYPE AddRef(void) + { + this->refcount++; + return this->refcount; + } + + virtual ULONG STDMETHODCALLTYPE Release(void) + { + this->refcount--; + if (this->refcount == 0) { + delete this; + return 0; + } + return this->refcount; + } + + // IDWritePixelSnapping + virtual HRESULT STDMETHODCALLTYPE GetCurrentTransform(void *clientDrawingContext, DWRITE_MATRIX *transform) + { + D2D1_MATRIX_3X2_F d2dtf; + + if (transform == NULL) + return E_POINTER; + this->rt->GetTransform(&d2dtf); + transform->m11 = d2dtf._11; + transform->m12 = d2dtf._12; + transform->m21 = d2dtf._21; + transform->m22 = d2dtf._22; + transform->dx = d2dtf._31; + transform->dy = d2dtf._32; + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE GetPixelsPerDip(void *clientDrawingContext, FLOAT *pixelsPerDip) + { + FLOAT dpix, dpiy; + + if (pixelsPerDip == NULL) + return E_POINTER; + this->rt->GetDpi(&dpix, &dpiy); + *pixelsPerDip = dpix / 96; + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE IsPixelSnappingDisabled(void *clientDrawingContext, BOOL *isDisabled) + { + if (isDisabled == NULL) + return E_POINTER; + *isDisabled = !this->snap; + return S_OK; + } + + // IDWriteTextRenderer + virtual HRESULT STDMETHODCALLTYPE DrawGlyphRun(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, DWRITE_MEASURING_MODE measuringMode, const DWRITE_GLYPH_RUN *glyphRun, const DWRITE_GLYPH_RUN_DESCRIPTION *glyphRunDescription, IUnknown *clientDrawingEffect) + { + D2D1_POINT_2F baseline; + textDrawingEffect *t = (textDrawingEffect *) clientDrawingEffect; + ID2D1SolidColorBrush *brush; + + baseline.x = baselineOriginX; + baseline.y = baselineOriginY; + brush = this->black; + if (t != NULL && t->hasColor) { + HRESULT hr; + + hr = mkSolidBrush(this->rt, t->r, t->g, t->b, t->a, &brush); + if (hr != S_OK) + return hr; + } + this->rt->DrawGlyphRun( + baseline, + glyphRun, + brush, + measuringMode); + if (t != NULL && t->hasColor) + brush->Release(); + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE DrawInlineObject(void *clientDrawingContext, FLOAT originX, FLOAT originY, IDWriteInlineObject *inlineObject, BOOL isSideways, BOOL isRightToLeft, IUnknown *clientDrawingEffect) + { + if (inlineObject == NULL) + return E_POINTER; + return inlineObject->Draw(clientDrawingContext, this, + originX, originY, + isSideways, isRightToLeft, + clientDrawingEffect); + } + + virtual HRESULT STDMETHODCALLTYPE DrawStrikethrough(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, const DWRITE_STRIKETHROUGH *strikethrough, IUnknown *clientDrawingEffect) + { + // we don't support strikethrough + return E_UNEXPECTED; + } + + virtual HRESULT STDMETHODCALLTYPE DrawUnderline(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, const DWRITE_UNDERLINE *underline, IUnknown *clientDrawingEffect) + { + textDrawingEffect *t = (textDrawingEffect *) clientDrawingEffect; + ID2D1SolidColorBrush *brush; + D2D1_RECT_F rect; + D2D1::Matrix3x2F pixeltf; + FLOAT dpix, dpiy; + D2D1_POINT_2F pt; + + if (underline == NULL) + return E_POINTER; + if (t == NULL) // we can only get here through an underline + return E_UNEXPECTED; + brush = this->black; + if (t->hasUnderlineColor) { + HRESULT hr; + + hr = mkSolidBrush(this->rt, t->ur, t->ug, t->ub, t->ua, &brush); + if (hr != S_OK) + return hr; + } else if (t->hasColor) { + // TODO formalize this rule + HRESULT hr; + + hr = mkSolidBrush(this->rt, t->r, t->g, t->b, t->a, &brush); + if (hr != S_OK) + return hr; + } + rect.left = baselineOriginX; + rect.top = baselineOriginY + underline->offset; + rect.right = rect.left + underline->width; + rect.bottom = rect.top + underline->thickness; + switch (t->u) { + case uiDrawUnderlineStyleSingle: + this->rt->FillRectangle(&rect, brush); + break; + case uiDrawUnderlineStyleDouble: + // TODO do any of the matrix methods return errors? + // TODO standardize double-underline shape across platforms? wavy underline shape? + this->rt->GetTransform(&pixeltf); + this->rt->GetDpi(&dpix, &dpiy); + pixeltf = pixeltf * D2D1::Matrix3x2F::Scale(dpix / 96, dpiy / 96); + pt.x = 0; + pt.y = rect.top; + pt = pixeltf.TransformPoint(pt); + rect.top = (FLOAT) ((int) (pt.y + 0.5)); + pixeltf.Invert(); + pt = pixeltf.TransformPoint(pt); + rect.top = pt.y; + // first line + rect.top -= underline->thickness; + // and it seems we need to recompute this + rect.bottom = rect.top + underline->thickness; + this->rt->FillRectangle(&rect, brush); + // second line + rect.top += 2 * underline->thickness; + rect.bottom = rect.top + underline->thickness; + this->rt->FillRectangle(&rect, brush); + break; + case uiDrawUnderlineStyleSuggestion: + { // TODO get rid of the extra block + // TODO properly clean resources on failure + // TODO use fully qualified C overloads for all methods + // TODO ensure all methods properly have errors handled + ID2D1PathGeometry *path; + ID2D1GeometrySink *sink; + double amplitude, period, xOffset, yOffset; + double t; + bool first = true; + HRESULT hr; + + hr = d2dfactory->CreatePathGeometry(&path); + if (hr != S_OK) + return hr; + hr = path->Open(&sink); + if (hr != S_OK) + return hr; + amplitude = underline->thickness; + period = 5 * underline->thickness; + xOffset = baselineOriginX; + yOffset = baselineOriginY + underline->offset; + for (t = 0; t < underline->width; t++) { + double x, angle, y; + D2D1_POINT_2F pt; + + x = t + xOffset; + angle = 2 * uiPi * fmod(x, period) / period; + y = amplitude * sin(angle) + yOffset; + pt.x = x; + pt.y = y; + if (first) { + sink->BeginFigure(pt, D2D1_FIGURE_BEGIN_HOLLOW); + first = false; + } else + sink->AddLine(pt); + } + sink->EndFigure(D2D1_FIGURE_END_OPEN); + hr = sink->Close(); + if (hr != S_OK) + return hr; + sink->Release(); + this->rt->DrawGeometry(path, brush, underline->thickness); + path->Release(); + } + break; + } + if (t->hasUnderlineColor || t->hasColor) + brush->Release(); + return S_OK; + } +}; + +// TODO this ignores clipping? +void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y) +{ + D2D1_POINT_2F pt; + ID2D1SolidColorBrush *black; + textRenderer *renderer; + HRESULT hr; + + for (const auto &f : *(tl->backgroundFuncs)) + f(c, tl, x, y); + + // TODO document that fully opaque black is the default text color; figure out whether this is upheld in various scenarios on other platforms + // TODO figure out if this needs to be cleaned out + black = mustMakeSolidBrush(c->rt, 0.0, 0.0, 0.0, 1.0); + +#define renderD2D 0 +#define renderOur 1 +#if renderD2D + pt.x = x; + pt.y = y; + // TODO D2D1_DRAW_TEXT_OPTIONS_NO_SNAP? + // TODO D2D1_DRAW_TEXT_OPTIONS_CLIP? + // TODO LONGTERM when setting 8.1 as minimum (TODO verify), D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT? + // TODO what is our pixel snapping setting related to the OPTIONS enum values? + c->rt->DrawTextLayout(pt, tl->layout, black, D2D1_DRAW_TEXT_OPTIONS_NONE); +#endif +#if renderD2D && renderOur + // draw ours semitransparent so we can check + // TODO get the actual color Charles Petzold uses and use that + black->Release(); + black = mustMakeSolidBrush(c->rt, 1.0, 0.0, 0.0, 0.75); +#endif +#if renderOur + renderer = new textRenderer(c->rt, + TRUE, // TODO FALSE for no-snap? + black); + hr = tl->layout->Draw(NULL, + renderer, + x, y); + if (hr != S_OK) + logHRESULT(L"error drawing IDWriteTextLayout", hr); + renderer->Release(); +#endif + + black->Release(); +} + +// TODO for a single line the height includes the leading; should it? TextEdit on OS X always includes the leading and/or paragraph spacing, otherwise Klee won't work... +// TODO width does not include trailing whitespace +void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height) +{ + DWRITE_TEXT_METRICS metrics; + HRESULT hr; + + hr = tl->layout->GetMetrics(&metrics); + if (hr != S_OK) + logHRESULT(L"error getting IDWriteTextLayout layout metrics", hr); + *width = metrics.width; + // TODO make sure the behavior of this on empty strings is the same on all platforms (ideally should be 0-width, line height-height; TODO note this in the docs too) + *height = metrics.height; +} + +int uiDrawTextLayoutNumLines(uiDrawTextLayout *tl) +{ + return tl->nLines; +} + +// DirectWrite doesn't provide a direct way to do this, so we have to do this manually +// TODO does that comment still apply here or to the code at the top of this file? +void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end) +{ + *start = tl->lineInfo[line].startPos; + *start = tl->u16tou8[*start]; + *end = tl->lineInfo[line].endPos - tl->lineInfo[line].newlineCount; + *end = tl->u16tou8[*end]; +} + +void uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m) +{ + m->X = tl->lineInfo[line].x; + m->Y = tl->lineInfo[line].y; + m->Width = tl->lineInfo[line].width; + m->Height = tl->lineInfo[line].height; + + // TODO rename tl->lineInfo[line].baseline to .baselineOffset or something of the sort to make its meaning more clear + m->BaselineY = tl->lineInfo[line].y + tl->lineInfo[line].baseline; + m->Ascent = tl->lineInfo[line].baseline; + m->Descent = tl->lineInfo[line].height - tl->lineInfo[line].baseline; + m->Leading = 0; // TODO + + m->ParagraphSpacingBefore = 0; // TODO + m->LineHeightSpace = 0; // TODO + m->LineSpacing = 0; // TODO + m->ParagraphSpacing = 0; // TODO +} + +// this algorithm comes from Microsoft's PadWrite sample, following TextEditor::SetSelectionFromPoint() +// TODO go back through all of these and make sure we convert coordinates properly +// TODO same for OS X +void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *pos, int *line) +{ + DWRITE_HIT_TEST_METRICS m; + BOOL trailing, inside; + size_t p; + UINT32 i; + HRESULT hr; + + hr = tl->layout->HitTestPoint(x, y, + &trailing, &inside, + &m); + if (hr != S_OK) + logHRESULT(L"error hit-testing IDWriteTextLayout", hr); + p = m.textPosition; + // on a trailing hit, align to the nearest cluster + if (trailing) { + DWRITE_HIT_TEST_METRICS m2; + FLOAT x, y; // crashes if I skip these :/ + + hr = tl->layout->HitTestTextPosition(m.textPosition, trailing, + &x, &y, &m2); + if (hr != S_OK) + logHRESULT(L"error aligning trailing hit to nearest cluster", hr); + p = m2.textPosition + m2.length; + } + *pos = tl->u16tou8[p]; + + for (i = 0; i < tl->nLines; i++) { + double ltop, lbottom; + + ltop = tl->lineInfo[i].y; + lbottom = ltop + tl->lineInfo[i].height; + // y will already >= ltop at this point since the past lbottom should == ltop + if (y < lbottom) + break; + } + if (i == tl->nLines) + i--; + *line = i; +} + +double uiDrawTextLayoutByteLocationInLine(uiDrawTextLayout *tl, size_t pos, int line) +{ + BOOL trailing; + DWRITE_HIT_TEST_METRICS m; + FLOAT x, y; + HRESULT hr; + + if (line < 0 || line >= tl->nLines) + return -1; + pos = tl->u8tou16[pos]; + // note: >, not >=, because the position at endPos is valid! + if (pos < tl->lineInfo[line].startPos || pos > tl->lineInfo[line].endPos) + return -1; + // this behavior seems correct + // there's also PadWrite's TextEditor::GetCaretRect() but that requires state... + // TODO where does this fail? + trailing = FALSE; + if (pos != 0 && pos != tl->nUTF16 && pos == tl->lineInfo[line].endPos) { + pos--; + trailing = TRUE; + } + hr = tl->layout->HitTestTextPosition(pos, trailing, + &x, &y, &m); + if (hr != S_OK) + logHRESULT(L"error calling IDWriteTextLayout::HitTestTextPosition()", hr); + return x; +} + +void caretDrawParams(uiDrawContext *c, double height, struct caretDrawParams *p) +{ + DWORD caretWidth; + + // there seems to be no defined caret color + // the best I can come up with is "inverts colors underneath" (according to https://msdn.microsoft.com/en-us/library/windows/desktop/ms648397(v=vs.85).aspx) which I have no idea how to do (TODO) + // just return black for now + p->r = 0.0; + p->g = 0.0; + p->b = 0.0; + p->a = 1.0; + + if (SystemParametersInfoW(SPI_GETCARETWIDTH, 0, &caretWidth, 0) == 0) + // don't log the failure, fall back gracefully + // the instruction to use this comes from https://msdn.microsoft.com/en-us/library/windows/desktop/ms648399(v=vs.85).aspx + // and we have to assume GetSystemMetrics() always succeeds, so + caretWidth = GetSystemMetrics(SM_CXBORDER); + // TODO make this a function and split it out of areautil.cpp + { + FLOAT dpix, dpiy; + + // TODO can we pass NULL for dpiy? + c->rt->GetDpi(&dpix, &dpiy); + // see https://msdn.microsoft.com/en-us/library/windows/desktop/dd756649%28v=vs.85%29.aspx (and others; search "direct2d mouse") + p->width = ((double) (caretWidth * 96)) / dpix; + } + // and there doesn't seem to be this either... (TODO check what PadWrite does?) + p->xoff = 0; +} diff --git a/_wip/custtvattr b/_wip/custtvattr deleted file mode 100755 index d782e12f..00000000 --- a/_wip/custtvattr +++ /dev/null @@ -1,91 +0,0 @@ -NSScrollView - backgroundColor NSCalibratedWhiteColorSpace 1 1 - drawsBackground 1 - borderType 2 - documentCursor - hasHorizontalScroller 0 - hasVerticalScroller 1 - autohidesScrollers 0 - hasHorizontalRuler 0 - hasVerticalRuler 0 - rulersVisible 0 - automaticallyAdjustsContentInsets 1 - contentInsets left:0 top:0 right:0 bottom:0 - scrollerInsets left:0 top:0 right:0 bottom:0 - scrollerKnobStyle 0 - scrollerStyle 1 - lineScroll 10 - horizontalLineScroll 10 - verticalLineScroll 10 - pageScroll 10 - horizontalPageScroll 10 - verticalPageScroll 10 - scrollsDynamically 1 - findBarPosition 1 - usesPredominantAxisScrolling 0 - horizontalScrollElasticity 0 - verticalScrollElasticity 0 - allowsMagnification 0 - magnification 1 - maxMagnification 4 - minMagnification 0.25 - -NSTextView - textContainerInset {0, 0} - textContainerOrigin {0, 0} - backgroundColor NSCalibratedWhiteColorSpace 1 1 - drawsBackground 1 - allowsDocumentBackgroundColorChange 0 - allowedInputSourceLocales (null) - allowsUndo 1 - isEditable 1 - isSelectable 1 - isFieldEditor 0 - isRichText 0 - importsGraphics 0 - defaultParagraphStyle (null) - allowsImageEditing 0 - isAutomaticQuoteSubstitutionEnabled 1 - isAutomaticLinkDetectionEnabled 0 - displaysLinkToolTips 1 - usesRuler 0 - isRulerVisible 0 - usesInspectorBar 0 - selectionAffinity 1 - selectionGranularity 0 - insertionPointColor NSNamedColorSpace System controlTextColor - selectedTextAttributes { - NSBackgroundColor = "NSNamedColorSpace System selectedTextBackgroundColor"; - NSColor = "NSNamedColorSpace System selectedTextColor"; -} - markedTextAttributes { - NSBackgroundColor = "NSCalibratedRGBColorSpace 1 0.866667 0.333333 1"; -} - linkTextAttributes { - NSColor = "NSCalibratedRGBColorSpace 0 0 1 1"; - NSCursor = ""; - NSUnderline = 1; -} - typingAttributes (null) - smartInsertDeleteEnabled 0 - isContinuousSpellCheckingEnabled 1 - isGrammarCheckingEnabled 0 - acceptsGlyphInfo 0 - usesFontPanel 0 - usesFindPanel 1 - enabledTextCheckingTypes 963 - isAutomaticDashSubstitutionEnabled 1 - isAutomaticDataDetectionEnabled 0 - isAutomaticSpellingCorrectionEnabled 1 - isAutomaticTextReplacementEnabled 1 - usesFindBar 0 - isIncrementalSearchingEnabled 0 - NSText: - font (null) - textColor (null) - baseWritingDirection -1 - maxSize {463, 10000000} - minSize {238, 133} - isVerticallyResizable 1 - isHorizontallyResizable 0 - diff --git a/_wip/deftvattr b/_wip/deftvattr deleted file mode 100755 index 2c79c899..00000000 --- a/_wip/deftvattr +++ /dev/null @@ -1,91 +0,0 @@ -NSScrollView - backgroundColor NSCalibratedWhiteColorSpace 1 1 - drawsBackground 1 - borderType 2 - documentCursor - hasHorizontalScroller 0 - hasVerticalScroller 1 - autohidesScrollers 0 - hasHorizontalRuler 0 - hasVerticalRuler 0 - rulersVisible 0 - automaticallyAdjustsContentInsets 1 - contentInsets left:0 top:0 right:0 bottom:0 - scrollerInsets left:0 top:0 right:0 bottom:0 - scrollerKnobStyle 0 - scrollerStyle 1 - lineScroll 10 - horizontalLineScroll 10 - verticalLineScroll 10 - pageScroll 10 - horizontalPageScroll 10 - verticalPageScroll 10 - scrollsDynamically 1 - findBarPosition 1 - usesPredominantAxisScrolling 0 - horizontalScrollElasticity 0 - verticalScrollElasticity 0 - allowsMagnification 0 - magnification 1 - maxMagnification 4 - minMagnification 0.25 - -NSTextView - textContainerInset {0, 0} - textContainerOrigin {0, 0} - backgroundColor NSCalibratedWhiteColorSpace 1 1 - drawsBackground 1 - allowsDocumentBackgroundColorChange 0 - allowedInputSourceLocales (null) - allowsUndo 1 - isEditable 1 - isSelectable 1 - isFieldEditor 0 - isRichText 1 - importsGraphics 0 - defaultParagraphStyle (null) - allowsImageEditing 0 - isAutomaticQuoteSubstitutionEnabled 1 - isAutomaticLinkDetectionEnabled 0 - displaysLinkToolTips 1 - usesRuler 1 - isRulerVisible 0 - usesInspectorBar 0 - selectionAffinity 0 - selectionGranularity 0 - insertionPointColor NSNamedColorSpace System controlTextColor - selectedTextAttributes { - NSBackgroundColor = "NSNamedColorSpace System selectedTextBackgroundColor"; - NSColor = "NSNamedColorSpace System selectedTextColor"; -} - markedTextAttributes { - NSBackgroundColor = "NSCalibratedRGBColorSpace 1 0.866667 0.333333 1"; -} - linkTextAttributes { - NSColor = "NSCalibratedRGBColorSpace 0 0 1 1"; - NSCursor = ""; - NSUnderline = 1; -} - typingAttributes (null) - smartInsertDeleteEnabled 1 - isContinuousSpellCheckingEnabled 1 - isGrammarCheckingEnabled 0 - acceptsGlyphInfo 1 - usesFontPanel 1 - usesFindPanel 1 - enabledTextCheckingTypes 963 - isAutomaticDashSubstitutionEnabled 1 - isAutomaticDataDetectionEnabled 0 - isAutomaticSpellingCorrectionEnabled 1 - isAutomaticTextReplacementEnabled 1 - usesFindBar 0 - isIncrementalSearchingEnabled 0 - NSText: - font (null) - textColor (null) - baseWritingDirection -1 - maxSize {463, 10000000} - minSize {238, 133} - isVerticallyResizable 1 - isHorizontallyResizable 0 - diff --git a/_wip/examples_drawtext/attributes.c b/_wip/examples_drawtext/attributes.c new file mode 100644 index 00000000..ed3cb973 --- /dev/null +++ b/_wip/examples_drawtext/attributes.c @@ -0,0 +1,912 @@ +// 11 february 2017 +#include "drawtext.h" + +static uiAttributedString *attrstr; + +#define nFeatures 256 +static uiOpenTypeFeatures *features[nFeatures]; +static int curFeature = 0; + +static uiOpenTypeFeatures *addFeature(const char tag[4], uint32_t value) +{ + uiOpenTypeFeatures *otf; + + if (curFeature >= nFeatures) { + fprintf(stderr, "TODO (also TODO is there a panic function?)\n"); + exit(EXIT_FAILURE); + } + otf = uiNewOpenTypeFeatures(); + uiOpenTypeFeaturesAdd(otf, tag[0], tag[1], tag[2], tag[3], value); + features[curFeature] = otf; + curFeature++; + return otf; +} + +// some of these examples come from Microsoft's and Apple's lists of typographic features and also https://www.fontfont.com/staticcontent/downloads/FF_OT_User_Guide.pdf +static void setupAttributedString(void) +{ + uiAttributeSpec spec; + size_t start, end; + const char *next; + uiOpenTypeFeatures *otf; + int i; + + attrstr = uiNewAttributedString("uiAttributedString isn't just for plain text! It supports "); + + next = "multiple fonts"; + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFamily; + spec.Family = "Courier New"; + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "multiple sizes"; + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeSize; + spec.Double = 18; + uiAttributedStringSetAttribute(attrstr, + &spec, start, end); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "multiple weights"; + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeWeight; + spec.Value = (uintptr_t) uiDrawTextWeightBold; + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "multiple italics"; + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeItalic; + spec.Value = (uintptr_t) uiDrawTextItalicItalic; + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "multiple stretches"; + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeStretch; + spec.Value = (uintptr_t) uiDrawTextStretchCondensed; + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "multiple colors"; + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeColor; + // Direct2D "Crimson" (#DC143C) + spec.R = 0.8627450980392156; + spec.G = 0.0784313725490196; + spec.B = 0.2352941176470588; + spec.A = 0.75; + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "multiple backgrounds"; + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeBackground; + // Direct2D "Peach Puff" (#FFDAB9) + // TODO choose a darker color + spec.R = 1.0; + spec.G = 0.85490196078431372; + spec.B = 0.7254901960784313; + spec.A = 0.5; + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "multiple"; + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeUnderline; + spec.Value = uiDrawUnderlineStyleSingle; + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, " "); + next = "underlines"; + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeUnderline; + spec.Value = uiDrawUnderlineStyleDouble; + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + spec.Type = uiAttributeUnderlineColor; + spec.Value = uiDrawUnderlineColorCustom; + spec.R = 0.5; + spec.G = 0.0; + spec.B = 1.0; + spec.A = 1.0; + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, " ("); + next = "including underlines for spelling correction and the like"; + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeUnderline; + spec.Value = uiDrawUnderlineStyleSuggestion; + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + spec.Type = uiAttributeUnderlineColor; + spec.Value = uiDrawUnderlineColorSpelling; + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + // TODO randomize these ranges better + // TODO make some overlap to test that + // TODO also change colors to light foreground dark background + next = "or any combination of the above"; + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeWeight; + spec.Value = (uintptr_t) uiDrawTextWeightBold; + uiAttributedStringSetAttribute(attrstr, &spec, start, end - 8); + spec.Type = uiAttributeItalic; + spec.Value = (uintptr_t) uiDrawTextItalicItalic; + uiAttributedStringSetAttribute(attrstr, &spec, start + 3, end - 4); + spec.Type = uiAttributeColor; + spec.R = 0.8627450980392156; + spec.G = 0.0784313725490196; + spec.B = 0.2352941176470588; + spec.A = 0.75; + uiAttributedStringSetAttribute(attrstr, &spec, start + 12, end); + spec.Type = uiAttributeFamily; + spec.Family = "Helvetica"; + uiAttributedStringSetAttribute(attrstr, &spec, start + 8, end - 1); + spec.Type = uiAttributeBackground; + spec.R = 1.0; + spec.G = 0.85490196078431372; + spec.B = 0.7254901960784313; + spec.A = 0.5; + uiAttributedStringSetAttribute(attrstr, &spec, start + 5, end - 7); + spec.Type = uiAttributeUnderline; + spec.Value = uiDrawUnderlineStyleSingle; + uiAttributedStringSetAttribute(attrstr, &spec, start + 9, end - 1); + + // TODO rewrite this to talk about OpenTpe instead + // TODO also shorten this to something more useful and that covers the general gist of things (and combines features arbitrarily like the previous demo) when we add a general OpenType demo (see the last TODO in this function) + uiAttributedStringAppendUnattributed(attrstr, ". In addition, a variety of typographical features are available (depending on the chosen font) that can be switched on (or off, if the font enables them by default): "); + + next = "fi"; + uiAttributedStringAppendUnattributed(attrstr, "standard ligatures like f+i ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("liga", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + // note the use of LTR marks and RTL embeds to make sure the bidi algorithm doesn't kick in for our demonstration (it will produce incorrect results) + // see also: https://www.w3.org/International/articles/inline-bidi-markup/#nomarkup + next = "\xD9\x84\xD8\xA7"; + uiAttributedStringAppendUnattributed(attrstr, "required ligatures like \xE2\x80\xAB\xD9\x84\xE2\x80\xAC+\xE2\x80\xAB\xD8\xA7\xE2\x80\xAC (\xE2\x80\x8E\xE2\x80\xAB"); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("rlig", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, "\xE2\x80\xAC)"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "ct"; + uiAttributedStringAppendUnattributed(attrstr, "discretionary/rare ligatures like c+t ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("dlig", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "the"; + uiAttributedStringAppendUnattributed(attrstr, "contextual ligatures like h+e in the ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("clig", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + otf = addFeature("hlig", 1); + // This technically isn't what is meant by "historical ligatures", but Core Text's internal AAT-to-OpenType mapping says to include it, so we include it too + uiOpenTypeFeaturesAdd(otf, 'h', 'i', 's', 't', 1); + next = "\xC3\x9F"; + uiAttributedStringAppendUnattributed(attrstr, "historical ligatures like the decomposition of \xC3\x9F ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = otf; + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + // TODO a different word than "writing"? + next = "UnICasE wRITInG"; + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("unic", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "316"; + uiAttributedStringAppendUnattributed(attrstr, "proportional ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("pnum", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ") and tabular/monospaced ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("tnum", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ") numbers"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "123"; + uiAttributedStringAppendUnattributed(attrstr, "superscipts ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("sups", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "123"; + uiAttributedStringAppendUnattributed(attrstr, "subscripts ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("subs", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "1st"; + uiAttributedStringAppendUnattributed(attrstr, "ordinals ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("ordn", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "H2O"; + uiAttributedStringAppendUnattributed(attrstr, "scientific inferiors ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("sinf", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "2/3"; + uiAttributedStringAppendUnattributed(attrstr, "fraction forms ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); +#if 0 /* TODO */ + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFractionForms; + spec.Value = uiAttributeFractionFormNone; + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ", "); +#endif + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("afrc", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ", "); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("frac", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "0"; + uiAttributedStringAppendUnattributed(attrstr, "slashed zeroes ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("zero", 0); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, " vs. "); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("zero", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "\xCE\xA0\xCE\xA3"; + uiAttributedStringAppendUnattributed(attrstr, "mathematical greek ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("mgrk", 0); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, " vs. "); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("mgrk", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "qwertyuiop\xE2\x80\xA2"; + uiAttributedStringAppendUnattributed(attrstr, "ornamental forms ("); + for (i = 1; i < 11; i++) { + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("ornm", i); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + next = "\xE2\x80\xA2"; + } + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "g"; + uiAttributedStringAppendUnattributed(attrstr, "specific forms/alternates ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("aalt", 0); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, " vs. "); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("aalt", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "ABCDEFGQWERTY"; + uiAttributedStringAppendUnattributed(attrstr, "titling capital forms ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("titl", 0); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, " vs. "); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("titl", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "\xE7\x80\x86"; + uiAttributedStringAppendUnattributed(attrstr, "alternate Han character forms ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("jp78", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, " vs. "); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("jp83", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + otf = addFeature("onum", 0); + // Core Text's internal AAT-to-OpenType mapping says to include this, so we include it too + // TODO is it always set? + uiOpenTypeFeaturesAdd(otf, 'l', 'n', 'u', 'm', 0); + next = "0123456789"; + uiAttributedStringAppendUnattributed(attrstr, "lowercase numbers ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = otf; + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, " vs. "); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + otf = addFeature("onum", 1); + uiOpenTypeFeaturesAdd(otf, 'l', 'n', 'u', 'm', 1); + spec.Features = otf; + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "\xE4\xBC\xBD"; + uiAttributedStringAppendUnattributed(attrstr, "hanja to hangul translation ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("hngl", 0); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, " vs. "); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("hngl", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "\xE3\x81\x82"; + uiAttributedStringAppendUnattributed(attrstr, "annotated glyph forms ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("nalt", 0); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, " vs. "); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("nalt", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, " vs. "); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("nalt", 4); // AAT inverted circle + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "\xE3\x81\x82"; + uiAttributedStringAppendUnattributed(attrstr, "ruby forms of kana ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("ruby", 0); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, " vs. "); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("ruby", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "now is the time"; + uiAttributedStringAppendUnattributed(attrstr, "italic forms of Latin letters in CJK fonts ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("ital", 0); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, " vs. "); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("ital", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "{I} > {J}"; + uiAttributedStringAppendUnattributed(attrstr, "case-sensitive character forms, such as punctuation ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("case", 0); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, " vs. "); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("case", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "ABC"; + uiAttributedStringAppendUnattributed(attrstr, "specialized spacing between uppercase letters ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("cpsp", 0); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, " vs. "); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("cpsp", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "\xE3\x82\xB9\xE3\x83\x98\xE3\x83\x88"; + uiAttributedStringAppendUnattributed(attrstr, "alternate horizontal ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("hkna", 0); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, " vs. "); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("hkna", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ") and vertical ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("vkna", 0); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, " vs. "); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("vkna", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ") kana forms"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "g"; + uiAttributedStringAppendUnattributed(attrstr, "stylistic alternates ("); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + for (i = 1; i <= 20; i++) { + char tag[4]; + + tag[0] = 's'; + tag[1] = 's'; + tag[2] = '0'; + if (i >= 10) + tag[2] = '1'; + tag[3] = (i % 10) + '0'; // TODO see how I wrote this elsewhere + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Features = addFeature(tag, 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + } + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "first"; + uiAttributedStringAppendUnattributed(attrstr, "contextual alternates ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("calt", 0); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, " vs. "); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("calt", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "FONT"; + uiAttributedStringAppendUnattributed(attrstr, "swashes ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("swsh", 0); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, " vs. "); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("swsh", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "Font"; + uiAttributedStringAppendUnattributed(attrstr, "contextual swashes ("); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("cswh", 0); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, " vs. "); + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("cswh", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ")"); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "Small Caps"; + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("smcp", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ", "); + next = "Petite Caps"; + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("pcap", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + + uiAttributedStringAppendUnattributed(attrstr, ", "); + + next = "SMALL UPPERCASES"; + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("c2sp", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + uiAttributedStringAppendUnattributed(attrstr, ", and "); + next = "PETITE UPPERCASES"; + start = uiAttributedStringLen(attrstr); + end = start + strlen(next); + uiAttributedStringAppendUnattributed(attrstr, next); + spec.Type = uiAttributeFeatures; + spec.Features = addFeature("c2pc", 1); + uiAttributedStringSetAttribute(attrstr, &spec, start, end); + + uiAttributedStringAppendUnattributed(attrstr, "."); + + // TODO write a dedicated example for experimenting with typographic features like the one in gtk3-demo +} + +static char fontFamily[] = "Times New Roman"; +// TODO should be const; look at constructor function? +static uiDrawFontDescriptor defaultFont = { + .Family = fontFamily, + .Size = 12, + .Weight = uiDrawTextWeightNormal, + .Italic = uiDrawTextItalicNormal, + .Stretch = uiDrawTextStretchNormal, +}; +static uiDrawTextLayoutParams params; + +#define margins 10 + +static uiBox *panel; +static uiCheckbox *showLineBounds; +static uiFontButton *fontButton; + +// TODO should be const? +static uiDrawBrush fillBrushes[4] = { + { + .Type = uiDrawBrushTypeSolid, + .R = 1.0, + .G = 0.0, + .B = 0.0, + .A = 0.5, + }, + { + .Type = uiDrawBrushTypeSolid, + .R = 0.0, + .G = 1.0, + .B = 0.0, + .A = 0.5, + }, + { + .Type = uiDrawBrushTypeSolid, + .R = 0.0, + .G = 0.0, + .B = 1.0, + .A = 0.5, + }, + { + .Type = uiDrawBrushTypeSolid, + .R = 0.0, + .G = 1.0, + .B = 1.0, + .A = 0.5, + }, +}; + +static void draw(uiAreaDrawParams *p) +{ + uiDrawPath *path; + uiDrawTextLayout *layout; + uiDrawBrush b; + + b.Type = uiDrawBrushTypeSolid; + + // only clip the text, not the guides + uiDrawSave(p->Context); + + path = uiDrawNewPath(uiDrawFillModeWinding); + uiDrawPathAddRectangle(path, margins, margins, + p->AreaWidth - 2 * margins, + p->AreaHeight - 2 * margins); + uiDrawPathEnd(path); + uiDrawClip(p->Context, path); + uiDrawFreePath(path); + + params.Width = p->AreaWidth - 2 * margins; + layout = uiDrawNewTextLayout(¶ms); + uiDrawText(p->Context, layout, margins, margins); + + uiDrawRestore(p->Context); + + if (uiCheckboxChecked(showLineBounds)) { + uiDrawTextLayoutLineMetrics m; + int i, n; + int fill = 0; + + n = uiDrawTextLayoutNumLines(layout); + for (i = 0; i < n; i++) { + uiDrawTextLayoutLineGetMetrics(layout, i, &m); + + path = uiDrawNewPath(uiDrawFillModeWinding); + uiDrawPathAddRectangle(path, margins + m.X, margins + m.Y, + m.Width, m.Height); + uiDrawPathEnd(path); + uiDrawFill(p->Context, path, fillBrushes + fill); + uiDrawFreePath(path); + fill = (fill + 1) % 4; + } + } + + uiDrawFreeTextLayout(layout); +} + +static struct example attributesExample; + +static void changeFont(uiFontButton *b, void *data) +{ + if (defaultFont.Family != fontFamily) + uiFreeText(defaultFont.Family); + // TODO rename defaultFont + uiFontButtonFont(fontButton, &defaultFont); + redraw(); +} + +// TODO share? +static void checkboxChecked(uiCheckbox *c, void *data) +{ + redraw(); +} + +static uiCheckbox *newCheckbox(const char *text) +{ + uiCheckbox *c; + + c = uiNewCheckbox(text); + uiCheckboxOnToggled(c, checkboxChecked, NULL); + uiBoxAppend(panel, uiControl(c), 0); + return c; +} + +struct example *mkAttributesExample(void) +{ + panel = uiNewVerticalBox(); + showLineBounds = newCheckbox("Show Line Bounds"); + fontButton = uiNewFontButton(); + uiFontButtonOnChanged(fontButton, changeFont, NULL); + // TODO set the font button to the current defaultFont + uiBoxAppend(panel, uiControl(fontButton), 0); + + attributesExample.name = "Attributed Text"; + attributesExample.panel = uiControl(panel); + attributesExample.draw = draw; + attributesExample.mouse = NULL; + attributesExample.key = NULL; + + setupAttributedString(); + params.String = attrstr; + params.DefaultFont = &defaultFont; + params.Align = uiDrawTextAlignLeft; + + return &attributesExample; +} diff --git a/_wip/examples_drawtext/basic.c b/_wip/examples_drawtext/basic.c new file mode 100644 index 00000000..ede973b3 --- /dev/null +++ b/_wip/examples_drawtext/basic.c @@ -0,0 +1,267 @@ +// 17 january 2017 +#include "drawtext.h" + +static const char *text = + "It is with a kind of fear that I begin to write the history of my life. " + "I have, as it were, a superstitious hesitation in lifting the veil that " + "clings about my childhood like a golden mist. The task of writing an " + "autobiography is a difficult one. When I try to classify my earliest " + "impressions, I find that fact and fancy look alike across the years that " + "link the past with the present. The woman paints the child's experiences " + "in her own fantasy. A few impressions stand out vividly from the first " + "years of my life; but \"the shadows of the prison-house are on the rest.\" " + "Besides, many of the joys and sorrows of childhood have lost their " + "poignancy; and many incidents of vital importance in my early education " + "have been forgotten in the excitement of great discoveries. In order, " + "therefore, not to be tedious I shall try to present in a series of " + "sketches only the episodes that seem to me to be the most interesting " + "and important." + ""; +static char fontFamily[] = "Palatino"; +// TODO should be const; look at constructor function? +static uiDrawFontDescriptor defaultFont = { + .Family = fontFamily, + .Size = 18, + .Weight = uiDrawTextWeightNormal, + .Italic = uiDrawTextItalicNormal, + .Stretch = uiDrawTextStretchNormal, +}; +static uiAttributedString *attrstr; +static uiDrawTextLayoutParams params; + +#define margins 10 + +static uiBox *panel; +static uiCheckbox *showExtents; +static uiCheckbox *showLineBounds; +static uiCheckbox *showLineGuides; + +// TODO should this be const? +static double strokeDashes[] = { 5, 2 }; +// TODO this should be const +static uiDrawStrokeParams strokeParams = { + .Cap = uiDrawLineCapFlat, + .Join = uiDrawLineJoinMiter, + .Thickness = 1, + .MiterLimit = uiDrawDefaultMiterLimit, + .Dashes = strokeDashes, + .NumDashes = 2, + .DashPhase = 0, +}; + +// TODO should be const? +static uiDrawBrush fillBrushes[4] = { + { + .Type = uiDrawBrushTypeSolid, + .R = 1.0, + .G = 0.0, + .B = 0.0, + .A = 0.5, + }, + { + .Type = uiDrawBrushTypeSolid, + .R = 0.0, + .G = 1.0, + .B = 0.0, + .A = 0.5, + }, + { + .Type = uiDrawBrushTypeSolid, + .R = 0.0, + .G = 0.0, + .B = 1.0, + .A = 0.5, + }, + { + .Type = uiDrawBrushTypeSolid, + .R = 0.0, + .G = 1.0, + .B = 1.0, + .A = 0.5, + }, +}; +// TODO come up with better colors +static uiDrawBrush strokeBrushes[3] = { + // baseline + { + .Type = uiDrawBrushTypeSolid, + .R = 0.5, + .G = 0.5, + .B = 0.0, + .A = 0.75, + }, + // ascent + { + .Type = uiDrawBrushTypeSolid, + .R = 1.0, + .G = 0.0, + .B = 1.0, + .A = 0.75, + }, + // descent + { + .Type = uiDrawBrushTypeSolid, + .R = 0.5, + .G = 0.75, + .B = 1.0, + .A = 0.75, + }, +}; + +static void draw(uiAreaDrawParams *p) +{ + uiDrawPath *path; + uiDrawTextLayout *layout; + uiDrawBrush b; + + b.Type = uiDrawBrushTypeSolid; + + // only clip the text, not the guides + uiDrawSave(p->Context); + + path = uiDrawNewPath(uiDrawFillModeWinding); + uiDrawPathAddRectangle(path, margins, margins, + p->AreaWidth - 2 * margins, + p->AreaHeight - 2 * margins); + uiDrawPathEnd(path); + uiDrawClip(p->Context, path); + uiDrawFreePath(path); + + // TODO get rid of this later +#if 0 + path = uiDrawNewPath(uiDrawFillModeWinding); + uiDrawPathAddRectangle(path, -100, -100, + p->AreaWidth * 2, + p->AreaHeight * 2); + uiDrawPathEnd(path); + b.R = 0.0; + b.G = 1.0; + b.B = 0.0; + b.A = 1.0; + uiDrawFill(p->Context, path, &b); + uiDrawFreePath(path); +#endif + + params.Width = p->AreaWidth - 2 * margins; + layout = uiDrawNewTextLayout(¶ms); + uiDrawText(p->Context, layout, margins, margins); + + uiDrawRestore(p->Context); + + if (uiCheckboxChecked(showExtents)) { + double width, height; + + uiDrawTextLayoutExtents(layout, &width, &height); + path = uiDrawNewPath(uiDrawFillModeWinding); + uiDrawPathAddRectangle(path, margins, margins, + width, height); + uiDrawPathEnd(path); + b.R = 1.0; + b.G = 0.0; + b.B = 1.0; + b.A = 0.5; + uiDrawStroke(p->Context, path, &b, &strokeParams); + uiDrawFreePath(path); + } + + if (uiCheckboxChecked(showLineBounds) || uiCheckboxChecked(showLineGuides)) { + uiDrawTextLayoutLineMetrics m; + int i, n; + int fill = 0; + + n = uiDrawTextLayoutNumLines(layout); + for (i = 0; i < n; i++) { + uiDrawTextLayoutLineGetMetrics(layout, i, &m); + + if (uiCheckboxChecked(showLineBounds)) { + path = uiDrawNewPath(uiDrawFillModeWinding); + uiDrawPathAddRectangle(path, margins + m.X, margins + m.Y, + m.Width, m.Height); + uiDrawPathEnd(path); + uiDrawFill(p->Context, path, fillBrushes + fill); + uiDrawFreePath(path); + fill = (fill + 1) % 4; + } + if (uiCheckboxChecked(showLineGuides)) { + // baseline + path = uiDrawNewPath(uiDrawFillModeWinding); + uiDrawPathNewFigure(path, + margins + m.X, + margins + m.BaselineY); + uiDrawPathLineTo(path, + margins + m.X + m.Width, + margins + m.BaselineY); + uiDrawPathEnd(path); + uiDrawStroke(p->Context, path, &(strokeBrushes[0]), &strokeParams); + uiDrawFreePath(path); + + // ascent line + path = uiDrawNewPath(uiDrawFillModeWinding); + uiDrawPathNewFigure(path, + margins + m.X, + margins + m.BaselineY - m.Ascent); + uiDrawPathLineTo(path, + margins + m.X + m.Width, + margins + m.BaselineY - m.Ascent); + uiDrawPathEnd(path); + uiDrawStroke(p->Context, path, &(strokeBrushes[1]), &strokeParams); + uiDrawFreePath(path); + + // descent line + path = uiDrawNewPath(uiDrawFillModeWinding); + uiDrawPathNewFigure(path, + margins + m.X, + margins + m.BaselineY + m.Descent); + uiDrawPathLineTo(path, + margins + m.X + m.Width, + margins + m.BaselineY + m.Descent); + uiDrawPathEnd(path); + uiDrawStroke(p->Context, path, &(strokeBrushes[2]), &strokeParams); + uiDrawFreePath(path); + } + } + } + + uiDrawFreeTextLayout(layout); +} + +static struct example basicExample; + +// TODO share? +static void checkboxChecked(uiCheckbox *c, void *data) +{ + redraw(); +} + +static uiCheckbox *newCheckbox(const char *text) +{ + uiCheckbox *c; + + c = uiNewCheckbox(text); + uiCheckboxOnToggled(c, checkboxChecked, NULL); + uiBoxAppend(panel, uiControl(c), 0); + return c; +} + +struct example *mkBasicExample(void) +{ + panel = uiNewVerticalBox(); + showExtents = newCheckbox("Show Layout Extents"); + showLineBounds = newCheckbox("Show Line Bounds"); + showLineGuides = newCheckbox("Show Line Guides"); + + basicExample.name = "Basic Paragraph of Text"; + basicExample.panel = uiControl(panel); + basicExample.draw = draw; + basicExample.mouse = NULL; + basicExample.key = NULL; + + attrstr = uiNewAttributedString(text); + params.String = attrstr; + params.DefaultFont = &defaultFont; + params.Align = uiDrawTextAlignLeft; + + return &basicExample; +} + +// TODO on GTK+ an area by itself in a window doesn't get expand properties set properly? diff --git a/_wip/examples_drawtext/drawtext.h b/_wip/examples_drawtext/drawtext.h new file mode 100644 index 00000000..484dcec9 --- /dev/null +++ b/_wip/examples_drawtext/drawtext.h @@ -0,0 +1,29 @@ +// 20 january 2017 +#include +#include +#include +#include "../../ui.h" + +struct example { + const char *name; + uiControl *panel; + void (*draw)(uiAreaDrawParams *p); + void (*mouse)(uiAreaMouseEvent *e); + int (*key)(uiAreaKeyEvent *e); + // TODO key? +}; + +// main.c +extern void redraw(void); + +// basic.c +extern struct example *mkBasicExample(void); + +// hittest.c +extern struct example *mkHitTestExample(void); + +// attributes.c +extern struct example *mkAttributesExample(void); + +// emptystr_hittest.c +extern struct example *mkEmptyStringExample(void); diff --git a/_wip/examples_drawtext/emptystr_hittest.c b/_wip/examples_drawtext/emptystr_hittest.c new file mode 100644 index 00000000..fba386d8 --- /dev/null +++ b/_wip/examples_drawtext/emptystr_hittest.c @@ -0,0 +1,254 @@ +// 20 january 2017 +#include "drawtext.h" + +// TODO FOR THIS FILE +// get rid of it once we rewrite this example (which requires having more fine-grained scrolling control) + +// TODO double-check ligatures on all platforms to make sure we can place the cursor at the right place +// TODO the hiding and showing does not work properly on GTK+ +// TODO using the arrow keys allows us to walk back to the end of the line on some platforms (TODO which?); IIRC arrow keys shouldn't do that +// TODO make sure to check the cursor positions of RTL on all platforms + +static const char *text = ""; +static char fontFamily[] = "Helvetica"; +static uiDrawFontDescriptor defaultFont = { + .Family = fontFamily, + .Size = 14, + .Weight = uiDrawTextWeightNormal, + .Italic = uiDrawTextItalicNormal, + .Stretch = uiDrawTextStretchNormal, +}; +static uiAttributedString *attrstr; +static uiDrawTextLayoutParams params; + +#define margins 10 + +static uiBox *panel; +static uiBox *vbox; +static uiLabel *caretLabel; +static uiCheckbox *showLineBounds; +static uiFontButton *fontButton; +static uiCombobox *textAlign; + +static int caretLine = -1; +static size_t caretPos; + +// TODO should be const? +static uiDrawBrush fillBrushes[4] = { + { + .Type = uiDrawBrushTypeSolid, + .R = 1.0, + .G = 0.0, + .B = 0.0, + .A = 0.5, + }, + { + .Type = uiDrawBrushTypeSolid, + .R = 0.0, + .G = 1.0, + .B = 0.0, + .A = 0.5, + }, + { + .Type = uiDrawBrushTypeSolid, + .R = 0.0, + .G = 0.0, + .B = 1.0, + .A = 0.5, + }, + { + .Type = uiDrawBrushTypeSolid, + .R = 0.0, + .G = 1.0, + .B = 1.0, + .A = 0.5, + }, +}; + +static void draw(uiAreaDrawParams *p) +{ + uiDrawPath *path; + uiDrawTextLayout *layout; + uiDrawTextLayoutLineMetrics m; + + // only clip the text, not the guides + uiDrawSave(p->Context); + + path = uiDrawNewPath(uiDrawFillModeWinding); + uiDrawPathAddRectangle(path, margins, margins, + p->AreaWidth - 2 * margins, + p->AreaHeight - 2 * margins); + uiDrawPathEnd(path); + uiDrawClip(p->Context, path); + uiDrawFreePath(path); + + params.Width = p->AreaWidth - 2 * margins; + layout = uiDrawNewTextLayout(¶ms); + uiDrawText(p->Context, layout, margins, margins); + + uiDrawRestore(p->Context); + + if (caretLine == -1) { + caretLine = uiDrawTextLayoutNumLines(layout) - 1; + caretPos = uiAttributedStringLen(attrstr); + } + uiDrawCaret(p->Context, margins, margins, + layout, caretPos, &caretLine); + + if (uiCheckboxChecked(showLineBounds)) { + int i, n; + int fill = 0; + + n = uiDrawTextLayoutNumLines(layout); + for (i = 0; i < n; i++) { + uiDrawTextLayoutLineGetMetrics(layout, i, &m); + path = uiDrawNewPath(uiDrawFillModeWinding); + uiDrawPathAddRectangle(path, margins + m.X, margins + m.Y, + m.Width, m.Height); + uiDrawPathEnd(path); + uiDrawFill(p->Context, path, fillBrushes + fill); + uiDrawFreePath(path); + fill = (fill + 1) % 4; + } + } + + uiDrawFreeTextLayout(layout); +} + +static void mouse(uiAreaMouseEvent *e) +{ + uiDrawTextLayout *layout; + char labelText[128]; + + if (e->Down != 1) + return; + + params.Width = e->AreaWidth - 2 * margins; + layout = uiDrawNewTextLayout(¶ms); + uiDrawTextLayoutHitTest(layout, + e->X - margins, e->Y - margins, + &caretPos, &caretLine); + uiDrawFreeTextLayout(layout); + + // TODO move this into the draw handler so it is reflected by keyboard-based position changes + // urgh %zd is not supported by MSVC with sprintf() + // TODO get that part in test/ about having no other option + sprintf(labelText, "pos %d line %d", + (int) caretPos, caretLine); + uiLabelSetText(caretLabel, labelText); + + redraw(); +} + +static int key(uiAreaKeyEvent *e) +{ + size_t grapheme; + + if (e->Up) + return 0; + if (e->Key != 0) + return 0; + switch (e->ExtKey) { + case uiExtKeyUp: + // TODO + return 1; + case uiExtKeyDown: + // TODO + return 1; + case uiExtKeyLeft: + grapheme = uiAttributedStringByteIndexToGrapheme(attrstr, caretPos); + if (grapheme == 0) + return 0; + grapheme--; + caretPos = uiAttributedStringGraphemeToByteIndex(attrstr, grapheme); + redraw(); + return 1; + case uiExtKeyRight: + grapheme = uiAttributedStringByteIndexToGrapheme(attrstr, caretPos); + if (grapheme == uiAttributedStringNumGraphemes(attrstr)) + return 0; + grapheme++; + caretPos = uiAttributedStringGraphemeToByteIndex(attrstr, grapheme); + redraw(); + return 1; + } + return 0; +} + +static struct example hitTestExample; + +// TODO share? +static void checkboxChecked(uiCheckbox *c, void *data) +{ + redraw(); +} + +static void changeFont(uiFontButton *b, void *data) +{ + if (defaultFont.Family != fontFamily) + uiFreeText(defaultFont.Family); + // TODO rename defaultFont + uiFontButtonFont(fontButton, &defaultFont); + printf("{\n\tfamily: %s\n\tsize: %g\n\tweight: %d\n\titalic: %d\n\tstretch: %d\n}\n", + defaultFont.Family, + defaultFont.Size, + (int) (defaultFont.Weight), + (int) (defaultFont.Italic), + (int) (defaultFont.Stretch)); + redraw(); +} + +static void changeTextAlign(uiCombobox *c, void *data) +{ + // note the order of the items added below + params.Align = (uiDrawTextAlign) uiComboboxSelected(textAlign); + redraw(); +} + +// TODO share? +static uiCheckbox *newCheckbox(uiBox *box, const char *text) +{ + uiCheckbox *c; + + c = uiNewCheckbox(text); + uiCheckboxOnToggled(c, checkboxChecked, NULL); + uiBoxAppend(box, uiControl(c), 0); + return c; +} + +struct example *mkEmptyStringExample(void) +{ + panel = uiNewHorizontalBox(); + vbox = uiNewVerticalBox(); + // TODO the second vbox causes this not to stretch at least on OS X + uiBoxAppend(panel, uiControl(vbox), 1); + caretLabel = uiNewLabel("Caret information is shown here"); + uiBoxAppend(vbox, uiControl(caretLabel), 0); + showLineBounds = newCheckbox(vbox, "Show Line Bounds (for debugging metrics)"); + vbox = uiNewVerticalBox(); + uiBoxAppend(panel, uiControl(vbox), 0); + fontButton = uiNewFontButton(); + uiFontButtonOnChanged(fontButton, changeFont, NULL); + // TODO set the font button to the current defaultFont + uiBoxAppend(vbox, uiControl(fontButton), 0); + textAlign = uiNewCombobox(); + // note that these are in the order in the enum + uiComboboxAppend(textAlign, "Left"); + uiComboboxAppend(textAlign, "Center"); + uiComboboxAppend(textAlign, "Right"); + uiComboboxOnSelected(textAlign, changeTextAlign, NULL); + uiBoxAppend(vbox, uiControl(textAlign), 0); + + hitTestExample.name = "Empty String"; + hitTestExample.panel = uiControl(panel); + hitTestExample.draw = draw; + hitTestExample.mouse = mouse; + hitTestExample.key = key; + + attrstr = uiNewAttributedString(text); + params.String = attrstr; + params.DefaultFont = &defaultFont; + params.Align = uiDrawTextAlignLeft; + + return &hitTestExample; +} diff --git a/_wip/examples_drawtext/hittest.c b/_wip/examples_drawtext/hittest.c new file mode 100644 index 00000000..a8092b47 --- /dev/null +++ b/_wip/examples_drawtext/hittest.c @@ -0,0 +1,262 @@ +// 20 january 2017 +#include "drawtext.h" + +// TODO double-check ligatures on all platforms to make sure we can place the cursor at the right place +// TODO using the arrow keys allows us to walk back to the end of the line on some platforms (TODO which?); IIRC arrow keys shouldn't do that +// TODO make sure to check the cursor positions of RTL on all platforms + +static const char *text = + "Each of the glyphs an end user interacts with are called graphemes. " + "If you enter a byte range in the text boxes below and click the button, you can see the blue box move to surround that byte range, as well as what the actual byte range necessary is. " + // TODO rephrase this; I don't think this code will use those grapheme functions... + "You'll also see the index of the first grapheme; uiAttributedString has facilities for converting between UTF-8 code points and grapheme indices. " + "Additionally, click on the string to move the caret. Watch the status text at the bottom change too. " + "That being said: " + "\xC3\x93O\xCC\x81 (combining accents) " + "A\xCC\xAA\xEF\xB8\xA0 (multiple combining characters) " + "\xE2\x80\xAE#\xE2\x80\xAC (RTL glyph) " + "\xF0\x9F\x92\xBB (non-BMP character) " + "\xF0\x9F\x92\xBB\xCC\x80 (combined non-BMP character; may render strangely) " + ""; +static char fontFamily[] = "Helvetica"; +static uiDrawFontDescriptor defaultFont = { + .Family = fontFamily, + .Size = 14, + .Weight = uiDrawTextWeightNormal, + .Italic = uiDrawTextItalicNormal, + .Stretch = uiDrawTextStretchNormal, +}; +static uiAttributedString *attrstr; +static uiDrawTextLayoutParams params; + +#define margins 10 + +static uiBox *panel; +static uiBox *vbox; +static uiLabel *caretLabel; +static uiCheckbox *showLineBounds; +static uiFontButton *fontButton; +static uiCombobox *textAlign; + +static int caretLine = -1; +static size_t caretPos; + +// TODO should be const? +static uiDrawBrush fillBrushes[4] = { + { + .Type = uiDrawBrushTypeSolid, + .R = 1.0, + .G = 0.0, + .B = 0.0, + .A = 0.5, + }, + { + .Type = uiDrawBrushTypeSolid, + .R = 0.0, + .G = 1.0, + .B = 0.0, + .A = 0.5, + }, + { + .Type = uiDrawBrushTypeSolid, + .R = 0.0, + .G = 0.0, + .B = 1.0, + .A = 0.5, + }, + { + .Type = uiDrawBrushTypeSolid, + .R = 0.0, + .G = 1.0, + .B = 1.0, + .A = 0.5, + }, +}; + +static void draw(uiAreaDrawParams *p) +{ + uiDrawPath *path; + uiDrawTextLayout *layout; + uiDrawTextLayoutLineMetrics m; + + // only clip the text, not the guides + uiDrawSave(p->Context); + + path = uiDrawNewPath(uiDrawFillModeWinding); + uiDrawPathAddRectangle(path, margins, margins, + p->AreaWidth - 2 * margins, + p->AreaHeight - 2 * margins); + uiDrawPathEnd(path); + uiDrawClip(p->Context, path); + uiDrawFreePath(path); + + params.Width = p->AreaWidth - 2 * margins; + layout = uiDrawNewTextLayout(¶ms); + uiDrawText(p->Context, layout, margins, margins); + + uiDrawRestore(p->Context); + + if (caretLine == -1) { + caretLine = uiDrawTextLayoutNumLines(layout) - 1; + caretPos = uiAttributedStringLen(attrstr); + } + uiDrawCaret(p->Context, margins, margins, + layout, caretPos, &caretLine); + + if (uiCheckboxChecked(showLineBounds)) { + int i, n; + int fill = 0; + + n = uiDrawTextLayoutNumLines(layout); + for (i = 0; i < n; i++) { + uiDrawTextLayoutLineGetMetrics(layout, i, &m); + path = uiDrawNewPath(uiDrawFillModeWinding); + uiDrawPathAddRectangle(path, margins + m.X, margins + m.Y, + m.Width, m.Height); + uiDrawPathEnd(path); + uiDrawFill(p->Context, path, fillBrushes + fill); + uiDrawFreePath(path); + fill = (fill + 1) % 4; + } + } + + uiDrawFreeTextLayout(layout); +} + +static void mouse(uiAreaMouseEvent *e) +{ + uiDrawTextLayout *layout; + char labelText[128]; + + if (e->Down != 1) + return; + + params.Width = e->AreaWidth - 2 * margins; + layout = uiDrawNewTextLayout(¶ms); + uiDrawTextLayoutHitTest(layout, + e->X - margins, e->Y - margins, + &caretPos, &caretLine); + uiDrawFreeTextLayout(layout); + + // TODO move this into the draw handler so it is reflected by keyboard-based position changes + // urgh %zd is not supported by MSVC with sprintf() + // TODO get that part in test/ about having no other option + sprintf(labelText, "pos %d line %d", + (int) caretPos, caretLine); + uiLabelSetText(caretLabel, labelText); + + redraw(); +} + +static int key(uiAreaKeyEvent *e) +{ + size_t grapheme; + + if (e->Up) + return 0; + if (e->Key != 0) + return 0; + switch (e->ExtKey) { + case uiExtKeyUp: + // TODO + return 1; + case uiExtKeyDown: + // TODO + return 1; + case uiExtKeyLeft: + grapheme = uiAttributedStringByteIndexToGrapheme(attrstr, caretPos); + if (grapheme == 0) + return 0; + grapheme--; + caretPos = uiAttributedStringGraphemeToByteIndex(attrstr, grapheme); + redraw(); + return 1; + case uiExtKeyRight: + grapheme = uiAttributedStringByteIndexToGrapheme(attrstr, caretPos); + if (grapheme == uiAttributedStringNumGraphemes(attrstr)) + return 0; + grapheme++; + caretPos = uiAttributedStringGraphemeToByteIndex(attrstr, grapheme); + redraw(); + return 1; + } + return 0; +} + +static struct example hitTestExample; + +// TODO share? +static void checkboxChecked(uiCheckbox *c, void *data) +{ + redraw(); +} + +static void changeFont(uiFontButton *b, void *data) +{ + if (defaultFont.Family != fontFamily) + uiFreeText(defaultFont.Family); + // TODO rename defaultFont + uiFontButtonFont(fontButton, &defaultFont); + printf("{\n\tfamily: %s\n\tsize: %g\n\tweight: %d\n\titalic: %d\n\tstretch: %d\n}\n", + defaultFont.Family, + defaultFont.Size, + (int) (defaultFont.Weight), + (int) (defaultFont.Italic), + (int) (defaultFont.Stretch)); + redraw(); +} + +static void changeTextAlign(uiCombobox *c, void *data) +{ + // note the order of the items added below + params.Align = (uiDrawTextAlign) uiComboboxSelected(textAlign); + redraw(); +} + +// TODO share? +static uiCheckbox *newCheckbox(uiBox *box, const char *text) +{ + uiCheckbox *c; + + c = uiNewCheckbox(text); + uiCheckboxOnToggled(c, checkboxChecked, NULL); + uiBoxAppend(box, uiControl(c), 0); + return c; +} + +struct example *mkHitTestExample(void) +{ + panel = uiNewHorizontalBox(); + vbox = uiNewVerticalBox(); + // TODO the second vbox causes this not to stretch at least on OS X + uiBoxAppend(panel, uiControl(vbox), 1); + caretLabel = uiNewLabel("Caret information is shown here"); + uiBoxAppend(vbox, uiControl(caretLabel), 0); + showLineBounds = newCheckbox(vbox, "Show Line Bounds (for debugging metrics)"); + vbox = uiNewVerticalBox(); + uiBoxAppend(panel, uiControl(vbox), 0); + fontButton = uiNewFontButton(); + uiFontButtonOnChanged(fontButton, changeFont, NULL); + // TODO set the font button to the current defaultFont + uiBoxAppend(vbox, uiControl(fontButton), 0); + textAlign = uiNewCombobox(); + // note that these are in the order in the enum + uiComboboxAppend(textAlign, "Left"); + uiComboboxAppend(textAlign, "Center"); + uiComboboxAppend(textAlign, "Right"); + uiComboboxOnSelected(textAlign, changeTextAlign, NULL); + uiBoxAppend(vbox, uiControl(textAlign), 0); + + hitTestExample.name = "Hit-Testing and Grapheme Boundaries"; + hitTestExample.panel = uiControl(panel); + hitTestExample.draw = draw; + hitTestExample.mouse = mouse; + hitTestExample.key = key; + + attrstr = uiNewAttributedString(text); + params.String = attrstr; + params.DefaultFont = &defaultFont; + params.Align = uiDrawTextAlignLeft; + + return &hitTestExample; +} diff --git a/_wip/examples_drawtext/httext.gz b/_wip/examples_drawtext/httext.gz new file mode 100644 index 00000000..7fd947cf Binary files /dev/null and b/_wip/examples_drawtext/httext.gz differ diff --git a/_wip/examples_drawtext/main.c b/_wip/examples_drawtext/main.c new file mode 100644 index 00000000..4e72b25e --- /dev/null +++ b/_wip/examples_drawtext/main.c @@ -0,0 +1,136 @@ +// 17 january 2017 +#include "drawtext.h" + +static uiWindow *mainwin; +static uiBox *box; +static uiCombobox *exampleList; +static uiArea *area; +static uiAreaHandler handler; + +#define nExamples 20 +static struct example *examples[nExamples]; +static int curExample = 0; + +static void onExampleChanged(uiCombobox *c, void *data) +{ + uiControlHide(examples[curExample]->panel); + curExample = uiComboboxSelected(exampleList); + uiControlShow(examples[curExample]->panel); + redraw(); +} + +void redraw(void) +{ + uiAreaQueueRedrawAll(area); +} + +static void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *p) +{ + examples[curExample]->draw(p); +} + +static void handlerMouseEvent(uiAreaHandler *a, uiArea *area, uiAreaMouseEvent *e) +{ + if (examples[curExample]->mouse != NULL) + examples[curExample]->mouse(e); +} + +static void handlerMouseCrossed(uiAreaHandler *ah, uiArea *a, int left) +{ + // do nothing +} + +static void handlerDragBroken(uiAreaHandler *ah, uiArea *a) +{ + // do nothing +} + +static int handlerKeyEvent(uiAreaHandler *ah, uiArea *a, uiAreaKeyEvent *e) +{ + if (examples[curExample]->key != NULL) + return examples[curExample]->key(e); + return 0; +} + +static int onClosing(uiWindow *w, void *data) +{ + uiControlDestroy(uiControl(mainwin)); + uiQuit(); + return 0; +} + +static int shouldQuit(void *data) +{ + uiControlDestroy(uiControl(mainwin)); + return 1; +} + +int main(void) +{ + uiInitOptions o; + const char *err; + int n; + + handler.Draw = handlerDraw; + handler.MouseEvent = handlerMouseEvent; + handler.MouseCrossed = handlerMouseCrossed; + handler.DragBroken = handlerDragBroken; + handler.KeyEvent = handlerKeyEvent; + + memset(&o, 0, sizeof (uiInitOptions)); + err = uiInit(&o); + if (err != NULL) { + fprintf(stderr, "error initializing ui: %s\n", err); + uiFreeInitError(err); + return 1; + } + + uiOnShouldQuit(shouldQuit, NULL); + + mainwin = uiNewWindow("libui Text-Drawing Example", 640, 480, 1); + uiWindowOnClosing(mainwin, onClosing, NULL); + + box = uiNewVerticalBox(); + uiWindowSetChild(mainwin, uiControl(box)); + + exampleList = uiNewCombobox(); + uiBoxAppend(box, uiControl(exampleList), 0); + + area = uiNewArea(&handler); + uiBoxAppend(box, uiControl(area), 1); + + n = 0; + examples[n] = mkBasicExample(); + uiComboboxAppend(exampleList, examples[n]->name); + uiControlHide(examples[n]->panel); + uiBoxAppend(box, examples[n]->panel, 0); + n++; + examples[n] = mkHitTestExample(); + uiComboboxAppend(exampleList, examples[n]->name); + uiControlHide(examples[n]->panel); + uiBoxAppend(box, examples[n]->panel, 0); + n++; + examples[n] = mkAttributesExample(); + uiComboboxAppend(exampleList, examples[n]->name); + uiControlHide(examples[n]->panel); + uiBoxAppend(box, examples[n]->panel, 0); + n++; + examples[n] = mkEmptyStringExample(); + uiComboboxAppend(exampleList, examples[n]->name); + uiControlHide(examples[n]->panel); + uiBoxAppend(box, examples[n]->panel, 0); + n++; + // and set things up for the initial state + uiComboboxSetSelected(exampleList, 0); + uiComboboxOnSelected(exampleList, onExampleChanged, NULL); + // and set up the first one + onExampleChanged(NULL, NULL); + + uiControlShow(uiControl(mainwin)); + uiMain(); + + // TODO free examples + + uiUninit(); + return 0; +} diff --git a/_wip/examples_drawtext_CMakeLists.txt b/_wip/examples_drawtext_CMakeLists.txt new file mode 100644 index 00000000..f2289d21 --- /dev/null +++ b/_wip/examples_drawtext_CMakeLists.txt @@ -0,0 +1,58 @@ +# 3 june 2016 + +if(WIN32) + set(_EXAMPLE_RESOURCES_RC resources.rc) +endif() + +macro(_add_example _name) + _add_exec(${_name} ${ARGN}) + # because Microsoft's toolchain is dumb + if(MSVC) + set_property(TARGET ${_name} APPEND_STRING PROPERTY + LINK_FLAGS " /ENTRY:mainCRTStartup") + endif() +endmacro() + +_add_example(controlgallery + controlgallery/main.c + ${_EXAMPLE_RESOURCES_RC} +) + +_add_example(histogram + histogram/main.c + ${_EXAMPLE_RESOURCES_RC} +) + +_add_example(cpp-multithread + cpp-multithread/main.cpp + ${_EXAMPLE_RESOURCES_RC} +) +if(NOT WIN32) + target_link_libraries(cpp-multithread pthread) +endif() + +_add_example(drawtext + drawtext/attributes.c + drawtext/basic.c + drawtext/emptystr_hittest.c + drawtext/hittest.c + drawtext/main.c + ${_EXAMPLE_RESOURCES_RC} +) +target_include_directories(drawtext + PRIVATE drawtext) + +_add_example(opentype + opentype/main.c + ${_EXAMPLE_RESOURCES_RC} +) +target_include_directories(opentype + PRIVATE opentype) + +add_custom_target(examples + DEPENDS + controlgallery + histogram + cpp-multithread + drawtext + opentype) diff --git a/_wip/examples_opentype/main.c b/_wip/examples_opentype/main.c new file mode 100644 index 00000000..0872aaa2 --- /dev/null +++ b/_wip/examples_opentype/main.c @@ -0,0 +1,201 @@ +// 10 june 2017 +#include +#include +#include +#include +#include "../../ui.h" + +// TODO the grid simply flat out does not work on OS X + +uiWindow *mainwin; +uiFontButton *fontButton; +uiEntry *textEntry; +uiCheckbox *nullFeatures; +uiArea *area; + +uiAttributedString *attrstr = NULL; + +static void remakeAttrStr(void) +{ + char *text; + uiOpenTypeFeatures *otf; + uiAttributeSpec spec; + + if (attrstr != NULL) + uiFreeAttributedString(attrstr); + + text = uiEntryText(textEntry); + attrstr = uiNewAttributedString(text); + uiFreeText(text); + + if (!uiCheckboxChecked(nullFeatures)) { + otf = uiNewOpenTypeFeatures(); + // TODO + spec.Type = uiAttributeFeatures; + spec.Features = otf; + uiAttributedStringSetAttribute(attrstr, &spec, + 0, uiAttributedStringLen(attrstr)); + // and uiAttributedString copied otf + uiFreeOpenTypeFeatures(otf); + } + + uiAreaQueueRedrawAll(area); +} + +// TODO make a variable of main()? in all programs? +static uiAreaHandler handler; + +static void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *p) +{ + uiDrawTextLayout *layout; + uiDrawTextLayoutParams lp; + uiDrawFontDescriptor desc; + + memset(&lp, 0, sizeof (uiDrawTextLayoutParams)); + lp.String = attrstr; + uiFontButtonFont(fontButton, &desc); + lp.DefaultFont = &desc; + lp.Width = p->AreaWidth; + lp.Align = uiDrawTextAlignLeft; + layout = uiDrawNewTextLayout(&lp); + uiDrawText(p->Context, layout, 0, 0); + uiDrawFreeTextLayout(layout); +} + +static void handlerMouseEvent(uiAreaHandler *a, uiArea *area, uiAreaMouseEvent *e) +{ + // do nothing +} + +static void handlerMouseCrossed(uiAreaHandler *ah, uiArea *a, int left) +{ + // do nothing +} + +static void handlerDragBroken(uiAreaHandler *ah, uiArea *a) +{ + // do nothing +} + +static int handlerKeyEvent(uiAreaHandler *ah, uiArea *a, uiAreaKeyEvent *e) +{ + // reject all keys + return 0; +} + +static void onFontChanged(uiFontButton *b, void *data) +{ + remakeAttrStr(); +} + +static void onTextChanged(uiEntry *e, void *data) +{ + remakeAttrStr(); +} + +static void onNULLToggled(uiCheckbox *c, void *data) +{ + remakeAttrStr(); +} + +static int onClosing(uiWindow *w, void *data) +{ + // TODO change the others to be like this? (the others destroy here rather than later) + // TODO move this below uiQuit()? + uiControlHide(uiControl(w)); + uiQuit(); + return 0; +} + +static int shouldQuit(void *data) +{ + uiControlDestroy(uiControl(mainwin)); + return 1; +} + +int main(void) +{ + uiInitOptions o; + const char *err; + uiGrid *grid; + uiBox *vbox; + + handler.Draw = handlerDraw; + handler.MouseEvent = handlerMouseEvent; + handler.MouseCrossed = handlerMouseCrossed; + handler.DragBroken = handlerDragBroken; + handler.KeyEvent = handlerKeyEvent; + + memset(&o, 0, sizeof (uiInitOptions)); + err = uiInit(&o); + if (err != NULL) { + fprintf(stderr, "error initializing ui: %s\n", err); + uiFreeInitError(err); + return 1; + } + + uiOnShouldQuit(shouldQuit, NULL); + + // TODO 800x600? the size of the GTK+ example? + mainwin = uiNewWindow("libui OpenType Features Example", 640, 480, 1); + uiWindowSetMargined(mainwin, 1); + uiWindowOnClosing(mainwin, onClosing, NULL); + + grid = uiNewGrid(); + uiGridSetPadded(grid, 1); + uiWindowSetChild(mainwin, uiControl(grid)); + + fontButton = uiNewFontButton(); + uiFontButtonOnChanged(fontButton, onFontChanged, NULL); + uiGridAppend(grid, uiControl(fontButton), + 0, 0, 1, 1, + // TODO are these Y values correct? + 0, uiAlignFill, 0, uiAlignCenter); + + textEntry = uiNewEntry(); + uiEntrySetText(textEntry, "afford afire aflight"); + uiEntryOnChanged(textEntry, onTextChanged, NULL); + uiGridAppend(grid, uiControl(textEntry), + 1, 0, 1, 1, + // TODO are these Y values correct too? + // TODO add a baseline align? or a form align? + 1, uiAlignFill, 0, uiAlignCenter); + + vbox = uiNewVerticalBox(); + uiBoxSetPadded(vbox, 1); + uiGridAppend(grid, uiControl(vbox), + 0, 1, 1, 1, + 0, uiAlignFill, 1, uiAlignFill); + + nullFeatures = uiNewCheckbox("NULL uiOpenTypeFeatures"); + uiCheckboxOnToggled(nullFeatures, onNULLToggled, NULL); + uiBoxAppend(vbox, uiControl(nullFeatures), 0); + + // TODO separator (if other stuff isn't a tab) + + // TODO needed for this to be testable on os x without rewriting everything again + { + int x; + + for (x = 0; x < 10; x++) + uiBoxAppend(vbox, uiControl(uiNewEntry()), 0); + } + + // TODO other stuff + + area = uiNewArea(&handler); + uiGridAppend(grid, uiControl(area), + 1, 1, 1, 1, + 1, uiAlignFill, 1, uiAlignFill); + + // and set up the initial draw + remakeAttrStr(); + + uiControlShow(uiControl(mainwin)); + uiMain(); + + uiControlDestroy(uiControl(mainwin)); + uiFreeAttributedString(attrstr); + uiUninit(); + return 0; +} diff --git a/_wip/manualtvattr b/_wip/manualtvattr deleted file mode 100755 index 4d66634d..00000000 --- a/_wip/manualtvattr +++ /dev/null @@ -1,93 +0,0 @@ -NSScrollView - backgroundColor NSNamedColorSpace System controlBackgroundColor - drawsBackground 1 - borderType 0 - documentCursor - hasHorizontalScroller 0 - hasVerticalScroller 0 - autohidesScrollers 0 - hasHorizontalRuler 0 - hasVerticalRuler 0 - rulersVisible 0 - automaticallyAdjustsContentInsets 1 - contentInsets left:0 top:0 right:0 bottom:0 - scrollerInsets left:0 top:0 right:0 bottom:0 - scrollerKnobStyle 0 - scrollerStyle 1 - lineScroll 10 - horizontalLineScroll 10 - verticalLineScroll 10 - pageScroll 10 - horizontalPageScroll 10 - verticalPageScroll 10 - scrollsDynamically 1 - findBarPosition 1 - usesPredominantAxisScrolling 1 - horizontalScrollElasticity 0 - verticalScrollElasticity 0 - allowsMagnification 0 - magnification 1 - maxMagnification 4 - minMagnification 0.25 - -NSTextView - textContainerInset {0, 0} - textContainerOrigin {0, 0} - backgroundColor NSCalibratedWhiteColorSpace 1 1 - drawsBackground 1 - allowsDocumentBackgroundColorChange 0 - allowedInputSourceLocales (null) - allowsUndo 0 - isEditable 1 - isSelectable 1 - isFieldEditor 0 - isRichText 1 - importsGraphics 0 - defaultParagraphStyle (null) - allowsImageEditing 0 - isAutomaticQuoteSubstitutionEnabled 1 - isAutomaticLinkDetectionEnabled 0 - displaysLinkToolTips 1 - usesRuler 1 - isRulerVisible 0 - usesInspectorBar 0 - selectionAffinity 0 - selectionGranularity 0 - insertionPointColor NSNamedColorSpace System controlTextColor - selectedTextAttributes { - NSBackgroundColor = "NSNamedColorSpace System selectedTextBackgroundColor"; - NSColor = "NSNamedColorSpace System selectedTextColor"; -} - markedTextAttributes { - NSBackgroundColor = "NSCalibratedRGBColorSpace 1 0.866667 0.333333 1"; -} - linkTextAttributes { - NSColor = "NSCalibratedRGBColorSpace 0 0 1 1"; - NSCursor = ""; - NSUnderline = 1; -} - typingAttributes { - NSFont = "\"Helvetica 12.00 pt. P [] (0x60000005f2f0) fobj=0x6000001e4f00, spc=3.33\""; -} - smartInsertDeleteEnabled 1 - isContinuousSpellCheckingEnabled 0 - isGrammarCheckingEnabled 0 - acceptsGlyphInfo 1 - usesFontPanel 1 - usesFindPanel 0 - enabledTextCheckingTypes 961 - isAutomaticDashSubstitutionEnabled 1 - isAutomaticDataDetectionEnabled 0 - isAutomaticSpellingCorrectionEnabled 1 - isAutomaticTextReplacementEnabled 1 - usesFindBar 0 - isIncrementalSearchingEnabled 0 - NSText: - font "Helvetica 12.00 pt. P [] (0x60000005f2f0) fobj=0x6000001e4f00, spc=3.33" - textColor (null) - baseWritingDirection -1 - maxSize {0, 10000000} - minSize {0, 0} - isVerticallyResizable 1 - isHorizontallyResizable 0 - diff --git a/_wip/sv/normal b/_wip/sv/normal new file mode 100644 index 00000000..8b6af87e --- /dev/null +++ b/_wip/sv/normal @@ -0,0 +1,25 @@ +2016-05-26 22:38:12.877 svtest[81790:544681] backgroundColor NSNamedColorSpace System controlColor +2016-05-26 22:38:12.877 svtest[81790:544681] drawsBackground 1 +2016-05-26 22:38:12.877 svtest[81790:544681] borderType 2 +2016-05-26 22:38:12.877 svtest[81790:544681] documentCursor (null) +2016-05-26 22:38:12.877 svtest[81790:544681] hasHorizontalScroller 1 +2016-05-26 22:38:12.877 svtest[81790:544681] hasVerticalScroller 1 +2016-05-26 22:38:12.877 svtest[81790:544681] autohidesScrollers 0 +2016-05-26 22:38:12.877 svtest[81790:544681] hasHorizontalRuler 0 +2016-05-26 22:38:12.878 svtest[81790:544681] hasVerticalRuler 0 +2016-05-26 22:38:12.878 svtest[81790:544681] rulersVisible 0 +2016-05-26 22:38:12.878 svtest[81790:544681] 10.10 autoAdjContentInsets 1 +2016-05-26 22:38:12.878 svtest[81790:544681] scrollerKnobStyle 0 +2016-05-26 22:38:12.878 svtest[81790:544681] scrollerStyle 1 +2016-05-26 22:38:12.878 svtest[81790:544681] horizontalLineScroll 10 +2016-05-26 22:38:12.878 svtest[81790:544681] verticalLineScroll 10 +2016-05-26 22:38:12.886 svtest[81790:544681] horizontalPageScroll 10 +2016-05-26 22:38:12.886 svtest[81790:544681] verticalPageScroll 10 +2016-05-26 22:38:12.886 svtest[81790:544681] scrollsDynamically 1 +2016-05-26 22:38:12.886 svtest[81790:544681] findBarPosition 1 +2016-05-26 22:38:12.886 svtest[81790:544681] usesPredomAxisScroll 0 +2016-05-26 22:38:12.886 svtest[81790:544681] horizontalElasticity 0 +2016-05-26 22:38:12.886 svtest[81790:544681] verticalElasticity 0 +2016-05-26 22:38:12.887 svtest[81790:544681] 10.8 allowsMagnification 0 +2016-05-26 22:38:12.887 svtest[81790:544681] 10.8 maxMagnification 4 +2016-05-26 22:38:12.887 svtest[81790:544681] 10.8 minMagnification 0.25 diff --git a/_wip/sv/normal.nots b/_wip/sv/normal.nots new file mode 100644 index 00000000..411d1d6d --- /dev/null +++ b/_wip/sv/normal.nots @@ -0,0 +1,25 @@ + backgroundColor NSNamedColorSpace System controlColor + drawsBackground 1 + borderType 2 + documentCursor (null) + hasHorizontalScroller 1 + hasVerticalScroller 1 + autohidesScrollers 0 + hasHorizontalRuler 0 + hasVerticalRuler 0 + rulersVisible 0 + 10.10 autoAdjContentInsets 1 + scrollerKnobStyle 0 + scrollerStyle 1 + horizontalLineScroll 10 + verticalLineScroll 10 + horizontalPageScroll 10 + verticalPageScroll 10 + scrollsDynamically 1 + findBarPosition 1 + usesPredomAxisScroll 0 + horizontalElasticity 0 + verticalElasticity 0 + 10.8 allowsMagnification 0 + 10.8 maxMagnification 4 + 10.8 minMagnification 0.25 diff --git a/_wip/sv/outlineview b/_wip/sv/outlineview new file mode 100644 index 00000000..67a30877 --- /dev/null +++ b/_wip/sv/outlineview @@ -0,0 +1,25 @@ +2016-05-26 22:42:16.208 svtest[82103:547159] backgroundColor NSNamedColorSpace System controlBackgroundColor +2016-05-26 22:42:16.208 svtest[82103:547159] drawsBackground 1 +2016-05-26 22:42:16.208 svtest[82103:547159] borderType 2 +2016-05-26 22:42:16.208 svtest[82103:547159] documentCursor (null) +2016-05-26 22:42:16.209 svtest[82103:547159] hasHorizontalScroller 1 +2016-05-26 22:42:16.209 svtest[82103:547159] hasVerticalScroller 1 +2016-05-26 22:42:16.209 svtest[82103:547159] autohidesScrollers 1 +2016-05-26 22:42:16.209 svtest[82103:547159] hasHorizontalRuler 0 +2016-05-26 22:42:16.209 svtest[82103:547159] hasVerticalRuler 0 +2016-05-26 22:42:16.209 svtest[82103:547159] rulersVisible 0 +2016-05-26 22:42:16.209 svtest[82103:547159] 10.10 autoAdjContentInsets 1 +2016-05-26 22:42:16.209 svtest[82103:547159] scrollerKnobStyle 0 +2016-05-26 22:42:16.209 svtest[82103:547159] scrollerStyle 1 +2016-05-26 22:42:16.209 svtest[82103:547159] horizontalLineScroll 19 +2016-05-26 22:42:16.209 svtest[82103:547159] verticalLineScroll 19 +2016-05-26 22:42:16.217 svtest[82103:547159] horizontalPageScroll 10 +2016-05-26 22:42:16.218 svtest[82103:547159] verticalPageScroll 10 +2016-05-26 22:42:16.218 svtest[82103:547159] scrollsDynamically 1 +2016-05-26 22:42:16.218 svtest[82103:547159] findBarPosition 1 +2016-05-26 22:42:16.218 svtest[82103:547159] usesPredomAxisScroll 0 +2016-05-26 22:42:16.218 svtest[82103:547159] horizontalElasticity 0 +2016-05-26 22:42:16.218 svtest[82103:547159] verticalElasticity 0 +2016-05-26 22:42:16.218 svtest[82103:547159] 10.8 allowsMagnification 0 +2016-05-26 22:42:16.218 svtest[82103:547159] 10.8 maxMagnification 4 +2016-05-26 22:42:16.218 svtest[82103:547159] 10.8 minMagnification 0.25 diff --git a/_wip/sv/outlineview.nots b/_wip/sv/outlineview.nots new file mode 100644 index 00000000..fcf18493 --- /dev/null +++ b/_wip/sv/outlineview.nots @@ -0,0 +1,25 @@ + backgroundColor NSNamedColorSpace System controlBackgroundColor + drawsBackground 1 + borderType 2 + documentCursor (null) + hasHorizontalScroller 1 + hasVerticalScroller 1 + autohidesScrollers 1 + hasHorizontalRuler 0 + hasVerticalRuler 0 + rulersVisible 0 + 10.10 autoAdjContentInsets 1 + scrollerKnobStyle 0 + scrollerStyle 1 + horizontalLineScroll 19 + verticalLineScroll 19 + horizontalPageScroll 10 + verticalPageScroll 10 + scrollsDynamically 1 + findBarPosition 1 + usesPredomAxisScroll 0 + horizontalElasticity 0 + verticalElasticity 0 + 10.8 allowsMagnification 0 + 10.8 maxMagnification 4 + 10.8 minMagnification 0.25 diff --git a/_wip/sv/sourcelist b/_wip/sv/sourcelist new file mode 100644 index 00000000..010d6525 --- /dev/null +++ b/_wip/sv/sourcelist @@ -0,0 +1,25 @@ +2016-05-26 22:43:58.600 svtest[82237:548359] backgroundColor (null) +2016-05-26 22:43:58.600 svtest[82237:548359] drawsBackground 0 +2016-05-26 22:43:58.600 svtest[82237:548359] borderType 2 +2016-05-26 22:43:58.600 svtest[82237:548359] documentCursor (null) +2016-05-26 22:43:58.600 svtest[82237:548359] hasHorizontalScroller 1 +2016-05-26 22:43:58.600 svtest[82237:548359] hasVerticalScroller 1 +2016-05-26 22:43:58.600 svtest[82237:548359] autohidesScrollers 1 +2016-05-26 22:43:58.600 svtest[82237:548359] hasHorizontalRuler 0 +2016-05-26 22:43:58.600 svtest[82237:548359] hasVerticalRuler 0 +2016-05-26 22:43:58.600 svtest[82237:548359] rulersVisible 0 +2016-05-26 22:43:58.601 svtest[82237:548359] 10.10 autoAdjContentInsets 1 +2016-05-26 22:43:58.601 svtest[82237:548359] scrollerKnobStyle 0 +2016-05-26 22:43:58.601 svtest[82237:548359] scrollerStyle 1 +2016-05-26 22:43:58.601 svtest[82237:548359] horizontalLineScroll 19 +2016-05-26 22:43:58.601 svtest[82237:548359] verticalLineScroll 19 +2016-05-26 22:43:58.645 svtest[82237:548359] horizontalPageScroll 10 +2016-05-26 22:43:58.645 svtest[82237:548359] verticalPageScroll 10 +2016-05-26 22:43:58.645 svtest[82237:548359] scrollsDynamically 1 +2016-05-26 22:43:58.645 svtest[82237:548359] findBarPosition 1 +2016-05-26 22:43:58.645 svtest[82237:548359] usesPredomAxisScroll 0 +2016-05-26 22:43:58.645 svtest[82237:548359] horizontalElasticity 0 +2016-05-26 22:43:58.646 svtest[82237:548359] verticalElasticity 0 +2016-05-26 22:43:58.646 svtest[82237:548359] 10.8 allowsMagnification 0 +2016-05-26 22:43:58.646 svtest[82237:548359] 10.8 maxMagnification 4 +2016-05-26 22:43:58.646 svtest[82237:548359] 10.8 minMagnification 0.25 diff --git a/_wip/sv/sourcelist.nots b/_wip/sv/sourcelist.nots new file mode 100644 index 00000000..742f41ea --- /dev/null +++ b/_wip/sv/sourcelist.nots @@ -0,0 +1,25 @@ + backgroundColor (null) + drawsBackground 0 + borderType 2 + documentCursor (null) + hasHorizontalScroller 1 + hasVerticalScroller 1 + autohidesScrollers 1 + hasHorizontalRuler 0 + hasVerticalRuler 0 + rulersVisible 0 + 10.10 autoAdjContentInsets 1 + scrollerKnobStyle 0 + scrollerStyle 1 + horizontalLineScroll 19 + verticalLineScroll 19 + horizontalPageScroll 10 + verticalPageScroll 10 + scrollsDynamically 1 + findBarPosition 1 + usesPredomAxisScroll 0 + horizontalElasticity 0 + verticalElasticity 0 + 10.8 allowsMagnification 0 + 10.8 maxMagnification 4 + 10.8 minMagnification 0.25 diff --git a/_wip/sv/tableview b/_wip/sv/tableview new file mode 100644 index 00000000..558b6e19 --- /dev/null +++ b/_wip/sv/tableview @@ -0,0 +1,25 @@ +2016-05-26 22:41:26.514 svtest[82032:546554] backgroundColor NSNamedColorSpace System controlBackgroundColor +2016-05-26 22:41:26.514 svtest[82032:546554] drawsBackground 1 +2016-05-26 22:41:26.514 svtest[82032:546554] borderType 2 +2016-05-26 22:41:26.514 svtest[82032:546554] documentCursor (null) +2016-05-26 22:41:26.515 svtest[82032:546554] hasHorizontalScroller 1 +2016-05-26 22:41:26.515 svtest[82032:546554] hasVerticalScroller 1 +2016-05-26 22:41:26.515 svtest[82032:546554] autohidesScrollers 1 +2016-05-26 22:41:26.515 svtest[82032:546554] hasHorizontalRuler 0 +2016-05-26 22:41:26.516 svtest[82032:546554] hasVerticalRuler 0 +2016-05-26 22:41:26.516 svtest[82032:546554] rulersVisible 0 +2016-05-26 22:41:26.516 svtest[82032:546554] 10.10 autoAdjContentInsets 1 +2016-05-26 22:41:26.516 svtest[82032:546554] scrollerKnobStyle 0 +2016-05-26 22:41:26.516 svtest[82032:546554] scrollerStyle 1 +2016-05-26 22:41:26.516 svtest[82032:546554] horizontalLineScroll 19 +2016-05-26 22:41:26.516 svtest[82032:546554] verticalLineScroll 19 +2016-05-26 22:41:26.528 svtest[82032:546554] horizontalPageScroll 10 +2016-05-26 22:41:26.528 svtest[82032:546554] verticalPageScroll 10 +2016-05-26 22:41:26.528 svtest[82032:546554] scrollsDynamically 1 +2016-05-26 22:41:26.528 svtest[82032:546554] findBarPosition 1 +2016-05-26 22:41:26.528 svtest[82032:546554] usesPredomAxisScroll 0 +2016-05-26 22:41:26.528 svtest[82032:546554] horizontalElasticity 0 +2016-05-26 22:41:26.528 svtest[82032:546554] verticalElasticity 0 +2016-05-26 22:41:26.528 svtest[82032:546554] 10.8 allowsMagnification 0 +2016-05-26 22:41:26.528 svtest[82032:546554] 10.8 maxMagnification 4 +2016-05-26 22:41:26.528 svtest[82032:546554] 10.8 minMagnification 0.25 diff --git a/_wip/sv/tableview.nots b/_wip/sv/tableview.nots new file mode 100644 index 00000000..fcf18493 --- /dev/null +++ b/_wip/sv/tableview.nots @@ -0,0 +1,25 @@ + backgroundColor NSNamedColorSpace System controlBackgroundColor + drawsBackground 1 + borderType 2 + documentCursor (null) + hasHorizontalScroller 1 + hasVerticalScroller 1 + autohidesScrollers 1 + hasHorizontalRuler 0 + hasVerticalRuler 0 + rulersVisible 0 + 10.10 autoAdjContentInsets 1 + scrollerKnobStyle 0 + scrollerStyle 1 + horizontalLineScroll 19 + verticalLineScroll 19 + horizontalPageScroll 10 + verticalPageScroll 10 + scrollsDynamically 1 + findBarPosition 1 + usesPredomAxisScroll 0 + horizontalElasticity 0 + verticalElasticity 0 + 10.8 allowsMagnification 0 + 10.8 maxMagnification 4 + 10.8 minMagnification 0.25 diff --git a/_wip/sv/textview b/_wip/sv/textview new file mode 100644 index 00000000..e6363625 --- /dev/null +++ b/_wip/sv/textview @@ -0,0 +1,25 @@ +2016-05-26 22:40:02.050 svtest[81927:545793] backgroundColor NSCalibratedWhiteColorSpace 1 1 +2016-05-26 22:40:02.050 svtest[81927:545793] drawsBackground 1 +2016-05-26 22:40:02.051 svtest[81927:545793] borderType 2 +2016-05-26 22:40:02.052 svtest[81927:545793] documentCursor +2016-05-26 22:40:02.052 svtest[81927:545793] hasHorizontalScroller 0 +2016-05-26 22:40:02.052 svtest[81927:545793] hasVerticalScroller 1 +2016-05-26 22:40:02.052 svtest[81927:545793] autohidesScrollers 0 +2016-05-26 22:40:02.052 svtest[81927:545793] hasHorizontalRuler 0 +2016-05-26 22:40:02.052 svtest[81927:545793] hasVerticalRuler 0 +2016-05-26 22:40:02.052 svtest[81927:545793] rulersVisible 0 +2016-05-26 22:40:02.052 svtest[81927:545793] 10.10 autoAdjContentInsets 1 +2016-05-26 22:40:02.052 svtest[81927:545793] scrollerKnobStyle 0 +2016-05-26 22:40:02.052 svtest[81927:545793] scrollerStyle 1 +2016-05-26 22:40:02.054 svtest[81927:545793] horizontalLineScroll 10 +2016-05-26 22:40:02.055 svtest[81927:545793] verticalLineScroll 10 +2016-05-26 22:40:02.062 svtest[81927:545793] horizontalPageScroll 10 +2016-05-26 22:40:02.062 svtest[81927:545793] verticalPageScroll 10 +2016-05-26 22:40:02.062 svtest[81927:545793] scrollsDynamically 1 +2016-05-26 22:40:02.062 svtest[81927:545793] findBarPosition 1 +2016-05-26 22:40:02.062 svtest[81927:545793] usesPredomAxisScroll 0 +2016-05-26 22:40:02.062 svtest[81927:545793] horizontalElasticity 0 +2016-05-26 22:40:02.062 svtest[81927:545793] verticalElasticity 0 +2016-05-26 22:40:02.062 svtest[81927:545793] 10.8 allowsMagnification 0 +2016-05-26 22:40:02.062 svtest[81927:545793] 10.8 maxMagnification 4 +2016-05-26 22:40:02.063 svtest[81927:545793] 10.8 minMagnification 0.25 diff --git a/_wip/sv/textview.nots b/_wip/sv/textview.nots new file mode 100644 index 00000000..7476b0ea --- /dev/null +++ b/_wip/sv/textview.nots @@ -0,0 +1,25 @@ + backgroundColor NSCalibratedWhiteColorSpace 1 1 + drawsBackground 1 + borderType 2 + documentCursor + hasHorizontalScroller 0 + hasVerticalScroller 1 + autohidesScrollers 0 + hasHorizontalRuler 0 + hasVerticalRuler 0 + rulersVisible 0 + 10.10 autoAdjContentInsets 1 + scrollerKnobStyle 0 + scrollerStyle 1 + horizontalLineScroll 10 + verticalLineScroll 10 + horizontalPageScroll 10 + verticalPageScroll 10 + scrollsDynamically 1 + findBarPosition 1 + usesPredomAxisScroll 0 + horizontalElasticity 0 + verticalElasticity 0 + 10.8 allowsMagnification 0 + 10.8 maxMagnification 4 + 10.8 minMagnification 0.25 diff --git a/_wip/xtvattr b/_wip/xtvattr deleted file mode 100755 index dab92ac0..00000000 --- a/_wip/xtvattr +++ /dev/null @@ -1,92 +0,0 @@ -NSScrollView - backgroundColor NSNamedColorSpace System controlBackgroundColor - drawsBackground 1 - borderType 2 - documentCursor - hasHorizontalScroller 0 - hasVerticalScroller 1 - autohidesScrollers 1 - hasHorizontalRuler 0 - hasVerticalRuler 0 - rulersVisible 0 - automaticallyAdjustsContentInsets 1 - contentInsets left:0 top:0 right:0 bottom:0 - scrollerInsets left:0 top:0 right:0 bottom:0 - scrollerKnobStyle 0 - scrollerStyle 1 - lineScroll 10 - horizontalLineScroll 10 - verticalLineScroll 10 - pageScroll 10 - horizontalPageScroll 10 - verticalPageScroll 10 - scrollsDynamically 1 - findBarPosition 1 - usesPredominantAxisScrolling 1 - horizontalScrollElasticity 0 - verticalScrollElasticity 0 - allowsMagnification 0 - magnification 1 - maxMagnification 4 - minMagnification 0.25 - -NSTextView - textContainerInset {0, 0} - textContainerOrigin {0, 0} - backgroundColor NSNamedColorSpace System textBackgroundColor - drawsBackground 1 - allowsDocumentBackgroundColorChange 0 - allowedInputSourceLocales (null) - allowsUndo 1 - isEditable 1 - isSelectable 1 - isFieldEditor 0 - isRichText 0 - importsGraphics 0 - defaultParagraphStyle (null) - allowsImageEditing 0 - isAutomaticQuoteSubstitutionEnabled 0 - isAutomaticLinkDetectionEnabled 0 - displaysLinkToolTips 1 - usesRuler 0 - isRulerVisible 0 - usesInspectorBar 0 - selectionAffinity 0 - selectionGranularity 0 - insertionPointColor NSNamedColorSpace System controlTextColor - selectedTextAttributes { - NSBackgroundColor = "NSNamedColorSpace System selectedTextBackgroundColor"; - NSColor = "NSNamedColorSpace System selectedTextColor"; -} - markedTextAttributes { - NSBackgroundColor = "NSCalibratedRGBColorSpace 1 0.866667 0.333333 1"; -} - linkTextAttributes { - NSColor = "NSCalibratedRGBColorSpace 0 0 1 1"; - NSCursor = ""; - NSUnderline = 1; -} - typingAttributes { - NSFont = "\".HelveticaNeueDeskInterface-Regular 13.00 pt. P [] (0x7f886b4bc6a0) fobj=0x7f886b474d90, spc=3.94\""; -} - smartInsertDeleteEnabled 0 - isContinuousSpellCheckingEnabled 0 - isGrammarCheckingEnabled 0 - acceptsGlyphInfo 0 - usesFontPanel 0 - usesFindPanel 0 - enabledTextCheckingTypes 0 - isAutomaticDashSubstitutionEnabled 0 - isAutomaticDataDetectionEnabled 0 - isAutomaticSpellingCorrectionEnabled 0 - isAutomaticTextReplacementEnabled 0 - usesFindBar 0 - isIncrementalSearchingEnabled 0 - NSText: - font ".HelveticaNeueDeskInterface-Regular 13.00 pt. P [] (0x7f886b4bc6a0) fobj=0x7f886b474d90, spc=3.94" - textColor (null) - baseWritingDirection -1 - maxSize {1, 10000000} - minSize {1, 1} - isVerticallyResizable 1 - isHorizontallyResizable 0 diff --git a/build/GNUbasegcc.mk b/build/GNUbasegcc.mk deleted file mode 100644 index dfbfb6c3..00000000 --- a/build/GNUbasegcc.mk +++ /dev/null @@ -1,79 +0,0 @@ -# 16 october 2015 - -# TODO the loader looks for the soname, not the base name, which is frustrating - -# Global flags. - -CFLAGS += \ - -fPIC \ - -Wall -Wextra -pedantic \ - -Wno-unused-parameter \ - -Wno-switch \ - --std=c99 - -# C++11 is needed due to stupid rules involving commas at the end of enum lists that C++03 stupidly didn't follow -# This means sorry, no GCC 2 for Haiku builds :( -CXXFLAGS += \ - -fPIC \ - -Wall -Wextra -pedantic \ - -Wno-unused-parameter \ - -Wno-switch \ - --std=c++11 - -LDFLAGS += \ - -fPIC - -ifneq ($(NODEBUG),1) - CFLAGS += -g - CXXFLAGS += -g - LDFLAGS += -g -endif - -# Build rules. - -OFILES = \ - $(subst /,_,$(CFILES)) \ - $(subst /,_,$(CXXFILES)) \ - $(subst /,_,$(MFILES)) \ - $(subst /,_,$(RCFILES)) - -OFILES := $(OFILES:%=$(OBJDIR)/%.o) - -OUT = $(OUTDIR)/$(NAME)$(SUFFIX) -OUTNOSONAME = $(OUTDIR)/$(NAME)$(LIBSUFFIX) - -# TODO allow using LD -# LD is defined by default so we need a way to override the default define without blocking a user define -ifeq ($(CXXFILES),) - reallinker = $(CC) -else - reallinker = $(CXX) -endif - -$(OUT): $(OFILES) | $(OUTDIR) - @$(reallinker) -o $(OUT) $(OFILES) $(LDFLAGS) -ifeq ($(USESSONAME),1) - @ln -sf $(NAME)$(SUFFIX) $(OUTNOSONAME) -endif - @echo ====== Linked $(OUT) - -.SECONDEXPANSION: - -$(OBJDIR)/%.c.o: $$(subst _,/,%).c $(HFILES) | $(OBJDIR) - @$(CC) -o $@ -c $< $(CFLAGS) - @echo ====== Compiled $< - -$(OBJDIR)/%.cpp.o: $$(subst _,/,%).cpp $(HFILES) | $(OBJDIR) - @$(CXX) -o $@ -c $< $(CXXFLAGS) - @echo ====== Compiled $< - -$(OBJDIR)/%.m.o: $$(subst _,/,%).m $(HFILES) | $(OBJDIR) - @$(CC) -o $@ -c $< $(CFLAGS) - @echo ====== Compiled $< - -$(OBJDIR)/%.rc.o: $$(subst _,/,%).rc $(HFILES) | $(OBJDIR) - @$(RC) $(RCFLAGS) $< $@ - @echo ====== Compiled $< - -$(OBJDIR) $(OUTDIR): - @mkdir -p $@ diff --git a/build/GNUbasemsvc.mk b/build/GNUbasemsvc.mk deleted file mode 100644 index 69bcbe8c..00000000 --- a/build/GNUbasemsvc.mk +++ /dev/null @@ -1,99 +0,0 @@ -# 16 october 2015 - -# IMPORTANT -# Do NOT use / for command-line options here! -# This breaks on GNU makes that come with some versions of -# MinGW because they mangle things that start with /, thinking that -# those arguments are Unix paths that need to be converted to -# Windows paths. This cannot be turned off. -_-' -# MSDN says cl, rc, and link all accept - instead of /, so we're good. -# See also: -# - https://github.com/andlabs/libui/issues/16 -# - http://www.mingw.org/wiki/Posix_path_conversion -# - http://www.mingw.org/wiki/FAQ -# - http://stackoverflow.com/questions/7250130/how-to-stop-mingw-and-msys-from-mangling-path-names-given-at-the-command-line -# - http://stackoverflow.com/questions/28533664/how-to-prevent-msys-to-convert-the-file-path-for-an-external-program - -# TODO subsystem version - -# TODO silence compiler non-diagnostics (/nologo is not enough) - -# Global flags. - -# TODO /Wall does too much -# TODO -Wno-switch equivalent -# TODO /sdl turns C4996 into an ERROR -# TODO loads of warnings in the system header files -# TODO /analyze requires us to write annotations everywhere -# TODO undecided flags from qo? -CFLAGS += \ - -W4 \ - -wd4100 \ - -TC \ - -bigobj -nologo \ - -RTC1 -RTCc -RTCs -RTCu - -# TODO prune these -# -EHsc is to shut the compiler up in some cases -CXXFLAGS += \ - -W4 \ - -wd4100 \ - -TP \ - -bigobj -nologo \ - -RTC1 -RTCc -RTCs -RTCu \ - -EHsc - -# TODO warnings on undefined symbols -LDFLAGS += \ - -largeaddressaware -nologo -incremental:no - -ifneq ($(NODEBUG),1) - CFLAGS += -Zi - CXXFLAGS += -Zi - LDFLAGS += -debug -endif - -# Build rules. - -OFILES = \ - $(subst /,_,$(CFILES)) \ - $(subst /,_,$(CXXFILES)) \ - $(subst /,_,$(MFILES)) \ - $(subst /,_,$(RCFILES)) - -OFILES := $(OFILES:%=$(OBJDIR)/%.o) - -OUT = $(OUTDIR)/$(NAME)$(SUFFIX) - -# TODO use $(CC), $(CXX), $(LD), and s$(RC) - -$(OUT): $(OFILES) | $(OUTDIR) - @link -out:$(OUT) $(OFILES) $(LDFLAGS) - @echo ====== Linked $(OUT) - -.SECONDEXPANSION: - -# TODO can we put /Fd$@.pdb in a variable? -$(OBJDIR)/%.c.o: $$(subst _,/,%).c $(HFILES) | $(OBJDIR) -ifeq ($(NODEBUG),1) - @cl -Fo:$@ -c $< $(CFLAGS) -else - @cl -Fo:$@ -c $< $(CFLAGS) -Fd$@.pdb -endif - @echo ====== Compiled $< - -$(OBJDIR)/%.cpp.o: $$(subst _,/,%).cpp $(HFILES) | $(OBJDIR) -ifeq ($(NODEBUG),1) - @cl -Fo:$@ -c $< $(CXXFLAGS) -else - @cl -Fo:$@ -c $< $(CXXFLAGS) -Fd$@.pdb -endif - @echo ====== Compiled $< - -# note: don't run cvtres directly; the linker does that for us -$(OBJDIR)/%.rc.o: $$(subst _,/,%).rc $(HFILES) | $(OBJDIR) - @rc -nologo -v -fo $@ $< - @echo ====== Compiled $< - -$(OBJDIR) $(OUTDIR): - @mkdir $@ diff --git a/build/GNUmakefile.example b/build/GNUmakefile.example deleted file mode 100644 index f2653865..00000000 --- a/build/GNUmakefile.example +++ /dev/null @@ -1,48 +0,0 @@ -# 16 october 2015 - -ifndef inlibuibuild -$(error Do not run these makefiles directly.) -endif -ifndef EXAMPLE -$(error You must specify an example to build by adding EXAMPLE=name to the command line.) -endif - -include $(OS)/GNUosspecific.mk - -ifneq ($(findstring cpp-,$(EXAMPLE)),) -CXXFILES += \ - examples/$(EXAMPLE)/main.cpp -else -CFILES += \ - examples/$(EXAMPLE)/main.c -endif - -HFILES += \ - ui.h - -ifeq ($(OS),windows) -RCFILES += \ - examples/resources.rc -endif - -NAME = $(EXAMPLE) -SUFFIX = $(EXESUFFIX) - -# TODO merge with the one in build/GNUmakefile.test -ifeq ($(TOOLCHAIN),gcc) - LDFLAGS += -L$(OUTDIR) -lui - # see build/GNUmakefile.test - ifeq ($(OS),darwin) - LDFLAGS += -Wl,-rpath,@executable_path - else - LDFLAGS += -Wl,-rpath,'$$ORIGIN' - endif -else - # TODO is there an equivalent to -L? - LDFLAGS += $(OUTDIR)/libui.lib -endif - -# executables are not shared libraries -USESSONAME = 0 - -include build/GNUbase$(TOOLCHAIN).mk diff --git a/build/GNUmakefile.libui b/build/GNUmakefile.libui deleted file mode 100644 index 393f054f..00000000 --- a/build/GNUmakefile.libui +++ /dev/null @@ -1,53 +0,0 @@ -# 16 october 2015 - -ifndef inlibuibuild -$(error Do not run these makefiles directly.) -endif - -# for GCC -SOVERSION0 = 0 -SOVERSIONA = A - -include $(OS)/GNUosspecific.mk -include common/GNUfiles.mk -include $(OS)/GNUfiles.mk - -HFILES += \ - ui.h \ - ui_$(OS)$(OSHSUFFIX) - -NAME = libui -SUFFIX = $(LIBSUFFIX) -ifeq ($(USESSONAME),1) - SUFFIX = $(SONAMEEXT) -endif - -ifeq ($(TOOLCHAIN),gcc) - # make every symbol hidden by default except _UI_EXTERN ones - # thanks ebassi in irc.gimp.net/#gtk+ - CFLAGS += \ - -D_UI_EXTERN='__attribute__((visibility("default"))) extern' \ - -fvisibility=hidden - CXXFLAGS += \ - -D_UI_EXTERN='__attribute__((visibility("default"))) extern' \ - -fvisibility=hidden - LDFLAGS += \ - -fvisibility=hidden -else - # make every symbol hidden by default except _UI_EXTERN ones - # TODO autogenerate a .def file? - CFLAGS += \ - -D "_UI_EXTERN=__declspec(dllexport) extern" - CXXFLAGS += \ - -D "_UI_EXTERN=__declspec(dllexport) extern" -endif - -ifeq ($(USESSONAME),1) - LDFLAGS += $(SONAMEFLAG)$(NAME)$(SUFFIX) -endif - -include build/GNUbase$(TOOLCHAIN).mk - -# install rule is OS specific -# TODO probably better off making it a toolchain-wide rule -include $(OS)/GNUinstall.mk diff --git a/build/GNUmakefile.test b/build/GNUmakefile.test deleted file mode 100644 index 6b992021..00000000 --- a/build/GNUmakefile.test +++ /dev/null @@ -1,34 +0,0 @@ -# 16 october 2015 - -ifndef inlibuibuild -$(error Do not run these makefiles directly.) -endif - -include $(OS)/GNUosspecific.mk -include test/GNUfiles.mk - -HFILES += \ - ui.h - -NAME = test -SUFFIX = $(EXESUFFIX) - -ifeq ($(TOOLCHAIN),gcc) - LDFLAGS += -L$(OUTDIR) -lui - # tell the dynamic loader to search in the executable path for shared objects - # note: OS X's linker complains if we say -rpath= instead of -rpath, - # also note that OS X doesn't use $ORIGIN - see http://jorgen.tjer.no/post/2014/05/20/dt-rpath-ld-and-at-rpath-dyld/ - ifeq ($(OS),darwin) - LDFLAGS += -Wl,-rpath,@executable_path - else - LDFLAGS += -Wl,-rpath,'$$ORIGIN' - endif -else - # TODO is there an equivalent to -L? - LDFLAGS += $(OUTDIR)/libui.lib -endif - -# executables are not shared libraries -USESSONAME = 0 - -include build/GNUbase$(TOOLCHAIN).mk diff --git a/buildnotes b/buildnotes deleted file mode 100644 index bc33bd1b..00000000 --- a/buildnotes +++ /dev/null @@ -1,42 +0,0 @@ -HOW TO BUILD -Simply type - - make [variables...] - -The build-time settings are - - OS=xxx - default operating system - supported OSs are - windows - unix - darwin - haiku - this is autodetected by default - CFLAGS=xxx - CXXFLAGS=xxx - LDFLAGS=xxx - compiler flags - this is where you can specify -m32 or -m64, for instance - Objective-C uses $(CFLAGS) - NODEBUG=xxx - set to 1 to disable debug symbols - must be 1, any other value means "include debug symbols" - PREFIX=xxx - TODO - DESTDIR=xxx - applied before PREFIX; used by Debian - -To build the test program use - - make [variables....] test - -The variables are the same as above. - -To build an example, use - - make EXAMPLE=name [variables...] example - -You must specify the example name. - -TODO discuss the internals diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt new file mode 100644 index 00000000..0e1360d8 --- /dev/null +++ b/common/CMakeLists.txt @@ -0,0 +1,23 @@ +# 3 june 2016 + +list(APPEND _LIBUI_SOURCES + common/attribute.c + common/attrlist.c + common/attrstr.c + common/areaevents.c + common/control.c + common/debug.c + common/matrix.c + common/opentype.c + common/shouldquit.c + common/tablemodel.c + common/tablevalue.c + common/userbugs.c + common/utf.c +) +set(_LIBUI_SOURCES ${_LIBUI_SOURCES} PARENT_SCOPE) + +list(APPEND _LIBUI_INCLUDEDIRS + common +) +set(_LIBUI_INCLUDEDIRS ${_LIBUI_INCLUDEDIRS} PARENT_SCOPE) diff --git a/common/GNUfiles.mk b/common/GNUfiles.mk deleted file mode 100644 index 14416f4e..00000000 --- a/common/GNUfiles.mk +++ /dev/null @@ -1,11 +0,0 @@ -# 16 october 2015 - -CFILES += \ - common/areaevents.c \ - common/control.c \ - common/matrix.c \ - common/shouldquit.c - -HFILES += \ - common/controlsigs.h \ - common/uipriv.h diff --git a/common/OLD_table.c b/common/OLD_table.c new file mode 100644 index 00000000..3726883a --- /dev/null +++ b/common/OLD_table.c @@ -0,0 +1,22 @@ +// 21 june 2016 +#include "../ui.h" +#include "uipriv.h" + +void *uiTableModelGiveInt(int i) +{ + return (void *) ((intptr_t) i); +} + +int uiTableModelTakeInt(void *v) +{ + return (int) ((intptr_t) v); +} + +uiTableColumn *uiTableAppendTextColumn(uiTable *t, const char *name, int modelColumn) +{ + uiTableColumn *tc; + + tc = uiTableAppendColumn(t, name); + uiTableColumnAppendTextPart(tc, modelColumn, 1); + return tc; +} diff --git a/common/areaevents.c b/common/areaevents.c index c4abf102..491a7283 100644 --- a/common/areaevents.c +++ b/common/areaevents.c @@ -10,11 +10,13 @@ For GTK+, we pull the double-click time and double-click distance, which work th On GTK+ this will also allow us to discard the GDK_BUTTON_2PRESS and GDK_BUTTON_3PRESS events, so the button press stream will be just like on other platforms. Thanks to mclasen, garnacho_, halfline, and tristan in irc.gimp.net/#gtk+. + +TODO note the bits about asymmetry and g_rcClick initial value not mattering in the oldnewthing article */ // x, y, xdist, ydist, and c.rect must have the same units // so must time, maxTime, and c.prevTime -uintmax_t clickCounterClick(clickCounter *c, uintmax_t button, intmax_t x, intmax_t y, uintptr_t time, uintptr_t maxTime, intmax_t xdist, intmax_t ydist) +int uiprivClickCounterClick(uiprivClickCounter *c, int button, int x, int y, uintptr_t time, uintptr_t maxTime, int32_t xdist, int32_t ydist) { // different button than before? if so, don't count if (button != c->curButton) @@ -48,7 +50,7 @@ uintmax_t clickCounterClick(clickCounter *c, uintmax_t button, intmax_t x, intma return c->count; } -void clickCounterReset(clickCounter *c) +void uiprivClickCounterReset(uiprivClickCounter *c) { c->curButton = 0; c->rectX0 = 0; @@ -149,7 +151,7 @@ static const struct { { 0xFFFF, 0 }, }; -int fromScancode(uintptr_t scancode, uiAreaKeyEvent *ke) +int uiprivFromScancode(uintptr_t scancode, uiAreaKeyEvent *ke) { int i; diff --git a/common/attribute.c b/common/attribute.c new file mode 100644 index 00000000..4a8f0160 --- /dev/null +++ b/common/attribute.c @@ -0,0 +1,266 @@ +// 19 february 2018 +#include "../ui.h" +#include "uipriv.h" +#include "attrstr.h" + +struct uiAttribute { + int ownedByUser; + size_t refcount; + uiAttributeType type; + union { + char *family; + double size; + uiTextWeight weight; + uiTextItalic italic; + uiTextStretch stretch; + struct { + double r; + double g; + double b; + double a; + // put this here so we can reuse this structure + uiUnderlineColor underlineColor; + } color; + uiUnderline underline; + uiOpenTypeFeatures *features; + } u; +}; + +static uiAttribute *newAttribute(uiAttributeType type) +{ + uiAttribute *a; + + a = uiprivNew(uiAttribute); + a->ownedByUser = 1; + a->refcount = 0; + a->type = type; + return a; +} + +// returns a to allow expressions like b = uiprivAttributeRetain(a) +// TODO would this allow us to copy attributes between strings in a foreach func, and if so, should that be allowed? +uiAttribute *uiprivAttributeRetain(uiAttribute *a) +{ + a->ownedByUser = 0; + a->refcount++; + return a; +} + +static void destroy(uiAttribute *a) +{ + switch (a->type) { + case uiAttributeTypeFamily: + uiprivFree(a->u.family); + break; + case uiAttributeTypeFeatures: + uiFreeOpenTypeFeatures(a->u.features); + break; + } + uiprivFree(a); +} + +void uiprivAttributeRelease(uiAttribute *a) +{ + if (a->ownedByUser) + /* TODO implementation bug: we can't release an attribute we don't own */; + a->refcount--; + if (a->refcount == 0) + destroy(a); +} + +void uiFreeAttribute(uiAttribute *a) +{ + if (!a->ownedByUser) + /* TODO user bug: you can't free an attribute you don't own */; + destroy(a); +} + +uiAttributeType uiAttributeGetType(const uiAttribute *a) +{ + return a->type; +} + +uiAttribute *uiNewFamilyAttribute(const char *family) +{ + uiAttribute *a; + + a = newAttribute(uiAttributeTypeFamily); + a->u.family = (char *) uiprivAlloc((strlen(family) + 1) * sizeof (char), "char[] (uiAttribute)"); + strcpy(a->u.family, family); + return a; +} + +const char *uiAttributeFamily(const uiAttribute *a) +{ + return a->u.family; +} + +uiAttribute *uiNewSizeAttribute(double size) +{ + uiAttribute *a; + + a = newAttribute(uiAttributeTypeSize); + a->u.size = size; + return a; +} + +double uiAttributeSize(const uiAttribute *a) +{ + return a->u.size; +} + +uiAttribute *uiNewWeightAttribute(uiTextWeight weight) +{ + uiAttribute *a; + + a = newAttribute(uiAttributeTypeWeight); + a->u.weight = weight; + return a; +} + +uiTextWeight uiAttributeWeight(const uiAttribute *a) +{ + return a->u.weight; +} + +uiAttribute *uiNewItalicAttribute(uiTextItalic italic) +{ + uiAttribute *a; + + a = newAttribute(uiAttributeTypeItalic); + a->u.italic = italic; + return a; +} + +uiTextItalic uiAttributeItalic(const uiAttribute *a) +{ + return a->u.italic; +} + +uiAttribute *uiNewStretchAttribute(uiTextStretch stretch) +{ + uiAttribute *a; + + a = newAttribute(uiAttributeTypeStretch); + a->u.stretch = stretch; + return a; +} + +uiTextStretch uiAttributeStretch(const uiAttribute *a) +{ + return a->u.stretch; +} + +uiAttribute *uiNewColorAttribute(double r, double g, double b, double a) +{ + uiAttribute *at; + + at = newAttribute(uiAttributeTypeColor); + at->u.color.r = r; + at->u.color.g = g; + at->u.color.b = b; + at->u.color.a = a; + return at; +} + +void uiAttributeColor(const uiAttribute *a, double *r, double *g, double *b, double *alpha) +{ + *r = a->u.color.r; + *g = a->u.color.g; + *b = a->u.color.b; + *alpha = a->u.color.a; +} + +uiAttribute *uiNewBackgroundAttribute(double r, double g, double b, double a) +{ + uiAttribute *at; + + at = newAttribute(uiAttributeTypeBackground); + at->u.color.r = r; + at->u.color.g = g; + at->u.color.b = b; + at->u.color.a = a; + return at; +} + +uiAttribute *uiNewUnderlineAttribute(uiUnderline u) +{ + uiAttribute *a; + + a = newAttribute(uiAttributeTypeUnderline); + a->u.underline = u; + return a; +} + +uiUnderline uiAttributeUnderline(const uiAttribute *a) +{ + return a->u.underline; +} + +uiAttribute *uiNewUnderlineColorAttribute(uiUnderlineColor u, double r, double g, double b, double a) +{ + uiAttribute *at; + + at = uiNewColorAttribute(r, g, b, a); + at->type = uiAttributeTypeUnderlineColor; + at->u.color.underlineColor = u; + return at; +} + +void uiAttributeUnderlineColor(const uiAttribute *a, uiUnderlineColor *u, double *r, double *g, double *b, double *alpha) +{ + *u = a->u.color.underlineColor; + uiAttributeColor(a, r, g, b, alpha); +} + +uiAttribute *uiNewFeaturesAttribute(const uiOpenTypeFeatures *otf) +{ + uiAttribute *a; + + a = newAttribute(uiAttributeTypeFeatures); + a->u.features = uiOpenTypeFeaturesClone(otf); + return a; +} + +const uiOpenTypeFeatures *uiAttributeFeatures(const uiAttribute *a) +{ + return a->u.features; +} + +int uiprivAttributeEqual(const uiAttribute *a, const uiAttribute *b) +{ + if (a == b) + return 1; + if (a->type != b->type) + return 0; + switch (a->type) { + case uiAttributeTypeFamily: + return uiprivStricmp(a->u.family, b->u.family); + case uiAttributeTypeSize: + // TODO is the use of == correct? + return a->u.size == b->u.size; + case uiAttributeTypeWeight: + return a->u.weight == b->u.weight; + case uiAttributeTypeItalic: + return a->u.italic == b->u.italic; + case uiAttributeTypeStretch: + return a->u.stretch == b->u.stretch; + case uiAttributeTypeUnderline: + return a->u.underline == b->u.underline; + case uiAttributeTypeUnderlineColor: + if (a->u.color.underlineColor != b->u.color.underlineColor) + return 0; + // fall through + case uiAttributeTypeColor: + case uiAttributeTypeBackground: + // TODO is the use of == correct? + return (a->u.color.r == b->u.color.r) && + (a->u.color.g == b->u.color.g) && + (a->u.color.b == b->u.color.b) && + (a->u.color.a == b->u.color.a); + case uiAttributeTypeFeatures: + return uiprivOpenTypeFeaturesEqual(a->u.features, b->u.features); + } + // TODO should not be reached + return 0; +} diff --git a/common/attrlist.c b/common/attrlist.c new file mode 100644 index 00000000..377778eb --- /dev/null +++ b/common/attrlist.c @@ -0,0 +1,612 @@ +// 16 december 2016 +#include "../ui.h" +#include "uipriv.h" +#include "attrstr.h" + +/* +An attribute list is a doubly linked list of attributes. +Attribute start positions are inclusive and attribute end positions are exclusive (or in other words, [start, end)). +The list is kept sorted in increasing order by start position. Whether or not the sort is stable is undefined, so no temporal information should be expected to stay. +Overlapping attributes are not allowed; if an attribute is added that conflicts with an existing one, the existing one is removed. +In addition, the linked list tries to reduce fragmentation: if an attribute is added that just expands another, then there will only be one entry in alist, not two. (TODO does it really?) +The linked list is not a ring; alist->fist->prev == NULL and alist->last->next == NULL. +TODO verify that this disallows attributes of length zero +*/ + +struct attr { + uiAttribute *val; + size_t start; + size_t end; + struct attr *prev; + struct attr *next; +}; + +struct uiprivAttrList { + struct attr *first; + struct attr *last; +}; + +// if before is NULL, add to the end of the list +static void attrInsertBefore(uiprivAttrList *alist, struct attr *a, struct attr *before) +{ + // if the list is empty, this is the first item + if (alist->first == NULL) { + alist->first = a; + alist->last = a; + return; + } + + // add to the end + if (before == NULL) { + struct attr *oldlast; + + oldlast = alist->last; + alist->last = a; + a->prev = oldlast; + oldlast->next = a; + return; + } + + // add to the beginning + if (before == alist->first) { + struct attr *oldfirst; + + oldfirst = alist->first; + alist->first = a; + oldfirst->prev = a; + a->next = oldfirst; + return; + } + + // add to the middle + a->prev = before->prev; + a->next = before; + before->prev = a; + a->prev->next = a; +} + +static int attrHasPos(struct attr *a, size_t pos) +{ + if (pos < a->start) + return 0; + return pos < a->end; +} + +// returns 1 if there was an intersection and 0 otherwise +static int attrRangeIntersect(struct attr *a, size_t *start, size_t *end) +{ + // is the range outside a entirely? + if (*start >= a->end) + return 0; + if (*end < a->start) + return 0; + + // okay, so there is an overlap + // compute the intersection + if (*start < a->start) + *start = a->start; + if (*end > a->end) + *end = a->end; + return 1; +} + +// returns the old a->next, for forward iteration +static struct attr *attrUnlink(uiprivAttrList *alist, struct attr *a) +{ + struct attr *p, *n; + + p = a->prev; + n = a->next; + a->prev = NULL; + a->next = NULL; + + // only item in list? + if (p == NULL && n == NULL) { + alist->first = NULL; + alist->last = NULL; + return NULL; + } + // start of list? + if (p == NULL) { + n->prev = NULL; + alist->first = n; + return n; + } + // end of list? + if (n == NULL) { + p->next = NULL; + alist->last = p; + return NULL; + } + // middle of list + p->next = n; + n->prev = p; + return n; +} + +// returns the old a->next, for forward iteration +static struct attr *attrDelete(uiprivAttrList *alist, struct attr *a) +{ + struct attr *next; + + next = attrUnlink(alist, a); + uiprivAttributeRelease(a->val); + uiprivFree(a); + return next; +} + +// attrDropRange() removes attributes without deleting characters. +// +// If the attribute needs no change, then nothing is done. +// +// If the attribute needs to be deleted, it is deleted. +// +// If the attribute only needs to be resized at the end, it is adjusted. +// +// If the attribute only needs to be resized at the start, it is adjusted, unlinked, and returned in tail. +// +// Otherwise, the attribute needs to be split. The existing attribute is adjusted to make the left half and a new attribute with the right half. This attribute is kept unlinked and returned in tail. +// +// In all cases, the return value is the next attribute to look at in a forward sequential loop. +static struct attr *attrDropRange(uiprivAttrList *alist, struct attr *a, size_t start, size_t end, struct attr **tail) +{ + struct attr *b; + + // always pre-initialize tail to NULL + *tail = NULL; + + if (!attrRangeIntersect(a, &start, &end)) + // out of range; nothing to do + return a->next; + + // just outright delete the attribute? + // the inequalities handle attributes entirely inside the range + // if both are equal, the attribute's range is equal to the range + if (a->start >= start && a->end <= end) + return attrDelete(alist, a); + + // only chop off the start or end? + if (a->start == start) { // chop off the start + // we are dropping the left half, so set a->start and unlink + a->start = end; + *tail = a; + return attrUnlink(alist, a); + } + if (a->end == end) { // chop off the end + // we are dropping the right half, so just set a->end + a->end = start; + return a->next; + } + + // we'll need to split the attribute into two + b = uiprivNew(struct attr); + b->val = uiprivAttributeRetain(a->val); + b->start = end; + b->end = a->end; + *tail = b; + + a->end = start; + return a->next; +} + +static void attrGrow(uiprivAttrList *alist, struct attr *a, size_t start, size_t end) +{ + struct attr *before; + + // adjusting the end is simple: if it ends before our new end, just set the new end + if (a->end < end) + a->end = end; + + // adjusting the start is harder + // if the start is before our new start, we are done + // otherwise, we have to move the start back AND reposition the attribute to keep the sorted order + if (a->start <= start) + return; + a->start = start; + attrUnlink(alist, a); + for (before = alist->first; before != NULL; before = before->next) + if (before->start > a->start) + break; + attrInsertBefore(alist, a, before); +} + +// returns the right side of the split, which is unlinked, or NULL if no split was done +static struct attr *attrSplitAt(uiprivAttrList *alist, struct attr *a, size_t at) +{ + struct attr *b; + + // no splittng needed? + // note the equality: we don't need to split at start or end + // in the end case, the last split point is at - 1; at itself is outside the range, and at - 1 results in the right hand side having length 1 + if (at <= a->start) + return NULL; + if (at >= a->end) + return NULL; + + b = uiprivNew(struct attr); + b->val = uiprivAttributeRetain(a->val); + b->start = at; + b->end = a->end; + + a->end = at; + return b; +} + +// attrDeleteRange() removes attributes while deleting characters. +// +// If the attribute does not include the deleted range, then nothing is done (though the start and end are adjusted as necessary). +// +// If the attribute needs to be deleted, it is deleted. +// +// Otherwise, the attribute only needs the start or end deleted, and it is adjusted. +// +// In all cases, the return value is the next attribute to look at in a forward sequential loop. +// TODO rewrite this comment +static struct attr *attrDeleteRange(uiprivAttrList *alist, struct attr *a, size_t start, size_t end) +{ + size_t ostart, oend; + size_t count; + + ostart = start; + oend = end; + count = oend - ostart; + + if (!attrRangeIntersect(a, &start, &end)) { + // out of range + // adjust if necessary + if (a->start >= ostart) + a->start -= count; + if (a->end >= oend) + a->end -= count; + return a->next; + } + + // just outright delete the attribute? + // the inequalities handle attributes entirely inside the range + // if both are equal, the attribute's range is equal to the range + if (a->start >= start && a->end <= end) + return attrDelete(alist, a); + + // only chop off the start or end? + if (a->start == start) { // chop off the start + // if we weren't adjusting positions this would just be setting a->start to end + // but since this is deleting from the start, we need to adjust both by count + a->start = end - count; + a->end -= count; + return a->next; + } + if (a->end == end) { // chop off the end + // a->start is already good + a->end = start; + return a->next; + } + + // in this case, the deleted range is inside the attribute + // we can clear it by just removing count from a->end + a->end -= count; + return a->next; +} + +uiprivAttrList *uiprivNewAttrList(void) +{ + return uiprivNew(uiprivAttrList); +} + +void uiprivFreeAttrList(uiprivAttrList *alist) +{ + struct attr *a, *next; + + a = alist->first; + while (a != NULL) { + next = a->next; + uiprivAttributeRelease(a->val); + uiprivFree(a); + a = next; + } + uiprivFree(alist); +} + +void uiprivAttrListInsertAttribute(uiprivAttrList *alist, uiAttribute *val, size_t start, size_t end) +{ + struct attr *a; + struct attr *before; + struct attr *tail = NULL; + int split = 0; + uiAttributeType valtype; + + // first, figure out where in the list this should go + // in addition, if this attribute overrides one that already exists, split that one apart so this one can take over + before = alist->first; + valtype = uiAttributeGetType(val); + while (before != NULL) { + size_t lstart, lend; + + // once we get to the first point after start, we know where to insert + if (before->start > start) + break; + + // if we have already split a prior instance of this attribute, don't bother doing it again + if (split) + goto next; + + // should we split this attribute? + if (uiAttributeGetType(before->val) != valtype) + goto next; + lstart = start; + lend = end; + if (!attrRangeIntersect(before, &lstart, &lend)) + goto next; + + // okay so this might conflict; if the val is the same as the one we want, we need to expand the existing attribute, not fragment anything + // TODO will this reduce fragmentation if we first add from 0 to 2 and then from 2 to 4? or do we have to do that separately? + if (uiprivAttributeEqual(before->val, val)) { + attrGrow(alist, before, start, end); + return; + } + // okay the values are different; we need to split apart + before = attrDropRange(alist, before, start, end, &tail); + split = 1; + continue; + + next: + before = before->next; + } + + // if we got here, we know we have to add the attribute before before + a = uiprivNew(struct attr); + a->val = uiprivAttributeRetain(val); + a->start = start; + a->end = end; + attrInsertBefore(alist, a, before); + + // and finally, if we split, insert the remainder + if (tail == NULL) + return; + // note we start at before; it won't be inserted before that by the sheer nature of how the code above works + for (; before != NULL; before = before->next) + if (before->start > tail->start) + break; + attrInsertBefore(alist, tail, before); +} + +void uiprivAttrListInsertCharactersUnattributed(uiprivAttrList *alist, size_t start, size_t count) +{ + struct attr *a; + struct attr *tails = NULL; + + // every attribute before the insertion point can either cross into the insertion point or not + // if it does, we need to split that attribute apart at the insertion point, keeping only the old attribute in place, adjusting the new tail, and preparing it for being re-added later + for (a = alist->first; a != NULL; a = a->next) { + struct attr *tail; + + // stop once we get to the insertion point + if (a->start >= start) + break; + // only do something if overlapping + if (!attrHasPos(a, start)) + continue; + + tail = attrSplitAt(alist, a, start); + // adjust the new tail for the insertion + tail->start += count; + tail->end += count; + // and queue it for re-adding later + // we can safely use tails as if it was singly-linked since it's just a temporary list; we properly merge them back in below and they'll be doubly-linked again then + // TODO actually we could probably save some time by making then doubly-linked now and adding them in one fell swoop, but that would make things a bit more complicated... + tail->next = tails; + tails = tail; + } + + // at this point in the attribute list, we are at or after the insertion point + // all the split-apart attributes will be at the insertion point + // therefore, we can just add them all back now, and the list will still be sorted correctly + while (tails != NULL) { + struct attr *next; + + // make all the links NULL before insertion, just to be safe + next = tails->next; + tails->next = NULL; + attrInsertBefore(alist, tails, a); + tails = next; + } + + // every remaining attribute will be either at or after the insertion point + // we just need to move them ahead + for (; a != NULL; a = a->next) { + a->start += count; + a->end += count; + } +} + +// The attributes are those of character start - 1. +// If start == 0, the attributes are those of character 0. +/* +This is an obtuse function. Here's some diagrams to help. + +Given the input string + abcdefghi (positions: 012345678 9) +and attribute set + red start 0 end 3 + bold start 2 end 6 + underline start 5 end 8 +or visually: + 012345678 9 + rrr------ + --bbbb--- + -----uuu- +If we insert qwe to result in positions 0123456789AB C: + +before 0, 1, 2 (grow the red part, move everything else down) + red -> start 0 (no change) end 3+3=6 + bold -> start 2+3=5 end 6+3=9 + underline -> start 5+3=8 end 8+3=B +before 3 (grow red and bold, move underline down) + red -> start 0 (no change) end 3+3=6 + bold -> start 2 (no change) end 6+3=9 + underline -> start 5+3=8 end 8+3=B +before 4, 5 (keep red, grow bold, move underline down) + red -> start 0 (no change) end 3 (no change) + bold -> start 2 (no change) end 6+3=9 + underline -> start 5+3=8 end 8+3=B +before 6 (keep red, grow bold and underline) + red -> start 0 (no change) end 3 (no change) + bold -> start 2 (no change) end 6+3=9 + underline -> start 5 (no change) end 8+3=B +before 7, 8 (keep red and bold, grow underline) + red -> start 0 (no change) end 3 (no change) + bold -> start 2 (no change) end 6 (no change) + underline -> start 5 (no change) end 8+3=B +before 9 (keep all three) + red -> start 0 (no change) end 3 (no change) + bold -> start 2 (no change) end 6 (no change) + underline -> start 5 (no change) end 8 (no change) + +result: + 0 1 2 3 4 5 6 7 8 9 + red E E E e n n n n n n + bold s s S E E E e n n n +underline s s s s s S E E e n +N = none +E = end only +S = start and end +uppercase = in original range, lowercase = not + +which results in our algorithm: + for each attribute + if start < insertion point + move start up + else if start == insertion point + if start != 0 + move start up + if end <= insertion point + move end up +*/ +// TODO does this ensure the list remains sorted? +void uiprivAttrListInsertCharactersExtendingAttributes(uiprivAttrList *alist, size_t start, size_t count) +{ + struct attr *a; + + for (a = alist->first; a != NULL; a = a->next) { + if (a->start < start) + a->start += count; + else if (a->start == start && start != 0) + a->start += count; + if (a->end <= start) + a->end += count; + } +} + +// TODO replace at point with — replaces with first character's attributes + +void uiprivAttrListRemoveAttribute(uiprivAttrList *alist, uiAttributeType type, size_t start, size_t end) +{ + struct attr *a; + struct attr *tails = NULL; // see uiprivAttrListInsertCharactersUnattributed() above + struct attr *tailsAt = NULL; + + a = alist->first; + while (a != NULL) { + size_t lstart, lend; + struct attr *tail; + + // this defines where to re-attach the tails + // (all the tails will have their start at end, so we can just insert them all before tailsAt) + if (a->start >= end) { + tailsAt = a; + // and at this point we're done, so + break; + } + if (uiAttributeGetType(a->val) != type) + goto next; + lstart = start; + lend = end; + if (!attrRangeIntersect(a, &lstart, &lend)) + goto next; + a = attrDropRange(alist, a, start, end, &tail); + if (tail != NULL) { + tail->next = tails; + tails = tail; + } + continue; + + next: + a = a->next; + } + + while (tails != NULL) { + struct attr *next; + + // make all the links NULL before insertion, just to be safe + next = tails->next; + tails->next = NULL; + attrInsertBefore(alist, tails, a); + tails = next; + } +} + +// TODO merge this with the above +void uiprivAttrListRemoveAttributes(uiprivAttrList *alist, size_t start, size_t end) +{ + struct attr *a; + struct attr *tails = NULL; // see uiprivAttrListInsertCharactersUnattributed() above + struct attr *tailsAt = NULL; + + a = alist->first; + while (a != NULL) { + size_t lstart, lend; + struct attr *tail; + + // this defines where to re-attach the tails + // (all the tails will have their start at end, so we can just insert them all before tailsAt) + if (a->start >= end) { + tailsAt = a; + // and at this point we're done, so + break; + } + lstart = start; + lend = end; + if (!attrRangeIntersect(a, &lstart, &lend)) + goto next; + a = attrDropRange(alist, a, start, end, &tail); + if (tail != NULL) { + tail->next = tails; + tails = tail; + } + continue; + + next: + a = a->next; + } + + while (tails != NULL) { + struct attr *next; + + // make all the links NULL before insertion, just to be safe + next = tails->next; + tails->next = NULL; + attrInsertBefore(alist, tails, a); + tails = next; + } +} + +void uiprivAttrListRemoveCharacters(uiprivAttrList *alist, size_t start, size_t end) +{ + struct attr *a; + + a = alist->first; + while (a != NULL) + a = attrDeleteRange(alist, a, start, end); +} + +void uiprivAttrListForEach(const uiprivAttrList *alist, const uiAttributedString *s, uiAttributedStringForEachAttributeFunc f, void *data) +{ + struct attr *a; + uiForEach ret; + + for (a = alist->first; a != NULL; a = a->next) { + ret = (*f)(s, a->val, a->start, a->end, data); + if (ret == uiForEachStop) + // TODO for all: break or return? + break; + } +} diff --git a/common/attrstr.c b/common/attrstr.c new file mode 100644 index 00000000..ca00c19d --- /dev/null +++ b/common/attrstr.c @@ -0,0 +1,357 @@ +// 3 december 2016 +#include "../ui.h" +#include "uipriv.h" +#include "attrstr.h" + +struct uiAttributedString { + char *s; + size_t len; + + uiprivAttrList *attrs; + + // indiscriminately keep a UTF-16 copy of the string on all platforms so we can hand this off to the grapheme calculator + // this ensures no one platform has a speed advantage (sorry GTK+) + uint16_t *u16; + size_t u16len; + + size_t *u8tou16; + size_t *u16tou8; + + // this is lazily created to keep things from getting *too* slow + uiprivGraphemes *graphemes; +}; + +static void resize(uiAttributedString *s, size_t u8, size_t u16) +{ + s->len = u8; + s->s = (char *) uiprivRealloc(s->s, (s->len + 1) * sizeof (char), "char[] (uiAttributedString)"); + s->u8tou16 = (size_t *) uiprivRealloc(s->u8tou16, (s->len + 1) * sizeof (size_t), "size_t[] (uiAttributedString)"); + s->u16len = u16; + s->u16 = (uint16_t *) uiprivRealloc(s->u16, (s->u16len + 1) * sizeof (uint16_t), "uint16_t[] (uiAttributedString)"); + s->u16tou8 = (size_t *) uiprivRealloc(s->u16tou8, (s->u16len + 1) * sizeof (size_t), "size_t[] (uiAttributedString)"); +} + +uiAttributedString *uiNewAttributedString(const char *initialString) +{ + uiAttributedString *s; + + s = uiprivNew(uiAttributedString); + s->attrs = uiprivNewAttrList(); + uiAttributedStringAppendUnattributed(s, initialString); + return s; +} + +// TODO make sure that all implementations of uiprivNewGraphemes() work fine with empty strings; in particular, the Windows one might not +static void recomputeGraphemes(uiAttributedString *s) +{ + if (s->graphemes != NULL) + return; + if (uiprivGraphemesTakesUTF16()) { + s->graphemes = uiprivNewGraphemes(s->u16, s->u16len); + return; + } + s->graphemes = uiprivNewGraphemes(s->s, s->len); +} + +static void invalidateGraphemes(uiAttributedString *s) +{ + if (s->graphemes == NULL) + return; + uiprivFree(s->graphemes->pointsToGraphemes); + uiprivFree(s->graphemes->graphemesToPoints); + uiprivFree(s->graphemes); + s->graphemes = NULL; +} + +void uiFreeAttributedString(uiAttributedString *s) +{ + uiprivFreeAttrList(s->attrs); + invalidateGraphemes(s); + uiprivFree(s->u16tou8); + uiprivFree(s->u8tou16); + uiprivFree(s->u16); + uiprivFree(s->s); + uiprivFree(s); +} + +const char *uiAttributedStringString(const uiAttributedString *s) +{ + return s->s; +} + +size_t uiAttributedStringLen(const uiAttributedString *s) +{ + return s->len; +} + +static void u8u16len(const char *str, size_t *n8, size_t *n16) +{ + uint32_t rune; + char buf[4]; + uint16_t buf16[2]; + + *n8 = 0; + *n16 = 0; + while (*str) { + str = uiprivUTF8DecodeRune(str, 0, &rune); + // TODO document the use of the function vs a pointer subtract here + // TODO also we need to consider namespace collision with utf.h... + *n8 += uiprivUTF8EncodeRune(rune, buf); + *n16 += uiprivUTF16EncodeRune(rune, buf16); + } +} + +void uiAttributedStringAppendUnattributed(uiAttributedString *s, const char *str) +{ + uiAttributedStringInsertAtUnattributed(s, str, s->len); +} + +// this works (and returns true, which is what we want) at s->len too because s->s[s->len] is always going to be 0 due to us allocating s->len + 1 bytes and because uiprivRealloc() always zero-fills allocated memory +static int onCodepointBoundary(uiAttributedString *s, size_t at) +{ + uint8_t c; + + // for uiNewAttributedString() + if (s->s == NULL && at == 0) + return 1; + c = (uint8_t) (s->s[at]); + return c < 0x80 || c >= 0xC0; +} + +// TODO note that at must be on a codeoint boundary +void uiAttributedStringInsertAtUnattributed(uiAttributedString *s, const char *str, size_t at) +{ + uint32_t rune; + char buf[4]; + uint16_t buf16[2]; + size_t n8, n16; // TODO make loop-local? to avoid using them in the wrong place again + size_t old, old16; + size_t oldn8, oldn16; + size_t oldlen, old16len; + size_t at16; + size_t i; + + if (!onCodepointBoundary(s, at)) { + // TODO + } + + at16 = 0; + if (s->u8tou16 != NULL) + at16 = s->u8tou16[at]; + + // do this first to reclaim memory + invalidateGraphemes(s); + + // first figure out how much we need to grow by + // this includes post-validated UTF-8 + u8u16len(str, &n8, &n16); + + // and resize + old = at; + old16 = at16; + oldlen = s->len; + old16len = s->u16len; + resize(s, s->len + n8, s->u16len + n16); + + // move existing characters out of the way + // note the use of memmove(): https://twitter.com/rob_pike/status/737797688217894912 + memmove( + s->s + at + n8, + s->s + at, + (oldlen - at) * sizeof (char)); + memmove( + s->u16 + at16 + n16, + s->u16 + at16, + (old16len - at16) * sizeof (uint16_t)); + // note the + 1 for these; we want to copy the terminating null too + memmove( + s->u8tou16 + at + n8, + s->u8tou16 + at, + (oldlen - at + 1) * sizeof (size_t)); + memmove( + s->u16tou8 + at16 + n16, + s->u16tou8 + at16, + (old16len - at16 + 1) * sizeof (size_t)); + oldn8 = n8; + oldn16 = n16; + + // and copy + while (*str) { + size_t n; + + str = uiprivUTF8DecodeRune(str, 0, &rune); + n = uiprivUTF8EncodeRune(rune, buf); + n16 = uiprivUTF16EncodeRune(rune, buf16); + s->s[old] = buf[0]; + s->u8tou16[old] = old16; + if (n > 1) { + s->s[old + 1] = buf[1]; + s->u8tou16[old + 1] = old16; + } + if (n > 2) { + s->s[old + 2] = buf[2]; + s->u8tou16[old + 2] = old16; + } + if (n > 3) { + s->s[old + 3] = buf[3]; + s->u8tou16[old + 3] = old16; + } + s->u16[old16] = buf16[0]; + s->u16tou8[old16] = old; + if (n16 > 1) { + s->u16[old16 + 1] = buf16[1]; + s->u16tou8[old16 + 1] = old; + } + old += n; + old16 += n16; + } + // and have an index for the end of the string + // TODO is this done by the below? +//TODO s->u8tou16[old] = old16; +//TODO s->u16tou8[old16] = old; + + // and adjust the prior values in the conversion tables + // use <= so the terminating 0 gets updated too + for (i = 0; i <= oldlen - at; i++) + s->u8tou16[at + oldn8 + i] += s->u16len - old16len; + for (i = 0; i <= old16len - at16; i++) + s->u16tou8[at16 + oldn16 + i] += s->len - oldlen; + + // and finally do the attributes + uiprivAttrListInsertCharactersUnattributed(s->attrs, at, n8); +} + +// TODO document that end is the first index that will be maintained +void uiAttributedStringDelete(uiAttributedString *s, size_t start, size_t end) +{ + size_t start16, end16; + size_t count, count16; + size_t i; + + if (!onCodepointBoundary(s, start)) { + // TODO + } + if (!onCodepointBoundary(s, end)) { + // TODO + } + + count = end - start; + start16 = s->u8tou16[start]; + end16 = s->u8tou16[end]; + count16 = end16 - start16; + + invalidateGraphemes(s); + + // overwrite old characters + memmove( + s->s + start, + s->s + end, + (s->len - end) * sizeof (char)); + memmove( + s->u16 + start16, + s->u16 + end16, + (s->u16len - end16) * sizeof (uint16_t)); + // note the + 1 for these; we want to copy the terminating null too + memmove( + s->u8tou16 + start, + s->u8tou16 + end, + (s->len - end + 1) * sizeof (size_t)); + memmove( + s->u16tou8 + start16, + s->u16tou8 + end16, + (s->u16len - end16 + 1) * sizeof (size_t)); + + // update the conversion tables + // note the use of <= to include the null terminator + for (i = 0; i <= (s->len - end); i++) + s->u8tou16[start + i] -= count16; + for (i = 0; i <= (s->u16len - end16); i++) + s->u16tou8[start16 + i] -= count; + + // null-terminate the string + s->s[start + (s->len - end)] = 0; + s->u16[start16 + (s->u16len - end16)] = 0; + + // fix up attributes + uiprivAttrListRemoveCharacters(s->attrs, start, end); + + // and finally resize + resize(s, s->len - count, s->u16len - count16); +} + +void uiAttributedStringSetAttribute(uiAttributedString *s, uiAttribute *a, size_t start, size_t end) +{ + uiprivAttrListInsertAttribute(s->attrs, a, start, end); +} + +// LONGTERM introduce an iterator object instead? +void uiAttributedStringForEachAttribute(const uiAttributedString *s, uiAttributedStringForEachAttributeFunc f, void *data) +{ + uiprivAttrListForEach(s->attrs, s, f, data); +} + +// TODO figure out if we should count the grapheme past the end +size_t uiAttributedStringNumGraphemes(uiAttributedString *s) +{ + recomputeGraphemes(s); + return s->graphemes->len; +} + +size_t uiAttributedStringByteIndexToGrapheme(uiAttributedString *s, size_t pos) +{ + recomputeGraphemes(s); + if (uiprivGraphemesTakesUTF16()) + pos = s->u8tou16[pos]; + return s->graphemes->pointsToGraphemes[pos]; +} + +size_t uiAttributedStringGraphemeToByteIndex(uiAttributedString *s, size_t pos) +{ + recomputeGraphemes(s); + pos = s->graphemes->graphemesToPoints[pos]; + if (uiprivGraphemesTakesUTF16()) + pos = s->u16tou8[pos]; + return pos; +} + +// helpers for platform-specific code + +const uint16_t *uiprivAttributedStringUTF16String(const uiAttributedString *s) +{ + return s->u16; +} + +size_t uiprivAttributedStringUTF16Len(const uiAttributedString *s) +{ + return s->u16len; +} + +// TODO is this still needed given the below? +size_t uiprivAttributedStringUTF8ToUTF16(const uiAttributedString *s, size_t n) +{ + return s->u8tou16[n]; +} + +size_t *uiprivAttributedStringCopyUTF8ToUTF16Table(const uiAttributedString *s, size_t *n) +{ + size_t *out; + size_t nbytes; + + nbytes = (s->len + 1) * sizeof (size_t); + *n = s->len; + out = (size_t *) uiprivAlloc(nbytes, "size_t[] (uiAttributedString)"); + memmove(out, s->u8tou16, nbytes); + return out; +} + +size_t *uiprivAttributedStringCopyUTF16ToUTF8Table(const uiAttributedString *s, size_t *n) +{ + size_t *out; + size_t nbytes; + + nbytes = (s->u16len + 1) * sizeof (size_t); + *n = s->u16len; + out = (size_t *) uiprivAlloc(nbytes, "size_t[] (uiAttributedString)"); + memmove(out, s->u16tou8, nbytes); + return out; +} diff --git a/common/attrstr.h b/common/attrstr.h new file mode 100644 index 00000000..69ada5c1 --- /dev/null +++ b/common/attrstr.h @@ -0,0 +1,46 @@ +// 19 february 2018 + +#ifdef __cplusplus +extern "C" { +#endif + +// attribute.c +extern uiAttribute *uiprivAttributeRetain(uiAttribute *a); +extern void uiprivAttributeRelease(uiAttribute *a); +extern int uiprivAttributeEqual(const uiAttribute *a, const uiAttribute *b); + +// opentype.c +extern int uiprivOpenTypeFeaturesEqual(const uiOpenTypeFeatures *a, const uiOpenTypeFeatures *b); + +// attrlist.c +typedef struct uiprivAttrList uiprivAttrList; +extern uiprivAttrList *uiprivNewAttrList(void); +extern void uiprivFreeAttrList(uiprivAttrList *alist); +extern void uiprivAttrListInsertAttribute(uiprivAttrList *alist, uiAttribute *val, size_t start, size_t end); +extern void uiprivAttrListInsertCharactersUnattributed(uiprivAttrList *alist, size_t start, size_t count); +extern void uiprivAttrListInsertCharactersExtendingAttributes(uiprivAttrList *alist, size_t start, size_t count); +extern void uiprivAttrListRemoveAttribute(uiprivAttrList *alist, uiAttributeType type, size_t start, size_t end); +extern void uiprivAttrListRemoveAttributes(uiprivAttrList *alist, size_t start, size_t end); +extern void uiprivAttrListRemoveCharacters(uiprivAttrList *alist, size_t start, size_t end); +extern void uiprivAttrListForEach(const uiprivAttrList *alist, const uiAttributedString *s, uiAttributedStringForEachAttributeFunc f, void *data); + +// attrstr.c +extern const uint16_t *uiprivAttributedStringUTF16String(const uiAttributedString *s); +extern size_t uiprivAttributedStringUTF16Len(const uiAttributedString *s); +extern size_t uiprivAttributedStringUTF8ToUTF16(const uiAttributedString *s, size_t n); +extern size_t *uiprivAttributedStringCopyUTF8ToUTF16Table(const uiAttributedString *s, size_t *n); +extern size_t *uiprivAttributedStringCopyUTF16ToUTF8Table(const uiAttributedString *s, size_t *n); + +// per-OS graphemes.c/graphemes.cpp/graphemes.m/etc. +typedef struct uiprivGraphemes uiprivGraphemes; +struct uiprivGraphemes { + size_t len; + size_t *pointsToGraphemes; + size_t *graphemesToPoints; +}; +extern int uiprivGraphemesTakesUTF16(void); +extern uiprivGraphemes *uiprivNewGraphemes(void *s, size_t len); + +#ifdef __cplusplus +} +#endif diff --git a/common/control.c b/common/control.c index d6da78bf..98cb94aa 100644 --- a/common/control.c +++ b/common/control.c @@ -57,30 +57,24 @@ void uiControlDisable(uiControl *c) (*(c->Disable))(c); } -#define uiControlSignature 0x7569436F +#define uiprivControlSignature 0x7569436F uiControl *uiAllocControl(size_t size, uint32_t OSsig, uint32_t typesig, const char *typenamestr) { uiControl *c; - c = (uiControl *) uiAlloc(size, typenamestr); - c->Signature = uiControlSignature; + c = (uiControl *) uiprivAlloc(size, typenamestr); + c->Signature = uiprivControlSignature; c->OSSignature = OSsig; c->TypeSignature = typesig; return c; } void uiFreeControl(uiControl *c) -{ - uiFree(c); -} - -// TODO except where noted, replace complain() with userbug() - -void uiControlVerifyDestroy(uiControl *c) { if (uiControlParent(c) != NULL) - complain("attempt to destroy uiControl %p while it has a parent", c); + uiprivUserBug("You cannot destroy a uiControl while it still has a parent. (control: %p)", c); + uiprivFree(c); } void uiControlVerifySetParent(uiControl *c, uiControl *parent) @@ -88,13 +82,12 @@ void uiControlVerifySetParent(uiControl *c, uiControl *parent) uiControl *curParent; if (uiControlToplevel(c)) - complain("cannot set a parent on a toplevel (uiWindow)"); + uiprivUserBug("You cannot give a toplevel uiControl a parent. (control: %p)", c); curParent = uiControlParent(c); if (parent != NULL && curParent != NULL) - complain("attempt to reparent uiControl %p (has parent %p, attempt to give parent %p)", c, curParent, parent); + uiprivUserBug("You cannot give a uiControl a parent while it already has one. (control: %p; current parent: %p; new parent: %p)", c, curParent, parent); if (parent == NULL && curParent == NULL) - // TODO implbug() - complain("attempt to double unparent uiControl %p — likely an implementation bug ", c); + uiprivImplBug("attempt to double unparent uiControl %p", c); } int uiControlEnabledToUser(uiControl *c) diff --git a/common/controlsigs.h b/common/controlsigs.h index 598e7342..944afa9b 100644 --- a/common/controlsigs.h +++ b/common/controlsigs.h @@ -1,13 +1,19 @@ // 24 april 2016 +// LONGTERM if I don't decide to remove these outright, should they be renamed uiprivTypeNameSignature? these aren't real symbols, so... + #define uiAreaSignature 0x41726561 #define uiBoxSignature 0x426F784C #define uiButtonSignature 0x42746F6E #define uiCheckboxSignature 0x43686B62 +#define uiColorButtonSignature 0x436F6C42 #define uiComboboxSignature 0x436F6D62 #define uiDateTimePickerSignature 0x44545069 +#define uiEditableComboboxSignature 0x45644362 #define uiEntrySignature 0x456E7472 #define uiFontButtonSignature 0x466F6E42 +#define uiFormSignature 0x466F726D +#define uiGridSignature 0x47726964 #define uiGroupSignature 0x47727062 #define uiLabelSignature 0x4C61626C #define uiMultilineEntrySignature 0x4D6C6E45 @@ -17,4 +23,5 @@ #define uiSliderSignature 0x536C6964 #define uiSpinboxSignature 0x5370696E #define uiTabSignature 0x54616273 +#define uiTableSignature 0x5461626C #define uiWindowSignature 0x57696E64 diff --git a/common/debug.c b/common/debug.c new file mode 100644 index 00000000..aa24e29f --- /dev/null +++ b/common/debug.c @@ -0,0 +1,21 @@ +// 13 may 2016 +#include "../ui.h" +#include "uipriv.h" + +void uiprivDoImplBug(const char *file, const char *line, const char *func, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + uiprivRealBug(file, line, func, "POSSIBLE IMPLEMENTATION BUG; CONTACT ANDLABS:\n", format, ap); + va_end(ap); +} + +void uiprivDoUserBug(const char *file, const char *line, const char *func, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + uiprivRealBug(file, line, func, "You have a bug: ", format, ap); + va_end(ap); +} diff --git a/common/matrix.c b/common/matrix.c index 7af2eb07..93d4d357 100644 --- a/common/matrix.c +++ b/common/matrix.c @@ -3,7 +3,7 @@ #include "../ui.h" #include "uipriv.h" -void setIdentity(uiDrawMatrix *m) +void uiDrawMatrixSetIdentity(uiDrawMatrix *m) { m->M11 = 1; m->M12 = 0; @@ -13,93 +13,38 @@ void setIdentity(uiDrawMatrix *m) m->M32 = 0; } -// TODO don't default to fallback functions within the fallback functions +// The rest of this file provides basic utilities in case the platform doesn't provide any of its own for these tasks. +// Keep these as minimal as possible. They should generally not call other fallbacks. // see https://msdn.microsoft.com/en-us/library/windows/desktop/ff684171%28v=vs.85%29.aspx#skew_transform -// TODO if Windows 7 is ever dropped change this so we can pass in D2D1Tan() -void fallbackSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount) +// TODO see if there's a way we can avoid the multiplication +void uiprivFallbackSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount) { uiDrawMatrix n; - setIdentity(&n); + uiDrawMatrixSetIdentity(&n); // TODO explain this n.M12 = tan(yamount); n.M21 = tan(xamount); n.M31 = -y * tan(xamount); n.M32 = -x * tan(yamount); - fallbackMultiply(m, &n); + uiDrawMatrixMultiply(m, &n); } -// see windows/draw.c for more information -// TODO we don't need to do this if we can bypass the multiplication somehow - -void fallbackTranslate(uiDrawMatrix *m, double x, double y) -{ - uiDrawMatrix m2; - - setIdentity(&m2); - m2.M31 = x; - m2.M32 = y; - fallbackMultiply(m, &m2); -} - -void scaleCenter(double xCenter, double yCenter, double *x, double *y) +void uiprivScaleCenter(double xCenter, double yCenter, double *x, double *y) { *x = xCenter - (*x * xCenter); *y = yCenter - (*y * yCenter); } -void fallbackScale(uiDrawMatrix *m, double xCenter, double yCenter, double x, double y) +// the basic algorithm is from cairo +// but it's the same algorithm as the transform point, just without M31 and M32 taken into account, so let's just do that instead +void uiprivFallbackTransformSize(uiDrawMatrix *m, double *x, double *y) { uiDrawMatrix m2; - setIdentity(&m2); - m2.M11 = x; - m2.M22 = y; - scaleCenter(xCenter, yCenter, &x, &y); - m2.M31 = x; - m2.M32 = y; - fallbackMultiply(m, &m2); -} - -void fallbackMultiply(uiDrawMatrix *dest, uiDrawMatrix *src) -{ - uiDrawMatrix out; - - out.M11 = (dest->M11 * src->M11) + - (dest->M12 * src->M21); - out.M12 = (dest->M11 * src->M12) + - (dest->M12 * src->M22); - out.M21 = (dest->M21 * src->M11) + - (dest->M22 * src->M21); - out.M22 = (dest->M21 * src->M12) + - (dest->M22 * src->M22); - out.M31 = (dest->M31 * src->M11) + - (dest->M32 * src->M21) + - src->M31; - out.M32 = (dest->M31 * src->M12) + - (dest->M32 * src->M22) + - src->M32; - *dest = out; -} - -void fallbackTransformPoint(uiDrawMatrix *m, double *x, double *y) -{ - double xout, yout; - - xout = (*x * m->M11) + (*y * m->M21) + m->M31; - yout = (*x * m->M12) + (*y * m->M22) + m->M32; - *x = xout; - *y = yout; -} - -// and this algorithm is according to cairo -void fallbackTransformSize(uiDrawMatrix *m, double *x, double *y) -{ - double xout, yout; - - xout = (*x * m->M11) + (*y * m->M21); - yout = (*x * m->M12) + (*y * m->M22); - *x = xout; - *y = yout; + m2 = *m; + m2.M31 = 0; + m2.M32 = 0; + uiDrawMatrixTransformPoint(&m2, x, y); } diff --git a/common/opentype.c b/common/opentype.c new file mode 100644 index 00000000..e579b612 --- /dev/null +++ b/common/opentype.c @@ -0,0 +1,165 @@ +// 25 february 2018 +#include +#include "../ui.h" +#include "uipriv.h" +#include "attrstr.h" + +struct feature { + char a; + char b; + char c; + char d; + uint32_t value; +}; + +struct uiOpenTypeFeatures { + struct feature *data; + size_t len; + size_t cap; +}; + +#define bytecount(n) ((n) * sizeof (struct feature)) + +uiOpenTypeFeatures *uiNewOpenTypeFeatures(void) +{ + uiOpenTypeFeatures *otf; + + otf = uiprivNew(uiOpenTypeFeatures); + otf->cap = 16; + otf->data = (struct feature *) uiprivAlloc(bytecount(otf->cap), "struct feature[]"); + otf->len = 0; + return otf; +} + +void uiFreeOpenTypeFeatures(uiOpenTypeFeatures *otf) +{ + uiprivFree(otf->data); + uiprivFree(otf); +} + +uiOpenTypeFeatures *uiOpenTypeFeaturesClone(const uiOpenTypeFeatures *otf) +{ + uiOpenTypeFeatures *ret; + + ret = uiprivNew(uiOpenTypeFeatures); + ret->len = otf->len; + ret->cap = otf->cap; + ret->data = (struct feature *) uiprivAlloc(bytecount(ret->cap), "struct feature[]"); + memset(ret->data, 0, bytecount(ret->cap)); + memmove(ret->data, otf->data, bytecount(ret->len)); + return ret; +} + +#define intdiff(a, b) (((int) (a)) - ((int) (b))) + +static int featurecmp(const void *a, const void *b) +{ + const struct feature *f = (const struct feature *) a; + const struct feature *g = (const struct feature *) b; + + if (f->a != g->a) + return intdiff(f->a, g->a); + if (f->b != g->b) + return intdiff(f->b, g->b); + if (f->c != g->c) + return intdiff(f->c, g->c); + return intdiff(f->d, g->d); +} + +static struct feature mkkey(char a, char b, char c, char d) +{ + struct feature f; + + f.a = a; + f.b = b; + f.c = c; + f.d = d; + return f; +} + +#define find(pkey, otf) bsearch(pkey, otf->data, otf->len, sizeof (struct feature), featurecmp) + +void uiOpenTypeFeaturesAdd(uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value) +{ + struct feature *f; + struct feature key; + + // replace existing value if any + key = mkkey(a, b, c, d); + f = (struct feature *) find(&key, otf); + if (f != NULL) { + f->value = value; + return; + } + + // if we got here, the tag is new + if (otf->len == otf->cap) { + otf->cap *= 2; + otf->data = (struct feature *) uiprivRealloc(otf->data, bytecount(otf->cap), "struct feature[]"); + } + f = otf->data + otf->len; + f->a = a; + f->b = b; + f->c = c; + f->d = d; + f->value = value; + // LONGTERM qsort here is overkill + otf->len++; + qsort(otf->data, otf->len, sizeof (struct feature), featurecmp); +} + +void uiOpenTypeFeaturesRemove(uiOpenTypeFeatures *otf, char a, char b, char c, char d) +{ + struct feature *f; + struct feature key; + ptrdiff_t index; + size_t count; + + key = mkkey(a, b, c, d); + f = (struct feature *) find(&key, otf); + if (f == NULL) + return; + + index = f - otf->data; + count = otf->len - index - 1; + memmove(f + 1, f, bytecount(count)); + otf->len--; +} + +int uiOpenTypeFeaturesGet(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t *value) +{ + const struct feature *f; + struct feature key; + + key = mkkey(a, b, c, d); + f = (const struct feature *) find(&key, otf); + if (f == NULL) + return 0; + *value = f->value; + return 1; +} + +void uiOpenTypeFeaturesForEach(const uiOpenTypeFeatures *otf, uiOpenTypeFeaturesForEachFunc f, void *data) +{ + size_t n; + const struct feature *p; + uiForEach ret; + + p = otf->data; + for (n = 0; n < otf->len; n++) { + ret = (*f)(otf, p->a, p->b, p->c, p->d, p->value, data); + // TODO for all: require exact match? + if (ret == uiForEachStop) + return; + p++; + } +} + +int uiprivOpenTypeFeaturesEqual(const uiOpenTypeFeatures *a, const uiOpenTypeFeatures *b) +{ + if (a == b) + return 1; + if (a->len != b->len) + return 0; + return memcmp(a->data, b->data, bytecount(a->len)) == 0; +} diff --git a/common/shouldquit.c b/common/shouldquit.c index 4e7aa5c3..df57b6c5 100644 --- a/common/shouldquit.c +++ b/common/shouldquit.c @@ -8,7 +8,7 @@ static int defaultOnShouldQuit(void *data) } static int (*onShouldQuit)(void *) = defaultOnShouldQuit; -static void *onShouldQuitData; +static void *onShouldQuitData = NULL; void uiOnShouldQuit(int (*f)(void *), void *data) { @@ -16,7 +16,7 @@ void uiOnShouldQuit(int (*f)(void *), void *data) onShouldQuitData = data; } -int shouldQuit(void) +int uiprivShouldQuit(void) { return (*onShouldQuit)(onShouldQuitData); } diff --git a/common/table.h b/common/table.h new file mode 100644 index 00000000..f83205df --- /dev/null +++ b/common/table.h @@ -0,0 +1,20 @@ +// 23 june 2018 + +#ifdef __cplusplus +extern "C" { +#endif + +// tablemodel.c +extern uiTableModelHandler *uiprivTableModelHandler(uiTableModel *m); +extern int uiprivTableModelNumColumns(uiTableModel *m); +extern uiTableValueType uiprivTableModelColumnType(uiTableModel *m, int column); +extern int uiprivTableModelNumRows(uiTableModel *m); +extern uiTableValue *uiprivTableModelCellValue(uiTableModel *m, int row, int column); +extern void uiprivTableModelSetCellValue(uiTableModel *m, int row, int column, const uiTableValue *value); +extern const uiTableTextColumnOptionalParams uiprivDefaultTextColumnOptionalParams; +extern int uiprivTableModelCellEditable(uiTableModel *m, int row, int column); +extern int uiprivTableModelColorIfProvided(uiTableModel *m, int row, int column, double *r, double *g, double *b, double *a); + +#ifdef __cplusplus +} +#endif diff --git a/common/tablemodel.c b/common/tablemodel.c new file mode 100644 index 00000000..dbda4a81 --- /dev/null +++ b/common/tablemodel.c @@ -0,0 +1,79 @@ +// 23 june 2018 +#include "../ui.h" +#include "uipriv.h" +#include "table.h" + +int uiprivTableModelNumColumns(uiTableModel *m) +{ + uiTableModelHandler *mh; + + mh = uiprivTableModelHandler(m); + return (*(mh->NumColumns))(mh, m); +} + +uiTableValueType uiprivTableModelColumnType(uiTableModel *m, int column) +{ + uiTableModelHandler *mh; + + mh = uiprivTableModelHandler(m); + return (*(mh->ColumnType))(mh, m, column); +} + +int uiprivTableModelNumRows(uiTableModel *m) +{ + uiTableModelHandler *mh; + + mh = uiprivTableModelHandler(m); + return (*(mh->NumRows))(mh, m); +} + +uiTableValue *uiprivTableModelCellValue(uiTableModel *m, int row, int column) +{ + uiTableModelHandler *mh; + + mh = uiprivTableModelHandler(m); + return (*(mh->CellValue))(mh, m, row, column); +} + +void uiprivTableModelSetCellValue(uiTableModel *m, int row, int column, const uiTableValue *value) +{ + uiTableModelHandler *mh; + + mh = uiprivTableModelHandler(m); + (*(mh->SetCellValue))(mh, m, row, column, value); +} + +const uiTableTextColumnOptionalParams uiprivDefaultTextColumnOptionalParams = { + .ColorModelColumn = -1, +}; + +int uiprivTableModelCellEditable(uiTableModel *m, int row, int column) +{ + uiTableValue *value; + int editable; + + switch (column) { + case uiTableModelColumnNeverEditable: + return 0; + case uiTableModelColumnAlwaysEditable: + return 1; + } + value = uiprivTableModelCellValue(m, row, column); + editable = uiTableValueInt(value); + uiFreeTableValue(value); + return editable; +} + +int uiprivTableModelColorIfProvided(uiTableModel *m, int row, int column, double *r, double *g, double *b, double *a) +{ + uiTableValue *value; + + if (column == -1) + return 0; + value = uiprivTableModelCellValue(m, row, column); + if (value == NULL) + return 0; + uiTableValueColor(value, r, g, b, a); + uiFreeTableValue(value); + return 1; +} diff --git a/common/tablevalue.c b/common/tablevalue.c new file mode 100644 index 00000000..10043de6 --- /dev/null +++ b/common/tablevalue.c @@ -0,0 +1,106 @@ +// 3 june 2018 +#include "../ui.h" +#include "uipriv.h" +#include "table.h" + +struct uiTableValue { + uiTableValueType type; + union { + char *str; + uiImage *img; + int i; + struct { + double r; + double g; + double b; + double a; + } color; + } u; +}; + +static uiTableValue *newTableValue(uiTableValueType type) +{ + uiTableValue *v; + + v = uiprivNew(uiTableValue); + v->type = type; + return v; +} + +void uiFreeTableValue(uiTableValue *v) +{ + switch (v->type) { + case uiTableValueTypeString: + uiprivFree(v->u.str); + break; + } + uiprivFree(v); +} + +uiTableValueType uiTableValueGetType(const uiTableValue *v) +{ + return v->type; +} + +uiTableValue *uiNewTableValueString(const char *str) +{ + uiTableValue *v; + + v = newTableValue(uiTableValueTypeString); + v->u.str = (char *) uiprivAlloc((strlen(str) + 1) * sizeof (char), "char[] (uiTableValue)"); + strcpy(v->u.str, str); + return v; +} + +const char *uiTableValueString(const uiTableValue *v) +{ + return v->u.str; +} + +uiTableValue *uiNewTableValueImage(uiImage *img) +{ + uiTableValue *v; + + v = newTableValue(uiTableValueTypeImage); + v->u.img = img; + return v; +} + +uiImage *uiTableValueImage(const uiTableValue *v) +{ + return v->u.img; +} + +uiTableValue *uiNewTableValueInt(int i) +{ + uiTableValue *v; + + v = newTableValue(uiTableValueTypeInt); + v->u.i = i; + return v; +} + +int uiTableValueInt(const uiTableValue *v) +{ + return v->u.i; +} + +uiTableValue *uiNewTableValueColor(double r, double g, double b, double a) +{ + uiTableValue *v; + + v = newTableValue(uiTableValueTypeColor); + v->u.color.r = r; + v->u.color.g = g; + v->u.color.b = b; + v->u.color.a = a; + return v; +} + +void uiTableValueColor(const uiTableValue *v, double *r, double *g, double *b, double *a) +{ + *r = v->u.color.r; + *g = v->u.color.g; + *b = v->u.color.b; + *a = v->u.color.a; +} diff --git a/common/uipriv.h b/common/uipriv.h index c8b6b6f0..6441ada5 100644 --- a/common/uipriv.h +++ b/common/uipriv.h @@ -1,53 +1,66 @@ // 6 april 2015 +// note: this file should not include ui.h, as the OS-specific ui_*.h files are included between that one and this one in the OS-specific uipriv_*.h* files +#include +#include +#include "controlsigs.h" +#include "utf.h" + #ifdef __cplusplus extern "C" { #endif -// TODO stdlib.h needed? -#include -#include "controlsigs.h" +// OS-specific init.* or main.* files +extern uiInitOptions uiprivOptions; -extern uiInitOptions options; +// OS-specific alloc.* files +extern void *uiprivAlloc(size_t, const char *); +#define uiprivNew(T) ((T *) uiprivAlloc(sizeof (T), #T)) +extern void *uiprivRealloc(void *, size_t, const char *); +extern void uiprivFree(void *); -extern void *uiAlloc(size_t, const char *); -#define uiNew(T) ((T *) uiAlloc(sizeof (T), #T)) -extern void *uiRealloc(void *, size_t, const char *); -extern void uiFree(void *); - -extern void complain(const char *, ...); - -// control.c -extern uiControl *newControl(size_t size, uint32_t OSsig, uint32_t typesig, const char *typenamestr); +// debug.c and OS-specific debug.* files +// TODO get rid of this mess... +// ugh, __func__ was only introduced in MSVC 2015... +#ifdef _MSC_VER +#define uiprivMacro__func__ __FUNCTION__ +#else +#define uiprivMacro__func__ __func__ +#endif +extern void uiprivRealBug(const char *file, const char *line, const char *func, const char *prefix, const char *format, va_list ap); +#define uiprivMacro_ns2(s) #s +#define uiprivMacro_ns(s) uiprivMacro_ns2(s) +extern void uiprivDoImplBug(const char *file, const char *line, const char *func, const char *format, ...); +#define uiprivImplBug(...) uiprivDoImplBug(__FILE__, uiprivMacro_ns(__LINE__), uiprivMacro__func__, __VA_ARGS__) +extern void uiprivDoUserBug(const char *file, const char *line, const char *func, const char *format, ...); +#define uiprivUserBug(...) uiprivDoUserBug(__FILE__, uiprivMacro_ns(__LINE__), uiprivMacro__func__, __VA_ARGS__) // shouldquit.c -extern int shouldQuit(void); +extern int uiprivShouldQuit(void); // areaevents.c -typedef struct clickCounter clickCounter; +typedef struct uiprivClickCounter uiprivClickCounter; // you should call Reset() to zero-initialize a new instance // it doesn't matter that all the non-count fields are zero: the first click will fail the curButton test straightaway, so it'll return 1 and set the rest of the structure accordingly -struct clickCounter { - uintmax_t curButton; - intmax_t rectX0; - intmax_t rectY0; - intmax_t rectX1; - intmax_t rectY1; +struct uiprivClickCounter { + int curButton; + int rectX0; + int rectY0; + int rectX1; + int rectY1; uintptr_t prevTime; - uintmax_t count; + int count; }; -extern uintmax_t clickCounterClick(clickCounter *, uintmax_t, intmax_t, intmax_t, uintptr_t, uintptr_t, intmax_t, intmax_t); -extern void clickCounterReset(clickCounter *); -extern int fromScancode(uintptr_t, uiAreaKeyEvent *); +extern int uiprivClickCounterClick(uiprivClickCounter *c, int button, int x, int y, uintptr_t time, uintptr_t maxTime, int32_t xdist, int32_t ydist); +extern void uiprivClickCounterReset(uiprivClickCounter *); +extern int uiprivFromScancode(uintptr_t, uiAreaKeyEvent *); // matrix.c -extern void setIdentity(uiDrawMatrix *); -extern void fallbackSkew(uiDrawMatrix *, double, double, double, double); -extern void fallbackTranslate(uiDrawMatrix *, double, double); -extern void scaleCenter(double, double, double *, double *); -extern void fallbackScale(uiDrawMatrix *, double, double, double, double); -extern void fallbackMultiply(uiDrawMatrix *, uiDrawMatrix *); -extern void fallbackTransformPoint(uiDrawMatrix *, double *, double *); -extern void fallbackTransformSize(uiDrawMatrix *, double *, double *); +extern void uiprivFallbackSkew(uiDrawMatrix *, double, double, double, double); +extern void uiprivScaleCenter(double, double, double *, double *); +extern void uiprivFallbackTransformSize(uiDrawMatrix *, double *, double *); + +// OS-specific text.* files +extern int uiprivStricmp(const char *a, const char *b); #ifdef __cplusplus } diff --git a/common/userbugs.c b/common/userbugs.c new file mode 100644 index 00000000..09cc703d --- /dev/null +++ b/common/userbugs.c @@ -0,0 +1,8 @@ +// 22 may 2016 +#include "../ui.h" +#include "uipriv.h" + +void uiUserBugCannotSetParentOnToplevel(const char *type) +{ + uiprivUserBug("You cannot make a %s a child of another uiControl,", type); +} diff --git a/common/utf.c b/common/utf.c new file mode 100644 index 00000000..5577529b --- /dev/null +++ b/common/utf.c @@ -0,0 +1,348 @@ +// utf by pietro gagliardi (andlabs) — https://github.com/andlabs/utf/ +// 10 november 2016 +// function names have been altered to avoid namespace collisions in libui static builds (see utf.h) +#include "utf.h" + +// this code imitates Go's unicode/utf8 and unicode/utf16 +// the biggest difference is that a rune is unsigned instead of signed (because Go guarantees what a right shift on a signed number will do, whereas C does not) +// it is also an imitation so we can license it under looser terms than the Go source +#define badrune 0xFFFD + +// encoded must be at most 4 bytes +// TODO clean this code up somehow +size_t uiprivUTF8EncodeRune(uint32_t rune, char *encoded) +{ + uint8_t b, c, d, e; + size_t n; + + // not in the valid range for Unicode + if (rune > 0x10FFFF) + rune = badrune; + // surrogate runes cannot be encoded + if (rune >= 0xD800 && rune < 0xE000) + rune = badrune; + + if (rune < 0x80) { // ASCII bytes represent themselves + b = (uint8_t) (rune & 0xFF); + n = 1; + goto done; + } + if (rune < 0x800) { // two-byte encoding + c = (uint8_t) (rune & 0x3F); + c |= 0x80; + rune >>= 6; + b = (uint8_t) (rune & 0x1F); + b |= 0xC0; + n = 2; + goto done; + } + if (rune < 0x10000) { // three-byte encoding + d = (uint8_t) (rune & 0x3F); + d |= 0x80; + rune >>= 6; + c = (uint8_t) (rune & 0x3F); + c |= 0x80; + rune >>= 6; + b = (uint8_t) (rune & 0x0F); + b |= 0xE0; + n = 3; + goto done; + } + // otherwise use a four-byte encoding + e = (uint8_t) (rune & 0x3F); + e |= 0x80; + rune >>= 6; + d = (uint8_t) (rune & 0x3F); + d |= 0x80; + rune >>= 6; + c = (uint8_t) (rune & 0x3F); + c |= 0x80; + rune >>= 6; + b = (uint8_t) (rune & 0x07); + b |= 0xF0; + n = 4; + +done: + encoded[0] = b; + if (n > 1) + encoded[1] = c; + if (n > 2) + encoded[2] = d; + if (n > 3) + encoded[3] = e; + return n; +} + +const char *uiprivUTF8DecodeRune(const char *s, size_t nElem, uint32_t *rune) +{ + uint8_t b, c; + uint8_t lowestAllowed, highestAllowed; + size_t i, expected; + int bad; + + b = (uint8_t) (*s); + if (b < 0x80) { // ASCII bytes represent themselves + *rune = b; + s++; + return s; + } + // 0xC0 and 0xC1 cover 2-byte overlong equivalents + // 0xF5 to 0xFD cover values > 0x10FFFF + // 0xFE and 0xFF were never defined (always illegal) + if (b < 0xC2 || b > 0xF4) { // invalid + *rune = badrune; + s++; + return s; + } + + // this determines the range of allowed first continuation bytes + lowestAllowed = 0x80; + highestAllowed = 0xBF; + switch (b) { + case 0xE0: + // disallow 3-byte overlong equivalents + lowestAllowed = 0xA0; + break; + case 0xED: + // disallow surrogate characters + highestAllowed = 0x9F; + break; + case 0xF0: + // disallow 4-byte overlong equivalents + lowestAllowed = 0x90; + break; + case 0xF4: + // disallow values > 0x10FFFF + highestAllowed = 0x8F; + break; + } + + // and this determines how many continuation bytes are expected + expected = 1; + if (b >= 0xE0) + expected++; + if (b >= 0xF0) + expected++; + if (nElem != 0) { // are there enough bytes? + nElem--; + if (nElem < expected) { // nope + *rune = badrune; + s++; + return s; + } + } + + // ensure that everything is correct + // if not, **only** consume the initial byte + bad = 0; + for (i = 0; i < expected; i++) { + c = (uint8_t) (s[1 + i]); + if (c < lowestAllowed || c > highestAllowed) { + bad = 1; + break; + } + // the old lowestAllowed and highestAllowed is only for the first continuation byte + lowestAllowed = 0x80; + highestAllowed = 0xBF; + } + if (bad) { + *rune = badrune; + s++; + return s; + } + + // now do the topmost bits + if (b < 0xE0) + *rune = b & 0x1F; + else if (b < 0xF0) + *rune = b & 0x0F; + else + *rune = b & 0x07; + s++; // we can finally move on + + // now do the continuation bytes + for (; expected; expected--) { + c = (uint8_t) (*s); + s++; + c &= 0x3F; // strip continuation bits + *rune <<= 6; + *rune |= c; + } + + return s; +} + +// encoded must have at most 2 elements +size_t uiprivUTF16EncodeRune(uint32_t rune, uint16_t *encoded) +{ + uint16_t low, high; + + // not in the valid range for Unicode + if (rune > 0x10FFFF) + rune = badrune; + // surrogate runes cannot be encoded + if (rune >= 0xD800 && rune < 0xE000) + rune = badrune; + + if (rune < 0x10000) { + encoded[0] = (uint16_t) rune; + return 1; + } + + rune -= 0x10000; + low = (uint16_t) (rune & 0x3FF); + rune >>= 10; + high = (uint16_t) (rune & 0x3FF); + encoded[0] = high | 0xD800; + encoded[1] = low | 0xDC00; + return 2; +} + +// TODO see if this can be cleaned up somehow +const uint16_t *uiprivUTF16DecodeRune(const uint16_t *s, size_t nElem, uint32_t *rune) +{ + uint16_t high, low; + + if (*s < 0xD800 || *s >= 0xE000) { + // self-representing character + *rune = *s; + s++; + return s; + } + if (*s >= 0xDC00) { + // out-of-order surrogates + *rune = badrune; + s++; + return s; + } + if (nElem == 1) { // not enough elements + *rune = badrune; + s++; + return s; + } + high = *s; + high &= 0x3FF; + if (s[1] < 0xDC00 || s[1] >= 0xE000) { + // bad surrogate pair + *rune = badrune; + s++; + return s; + } + s++; + low = *s; + s++; + low &= 0x3FF; + *rune = high; + *rune <<= 10; + *rune |= low; + *rune += 0x10000; + return s; +} + +// TODO find a way to reduce the code in all of these somehow +// TODO find a way to remove u as well +size_t uiprivUTF8RuneCount(const char *s, size_t nElem) +{ + size_t len; + uint32_t rune; + + if (nElem != 0) { + const char *t, *u; + + len = 0; + t = s; + while (nElem != 0) { + u = uiprivUTF8DecodeRune(t, nElem, &rune); + len++; + nElem -= u - t; + t = u; + } + return len; + } + len = 0; + while (*s) { + s = uiprivUTF8DecodeRune(s, nElem, &rune); + len++; + } + return len; +} + +size_t uiprivUTF8UTF16Count(const char *s, size_t nElem) +{ + size_t len; + uint32_t rune; + uint16_t encoded[2]; + + if (nElem != 0) { + const char *t, *u; + + len = 0; + t = s; + while (nElem != 0) { + u = uiprivUTF8DecodeRune(t, nElem, &rune); + len += uiprivUTF16EncodeRune(rune, encoded); + nElem -= u - t; + t = u; + } + return len; + } + len = 0; + while (*s) { + s = uiprivUTF8DecodeRune(s, nElem, &rune); + len += uiprivUTF16EncodeRune(rune, encoded); + } + return len; +} + +size_t uiprivUTF16RuneCount(const uint16_t *s, size_t nElem) +{ + size_t len; + uint32_t rune; + + if (nElem != 0) { + const uint16_t *t, *u; + + len = 0; + t = s; + while (nElem != 0) { + u = uiprivUTF16DecodeRune(t, nElem, &rune); + len++; + nElem -= u - t; + t = u; + } + return len; + } + len = 0; + while (*s) { + s = uiprivUTF16DecodeRune(s, nElem, &rune); + len++; + } + return len; +} + +size_t uiprivUTF16UTF8Count(const uint16_t *s, size_t nElem) +{ + size_t len; + uint32_t rune; + char encoded[4]; + + if (nElem != 0) { + const uint16_t *t, *u; + + len = 0; + t = s; + while (nElem != 0) { + u = uiprivUTF16DecodeRune(t, nElem, &rune); + len += uiprivUTF8EncodeRune(rune, encoded); + nElem -= u - t; + t = u; + } + return len; + } + len = 0; + while (*s) { + s = uiprivUTF16DecodeRune(s, nElem, &rune); + len += uiprivUTF8EncodeRune(rune, encoded); + } + return len; +} diff --git a/common/utf.h b/common/utf.h new file mode 100644 index 00000000..eafcea0d --- /dev/null +++ b/common/utf.h @@ -0,0 +1,105 @@ +// utf by pietro gagliardi (andlabs) — https://github.com/andlabs/utf/ +// 10 november 2016 + +// note the overridden names with uipriv at the beginning; this avoids potential symbol clashes when building libui as a static library +// LONGTERM find a way to encode the name overrides directly into the utf library + +#ifdef __cplusplus +extern "C" { +#endif + +// TODO (for utf itself as well) should this go outside the extern "C" block or not +#include +#include + +// if nElem == 0, assume the buffer has no upper limit and is '\0' terminated +// otherwise, assume buffer is NOT '\0' terminated but is bounded by nElem *elements* + +extern size_t uiprivUTF8EncodeRune(uint32_t rune, char *encoded); +extern const char *uiprivUTF8DecodeRune(const char *s, size_t nElem, uint32_t *rune); +extern size_t uiprivUTF16EncodeRune(uint32_t rune, uint16_t *encoded); +extern const uint16_t *uiprivUTF16DecodeRune(const uint16_t *s, size_t nElem, uint32_t *rune); + +extern size_t uiprivUTF8RuneCount(const char *s, size_t nElem); +extern size_t uiprivUTF8UTF16Count(const char *s, size_t nElem); +extern size_t uiprivUTF16RuneCount(const uint16_t *s, size_t nElem); +extern size_t uiprivUTF16UTF8Count(const uint16_t *s, size_t nElem); + +#ifdef __cplusplus +} + +// TODO sync this back to upstream (need copyright clearance first) + +// On Windows, wchar_t is equivalent to uint16_t, but C++ requires +// wchar_t to be a completely distinct type. These overloads allow +// passing wchar_t pointers directly into these functions from C++ +// on Windows. Otherwise, you'd need to cast to pass a wchar_t +// pointer, WCHAR pointer, or equivalent to these functions. +// +// This does not apply to MSVC because the situation there is +// slightly more complicated; see below. +#if defined(_WIN32) && !defined(_MSC_VER) + +inline size_t uiprivUTF16EncodeRune(uint32_t rune, wchar_t *encoded) +{ + return uiprivUTF16EncodeRune(rune, reinterpret_cast(encoded)); +} + +inline const wchar_t *uiprivUTF16DecodeRune(const wchar_t *s, size_t nElem, uint32_t *rune) +{ + const uint16_t *ret; + + ret = uiprivUTF16DecodeRune(reinterpret_cast(s), nElem, rune); + return reinterpret_cast(ret); +} + +inline size_t uiprivUTF16RuneCount(const wchar_t *s, size_t nElem) +{ + return uiprivUTF16RuneCount(reinterpret_cast(s), nElem); +} + +inline size_t uiprivUTF16UTF8Count(const wchar_t *s, size_t nElem) +{ + return uiprivUTF16UTF8Count(reinterpret_cast(s), nElem); +} + +#endif + +// This is the same as the above, except that with MSVC, whether +// wchar_t is a keyword or not is controlled by a compiler option! +// (At least with gcc, this is not the case; thanks redi in +// irc.freenode.net/#gcc.) We use __wchar_t to be independent of +// the option; see https://blogs.msdn.microsoft.com/oldnewthing/20161201-00/?p=94836 +// (ironically posted one day after I initially wrote this code!). +// TODO should defined(_WIN32) be used too? +// TODO check this under /Wall +// TODO are C-style casts enough? or will that fail in /Wall? +// TODO same for UniChar/unichar on Mac? if both are unsigned then we have nothing to worry about +#if defined(_MSC_VER) + +inline size_t uiprivUTF16EncodeRune(uint32_t rune, __wchar_t *encoded) +{ + return uiprivUTF16EncodeRune(rune, reinterpret_cast(encoded)); +} + +inline const __wchar_t *uiprivUTF16DecodeRune(const __wchar_t *s, size_t nElem, uint32_t *rune) +{ + const uint16_t *ret; + + ret = uiprivUTF16DecodeRune(reinterpret_cast(s), nElem, rune); + return reinterpret_cast(ret); +} + +inline size_t uiprivUTF16RuneCount(const __wchar_t *s, size_t nElem) +{ + return uiprivUTF16RuneCount(reinterpret_cast(s), nElem); +} + +inline size_t uiprivUTF16UTF8Count(const __wchar_t *s, size_t nElem) +{ + return uiprivUTF16UTF8Count(reinterpret_cast(s), nElem); +} + +#endif + +#endif diff --git a/darwin/CMakeLists.txt b/darwin/CMakeLists.txt new file mode 100644 index 00000000..bd7d576a --- /dev/null +++ b/darwin/CMakeLists.txt @@ -0,0 +1,64 @@ +# 3 june 2016 + +list(APPEND _LIBUI_SOURCES + darwin/aat.m + darwin/alloc.m + darwin/area.m + darwin/areaevents.m + darwin/attrstr.m + darwin/autolayout.m + darwin/box.m + darwin/button.m + darwin/checkbox.m + darwin/colorbutton.m + darwin/combobox.m + darwin/control.m + darwin/datetimepicker.m + darwin/debug.m + darwin/draw.m + darwin/drawtext.m + darwin/editablecombo.m + darwin/entry.m + darwin/fontbutton.m + darwin/fontmatch.m + darwin/fonttraits.m + darwin/fontvariation.m + darwin/form.m + darwin/future.m + darwin/graphemes.m + darwin/grid.m + darwin/group.m + darwin/image.m + darwin/label.m + darwin/main.m + darwin/map.m + darwin/menu.m + darwin/multilineentry.m + darwin/opentype.m + darwin/progressbar.m + darwin/radiobuttons.m + darwin/scrollview.m + darwin/separator.m + darwin/slider.m + darwin/spinbox.m + darwin/stddialogs.m + darwin/tab.m + darwin/table.m + darwin/tablecolumn.m + darwin/text.m + darwin/undocumented.m + darwin/util.m + darwin/window.m + darwin/winmoveresize.m +) +set(_LIBUI_SOURCES ${_LIBUI_SOURCES} PARENT_SCOPE) + +# TODO is this correct? +list(APPEND _LIBUI_INCLUDEDIRS + darwin +) +set(_LIBUI_INCLUDEDIRS _LIBUI_INCLUDEDIRS PARENT_SCOPE) + +set(_LIBUI_LIBS + objc "-framework Foundation" "-framework AppKit" +PARENT_SCOPE) diff --git a/darwin/GNUfiles.mk b/darwin/GNUfiles.mk deleted file mode 100644 index 49acd1d4..00000000 --- a/darwin/GNUfiles.mk +++ /dev/null @@ -1,62 +0,0 @@ -# 28 april 2015 - -MFILES += \ - darwin/alloc.m \ - darwin/area.m \ - darwin/areaevents.m \ - darwin/autolayout.m \ - darwin/box.m \ - darwin/button.m \ - darwin/checkbox.m \ - darwin/combobox.m \ - darwin/control.m \ - darwin/datetimepicker.m \ - darwin/draw.m \ - darwin/drawtext.m \ - darwin/entry.m \ - darwin/fontbutton.m \ - darwin/group.m \ - darwin/label.m \ - darwin/main.m \ - darwin/map.m \ - darwin/menu.m \ - darwin/multilineentry.m \ - darwin/progressbar.m \ - darwin/radiobuttons.m \ - darwin/separator.m \ - darwin/slider.m \ - darwin/spinbox.m \ - darwin/stddialogs.m \ - darwin/tab.m \ - darwin/text.m \ - darwin/util.m \ - darwin/window.m - -HFILES += \ - darwin/uipriv_darwin.h - -# TODO split into a separate file or put in GNUmakefile.libui somehow? - -# flags for Cocoa -LDFLAGS += \ - -lobjc \ - -framework Foundation \ - -framework AppKit - -# flags for OS X versioning -CFLAGS += \ - -mmacosx-version-min=10.7 \ - -DMACOSX_DEPLOYMENT_TARGET=10.7 -CXXFLAGS += \ - -mmacosx-version-min=10.7 \ - -DMACOSX_DEPLOYMENT_TARGET=10.7 -LDFLAGS += \ - -mmacosx-version-min=10.7 - -# flags for building a shared library -LDFLAGS += \ - -dynamiclib - -# on warning about undefined symbols: -# the gcc flags don't work with Apple's linker -# fortunately, we don't need any; Apple's linker warns about undefined symbols in -shared builds! diff --git a/darwin/GNUinstall.mk b/darwin/GNUinstall.mk deleted file mode 100644 index 41df8837..00000000 --- a/darwin/GNUinstall.mk +++ /dev/null @@ -1,9 +0,0 @@ -ifndef PREFIX - PREFIX=/usr -endif - -# Incorrect for Mac Os X, this should be easy to fix -install: $(OUT) - cp $(OUT) $(DESTDIR)$(PREFIX)/lib/libui.0.dylib - ln -s libui.0.dylib $(DESTDIR)$(PREFIX)/lib/libui.dylib - cp ui.h ui_$(OS).h $(DESTDIR)$(PREFIX)/include/ diff --git a/darwin/GNUosspecific.mk b/darwin/GNUosspecific.mk deleted file mode 100644 index 39acdaa4..00000000 --- a/darwin/GNUosspecific.mk +++ /dev/null @@ -1,13 +0,0 @@ -# 16 october 2015 - -EXESUFFIX = -LIBSUFFIX = .dylib -OSHSUFFIX = .h -TOOLCHAIN = gcc - -USESSONAME = 1 -SOVERSION = $(SOVERSIONA) -SONAMEEXT = .$(SOVERSION)$(LIBSUFFIX) -# note the explicit need for @rpath -# TODO -current_version, -compatibility_version -SONAMEFLAG = -Wl,-install_name,@rpath/ diff --git a/darwin/OLD_table.m b/darwin/OLD_table.m new file mode 100644 index 00000000..18231f55 --- /dev/null +++ b/darwin/OLD_table.m @@ -0,0 +1,39 @@ +// 21 june 2016 +#import "uipriv_darwin.h" + +// TODOs +// - header cell seems off +// - background color shows up for a line or two below selection +// - editable NSTextFields have no intrinsic width +// - is the Y position of checkbox cells correct? + +@implementation tablePart + +- (NSView *)mkView:(uiTableModel *)m row:(int)row +{ + // if stretchy, don't hug, otherwise hug forcibly + if (self.expand) + [view setContentHuggingPriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal]; + else + [view setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal]; +} + +@end + +uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name) +{ + uiTableColumn *c; + + c = uiprivNew(uiTableColumn); + c->c = [[tableColumn alloc] initWithIdentifier:@""]; + c->c.libui_col = c; + // via Interface Builder + [c->c setResizingMask:(NSTableColumnAutoresizingMask | NSTableColumnUserResizingMask)]; + // 10.10 adds -[NSTableColumn setTitle:]; before then we have to do this + [[c->c headerCell] setStringValue:uiprivToNSString(name)]; + // TODO is this sufficient? + [[c->c headerCell] setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]]; + c->parts = [NSMutableArray new]; + [t->tv addTableColumn:c->c]; + return c; +} diff --git a/darwin/aat.m b/darwin/aat.m new file mode 100644 index 00000000..05f0b268 --- /dev/null +++ b/darwin/aat.m @@ -0,0 +1,403 @@ +// 14 february 2017 +#import "uipriv_darwin.h" +#import "attrstr.h" + +// TODO explain the purpose of this file + +static void boolspec(uint32_t value, uint16_t type, uint16_t ifTrue, uint16_t ifFalse, uiprivAATBlock f) +{ + // TODO are values other than 1 accepted for true by OpenType itself? (same for the rest of the file) + if (value != 0) { + f(type, ifTrue); + return; + } + f(type, ifFalse); +} + +// TODO remove the need for this +// TODO remove x8tox32() +#define x8tox32(x) ((uint32_t) (((uint8_t) (x)) & 0xFF)) +#define mkTag(a, b, c, d) \ + ((x8tox32(a) << 24) | \ + (x8tox32(b) << 16) | \ + (x8tox32(c) << 8) | \ + x8tox32(d)) + +// TODO double-check drawtext example to make sure all of these are used properly (I already screwed dlig up by putting clig twice instead) +void uiprivOpenTypeToAAT(char a, char b, char c, char d, uint32_t value, uiprivAATBlock f) +{ + switch (mkTag(a, b, c, d)) { + case mkTag('l', 'i', 'g', 'a'): + boolspec(value, kLigaturesType, + kCommonLigaturesOnSelector, + kCommonLigaturesOffSelector, + f); + break; + case mkTag('r', 'l', 'i', 'g'): + boolspec(value, kLigaturesType, + kRequiredLigaturesOnSelector, + kRequiredLigaturesOffSelector, + f); + break; + case mkTag('d', 'l', 'i', 'g'): + boolspec(value, kLigaturesType, + kRareLigaturesOnSelector, + kRareLigaturesOffSelector, + f); + break; + case mkTag('c', 'l', 'i', 'g'): + boolspec(value, kLigaturesType, + kContextualLigaturesOnSelector, + kContextualLigaturesOffSelector, + f); + break; + case mkTag('h', 'l', 'i', 'g'): + // This technically isn't what is meant by "historical ligatures", but Core Text's internal AAT-to-OpenType mapping says to include it, so we include it too + case mkTag('h', 'i', 's', 't'): + boolspec(value, kLigaturesType, + kHistoricalLigaturesOnSelector, + kHistoricalLigaturesOffSelector, + f); + break; + case mkTag('u', 'n', 'i', 'c'): + // TODO is this correct, or should we provide an else case? + if (value != 0) + // this is undocumented; it comes from Core Text's internal AAT-to-OpenType conversion table + f(kLetterCaseType, 14); + break; + + // TODO will the following handle all cases properly, or are elses going to be needed? + case mkTag('p', 'n', 'u', 'm'): + if (value != 0) + f(kNumberSpacingType, kProportionalNumbersSelector); + break; + case mkTag('t', 'n', 'u', 'm'): + if (value != 0) + f(kNumberSpacingType, kMonospacedNumbersSelector); + break; + + // TODO will the following handle all cases properly, or are elses going to be needed? + case mkTag('s', 'u', 'p', 's'): + if (value != 0) + f(kVerticalPositionType, kSuperiorsSelector); + break; + case mkTag('s', 'u', 'b', 's'): + if (value != 0) + f(kVerticalPositionType, kInferiorsSelector); + break; + case mkTag('o', 'r', 'd', 'n'): + if (value != 0) + f(kVerticalPositionType, kOrdinalsSelector); + break; + case mkTag('s', 'i', 'n', 'f'): + if (value != 0) + f(kVerticalPositionType, kScientificInferiorsSelector); + break; + + // TODO will the following handle all cases properly, or are elses going to be needed? + case mkTag('a', 'f', 'r', 'c'): + if (value != 0) + f(kFractionsType, kVerticalFractionsSelector); + break; + case mkTag('f', 'r', 'a', 'c'): + if (value != 0) + f(kFractionsType, kDiagonalFractionsSelector); + break; + + case mkTag('z', 'e', 'r', 'o'): + boolspec(value, kTypographicExtrasType, + kSlashedZeroOnSelector, + kSlashedZeroOffSelector, + f); + break; + case mkTag('m', 'g', 'r', 'k'): + boolspec(value, kMathematicalExtrasType, + kMathematicalGreekOnSelector, + kMathematicalGreekOffSelector, + f); + break; + case mkTag('o', 'r', 'n', 'm'): + f(kOrnamentSetsType, (uint16_t) value); + break; + case mkTag('a', 'a', 'l', 't'): + f(kCharacterAlternativesType, (uint16_t) value); + break; + case mkTag('t', 'i', 't', 'l'): + // TODO is this correct, or should we provide an else case? + if (value != 0) + f(kStyleOptionsType, kTitlingCapsSelector); + break; + + // TODO will the following handle all cases properly, or are elses going to be needed? + case mkTag('t', 'r', 'a', 'd'): + if (value != 0) + f(kCharacterShapeType, kTraditionalCharactersSelector); + break; + case mkTag('s', 'm', 'p', 'l'): + if (value != 0) + f(kCharacterShapeType, kSimplifiedCharactersSelector); + break; + case mkTag('j', 'p', '7', '8'): + if (value != 0) + f(kCharacterShapeType, kJIS1978CharactersSelector); + break; + case mkTag('j', 'p', '8', '3'): + if (value != 0) + f(kCharacterShapeType, kJIS1983CharactersSelector); + break; + case mkTag('j', 'p', '9', '0'): + if (value != 0) + f(kCharacterShapeType, kJIS1990CharactersSelector); + break; + case mkTag('e', 'x', 'p', 't'): + if (value != 0) + f(kCharacterShapeType, kExpertCharactersSelector); + break; + case mkTag('j', 'p', '0', '4'): + if (value != 0) + f(kCharacterShapeType, kJIS2004CharactersSelector); + break; + case mkTag('h', 'o', 'j', 'o'): + if (value != 0) + f(kCharacterShapeType, kHojoCharactersSelector); + break; + case mkTag('n', 'l', 'c', 'k'): + if (value != 0) + f(kCharacterShapeType, kNLCCharactersSelector); + break; + case mkTag('t', 'n', 'a', 'm'): + if (value != 0) + f(kCharacterShapeType, kTraditionalNamesCharactersSelector); + break; + + case mkTag('o', 'n', 'u', 'm'): + // Core Text's internal AAT-to-OpenType mapping says to include this, so we include it too + // TODO is it always set? + case mkTag('l', 'n', 'u', 'm'): + // TODO is this correct, or should we provide an else case? + if (value != 0) + f(kNumberCaseType, kLowerCaseNumbersSelector); + break; + case mkTag('h', 'n', 'g', 'l'): + // TODO is this correct, or should we provide an else case? + if (value != 0) + f(kTransliterationType, kHanjaToHangulSelector); + break; + case mkTag('n', 'a', 'l', 't'): + f(kAnnotationType, (uint16_t) value); + break; + case mkTag('r', 'u', 'b', 'y'): + // include this for completeness + boolspec(value, kRubyKanaType, + kRubyKanaSelector, + kNoRubyKanaSelector, + f); + // this is the current one + boolspec(value, kRubyKanaType, + kRubyKanaOnSelector, + kRubyKanaOffSelector, + f); + break; + case mkTag('i', 't', 'a', 'l'): + // include this for completeness + boolspec(value, kItalicCJKRomanType, + kCJKItalicRomanSelector, + kNoCJKItalicRomanSelector, + f); + // this is the current one + boolspec(value, kItalicCJKRomanType, + kCJKItalicRomanOnSelector, + kCJKItalicRomanOffSelector, + f); + break; + case mkTag('c', 'a', 's', 'e'): + boolspec(value, kCaseSensitiveLayoutType, + kCaseSensitiveLayoutOnSelector, + kCaseSensitiveLayoutOffSelector, + f); + break; + case mkTag('c', 'p', 's', 'p'): + boolspec(value, kCaseSensitiveLayoutType, + kCaseSensitiveSpacingOnSelector, + kCaseSensitiveSpacingOffSelector, + f); + break; + case mkTag('h', 'k', 'n', 'a'): + boolspec(value, kAlternateKanaType, + kAlternateHorizKanaOnSelector, + kAlternateHorizKanaOffSelector, + f); + break; + case mkTag('v', 'k', 'n', 'a'): + boolspec(value, kAlternateKanaType, + kAlternateVertKanaOnSelector, + kAlternateVertKanaOffSelector, + f); + break; + case mkTag('s', 's', '0', '1'): + boolspec(value, kStylisticAlternativesType, + kStylisticAltOneOnSelector, + kStylisticAltOneOffSelector, + f); + break; + case mkTag('s', 's', '0', '2'): + boolspec(value, kStylisticAlternativesType, + kStylisticAltTwoOnSelector, + kStylisticAltTwoOffSelector, + f); + break; + case mkTag('s', 's', '0', '3'): + boolspec(value, kStylisticAlternativesType, + kStylisticAltThreeOnSelector, + kStylisticAltThreeOffSelector, + f); + break; + case mkTag('s', 's', '0', '4'): + boolspec(value, kStylisticAlternativesType, + kStylisticAltFourOnSelector, + kStylisticAltFourOffSelector, + f); + break; + case mkTag('s', 's', '0', '5'): + boolspec(value, kStylisticAlternativesType, + kStylisticAltFiveOnSelector, + kStylisticAltFiveOffSelector, + f); + break; + case mkTag('s', 's', '0', '6'): + boolspec(value, kStylisticAlternativesType, + kStylisticAltSixOnSelector, + kStylisticAltSixOffSelector, + f); + break; + case mkTag('s', 's', '0', '7'): + boolspec(value, kStylisticAlternativesType, + kStylisticAltSevenOnSelector, + kStylisticAltSevenOffSelector, + f); + break; + case mkTag('s', 's', '0', '8'): + boolspec(value, kStylisticAlternativesType, + kStylisticAltEightOnSelector, + kStylisticAltEightOffSelector, + f); + break; + case mkTag('s', 's', '0', '9'): + boolspec(value, kStylisticAlternativesType, + kStylisticAltNineOnSelector, + kStylisticAltNineOffSelector, + f); + break; + case mkTag('s', 's', '1', '0'): + boolspec(value, kStylisticAlternativesType, + kStylisticAltTenOnSelector, + kStylisticAltTenOffSelector, + f); + break; + case mkTag('s', 's', '1', '1'): + boolspec(value, kStylisticAlternativesType, + kStylisticAltElevenOnSelector, + kStylisticAltElevenOffSelector, + f); + break; + case mkTag('s', 's', '1', '2'): + boolspec(value, kStylisticAlternativesType, + kStylisticAltTwelveOnSelector, + kStylisticAltTwelveOffSelector, + f); + break; + case mkTag('s', 's', '1', '3'): + boolspec(value, kStylisticAlternativesType, + kStylisticAltThirteenOnSelector, + kStylisticAltThirteenOffSelector, + f); + break; + case mkTag('s', 's', '1', '4'): + boolspec(value, kStylisticAlternativesType, + kStylisticAltFourteenOnSelector, + kStylisticAltFourteenOffSelector, + f); + break; + case mkTag('s', 's', '1', '5'): + boolspec(value, kStylisticAlternativesType, + kStylisticAltFifteenOnSelector, + kStylisticAltFifteenOffSelector, + f); + break; + case mkTag('s', 's', '1', '6'): + boolspec(value, kStylisticAlternativesType, + kStylisticAltSixteenOnSelector, + kStylisticAltSixteenOffSelector, + f); + break; + case mkTag('s', 's', '1', '7'): + boolspec(value, kStylisticAlternativesType, + kStylisticAltSeventeenOnSelector, + kStylisticAltSeventeenOffSelector, + f); + break; + case mkTag('s', 's', '1', '8'): + boolspec(value, kStylisticAlternativesType, + kStylisticAltEighteenOnSelector, + kStylisticAltEighteenOffSelector, + f); + break; + case mkTag('s', 's', '1', '9'): + boolspec(value, kStylisticAlternativesType, + kStylisticAltNineteenOnSelector, + kStylisticAltNineteenOffSelector, + f); + break; + case mkTag('s', 's', '2', '0'): + boolspec(value, kStylisticAlternativesType, + kStylisticAltTwentyOnSelector, + kStylisticAltTwentyOffSelector, + f); + break; + case mkTag('c', 'a', 'l', 't'): + boolspec(value, kContextualAlternatesType, + kContextualAlternatesOnSelector, + kContextualAlternatesOffSelector, + f); + break; + case mkTag('s', 'w', 's', 'h'): + boolspec(value, kContextualAlternatesType, + kSwashAlternatesOnSelector, + kSwashAlternatesOffSelector, + f); + break; + case mkTag('c', 's', 'w', 'h'): + boolspec(value, kContextualAlternatesType, + kContextualSwashAlternatesOnSelector, + kContextualSwashAlternatesOffSelector, + f); + break; + + // TODO will the following handle all cases properly, or are elses going to be needed? + case mkTag('s', 'm', 'c', 'p'): + if (value != 0) { + // include this for compatibility (some fonts that come with OS X still use this!) + // TODO make it boolean? + f(kLetterCaseType, kSmallCapsSelector); + // this is the current one + f(kLowerCaseType, kLowerCaseSmallCapsSelector); + } + break; + case mkTag('p', 'c', 'a', 'p'): + if (value != 0) + f(kLowerCaseType, kLowerCasePetiteCapsSelector); + break; + + // TODO will the following handle all cases properly, or are elses going to be needed? + case mkTag('c', '2', 's', 'c'): + if (value != 0) + f(kUpperCaseType, kUpperCaseSmallCapsSelector); + break; + case mkTag('c', '2', 'p', 'c'): + if (value != 0) + f(kUpperCaseType, kUpperCasePetiteCapsSelector); + break; + } + // TODO handle this properly + // (it used to return 0 when this still returned the number of selectors produced but IDK what properly is anymore) +} diff --git a/darwin/alloc.m b/darwin/alloc.m index 5fd42f3b..e20f67f3 100644 --- a/darwin/alloc.m +++ b/darwin/alloc.m @@ -3,12 +3,12 @@ #import "uipriv_darwin.h" static NSMutableArray *allocations; -NSMutableArray *delegates; +NSMutableArray *uiprivDelegates; -void initAlloc(void) +void uiprivInitAlloc(void) { allocations = [NSMutableArray new]; - delegates = [NSMutableArray new]; + uiprivDelegates = [NSMutableArray new]; } #define UINT8(p) ((uint8_t *) (p)) @@ -20,32 +20,28 @@ void initAlloc(void) #define CCHAR(p) ((const char **) (p)) #define TYPE(p) CCHAR(UINT8(p) + sizeof (size_t)) -void uninitAlloc(void) +void uiprivUninitAlloc(void) { - NSUInteger i; + NSMutableString *str; + NSValue *v; - // delegates might have mapTables allocated - // TODO verify they are empty - for (i = 0; i < [delegates count]; i++) - [[delegates objectAtIndex:i] release]; - [delegates release]; + [uiprivDelegates release]; if ([allocations count] == 0) { [allocations release]; return; } - fprintf(stderr, "[libui] leaked allocations:\n"); - [allocations enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop) { - NSValue *v; + str = [NSMutableString new]; + for (v in allocations) { void *ptr; - v = (NSValue *) obj; ptr = [v pointerValue]; - fprintf(stderr, "[libui] %p %s\n", ptr, *TYPE(ptr)); - }]; - complain("either you left something around or there's a bug in libui"); + [str appendString:[NSString stringWithFormat:@"%p %s\n", ptr, *TYPE(ptr)]]; + } + uiprivUserBug("Some data was leaked; either you left a uiControl lying around or there's a bug in libui itself. Leaked data:\n%s", [str UTF8String]); + [str release]; } -void *uiAlloc(size_t size, const char *type) +void *uiprivAlloc(size_t size, const char *type) { void *out; @@ -61,21 +57,21 @@ void *uiAlloc(size_t size, const char *type) return DATA(out); } -void *uiRealloc(void *p, size_t new, const char *type) +void *uiprivRealloc(void *p, size_t new, const char *type) { void *out; size_t *s; if (p == NULL) - return uiAlloc(new, type); + return uiprivAlloc(new, type); p = BASE(p); out = realloc(p, EXTRA + new); if (out == NULL) { - fprintf(stderr, "memory exhausted in uiRealloc()\n"); + fprintf(stderr, "memory exhausted in uiprivRealloc()\n"); abort(); } s = SIZE(out); - if (new <= *s) + if (new > *s) memset(((uint8_t *) DATA(out)) + *s, 0, new - *s); *s = new; [allocations removeObject:[NSValue valueWithPointer:p]]; @@ -83,10 +79,10 @@ void *uiRealloc(void *p, size_t new, const char *type) return DATA(out); } -void uiFree(void *p) +void uiprivFree(void *p) { if (p == NULL) - complain("attempt to uiFree(NULL); there's a bug somewhere"); + uiprivImplBug("attempt to uiprivFree(NULL)"); p = BASE(p); free(p); [allocations removeObject:[NSValue valueWithPointer:p]]; diff --git a/darwin/area.m b/darwin/area.m index 72aee13c..a184bc4a 100644 --- a/darwin/area.m +++ b/darwin/area.m @@ -1,11 +1,14 @@ // 9 september 2015 #import "uipriv_darwin.h" -// TODO implement setEnabled: +// 10.8 fixups +#define NSEventModifierFlags NSUInteger @interface areaView : NSView { uiArea *libui_a; NSTrackingArea *libui_ta; + NSSize libui_ss; + BOOL libui_enabled; } - (id)initWithFrame:(NSRect)r area:(uiArea *)a; - (uiModifiers)parseModifiers:(NSEvent *)e; @@ -16,6 +19,9 @@ - (int)doKeyUp:(NSEvent *)e; - (int)doFlagsChanged:(NSEvent *)e; - (void)setupNewTrackingArea; +- (void)setScrollingSize:(NSSize)s; +- (BOOL)isEnabled; +- (void)setEnabled:(BOOL)e; @end struct uiArea { @@ -23,8 +29,10 @@ struct uiArea { NSView *view; // either sv or area depending on whether it is scrolling NSScrollView *sv; areaView *area; + uiprivScrollViewData *d; uiAreaHandler *ah; BOOL scrolling; + NSEvent *dragevent; }; @implementation areaView @@ -35,6 +43,8 @@ struct uiArea { if (self) { self->libui_a = a; [self setupNewTrackingArea]; + self->libui_ss = r.size; + self->libui_enabled = YES; } return self; } @@ -47,12 +57,11 @@ struct uiArea { c = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort]; // see draw.m under text for why we need the height - dp.Context = newContext(c, [self bounds].size.height); + dp.Context = uiprivDrawNewContext(c, [self bounds].size.height); dp.AreaWidth = 0; dp.AreaHeight = 0; if (!a->scrolling) { - // TODO frame or bounds? dp.AreaWidth = [self frame].size.width; dp.AreaHeight = [self frame].size.height; } @@ -65,7 +74,7 @@ struct uiArea { // no need to save or restore the graphics state to reset transformations; Cocoa creates a brand-new context each time (*(a->ah->Draw))(a->ah, a, &dp); - freeContext(dp.Context); + uiprivDrawFreeContext(dp.Context); } - (BOOL)isFlipped @@ -98,7 +107,6 @@ struct uiArea { - (void)setupNewTrackingArea { - // TODO NSTrackingAssumeInside? self->libui_ta = [[NSTrackingArea alloc] initWithRect:[self bounds] options:(NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | @@ -110,7 +118,6 @@ struct uiArea { [self addTrackingArea:self->libui_ta]; } -// TODO when do we call super here? - (void)updateTrackingAreas { [self removeTrackingArea:self->libui_ta]; @@ -124,7 +131,7 @@ struct uiArea { uiArea *a = self->libui_a; uiAreaMouseEvent me; NSPoint point; - uintmax_t buttonNumber; + int buttonNumber; NSUInteger pmb; unsigned int i, max; @@ -137,7 +144,6 @@ struct uiArea { me.AreaWidth = 0; me.AreaHeight = 0; if (!a->scrolling) { - // TODO frame or bounds? me.AreaWidth = [self frame].size.width; me.AreaHeight = [self frame].size.height; } @@ -182,11 +188,9 @@ struct uiArea { me.Held1To64 |= 2; if (buttonNumber != 3 && (pmb & 2) != 0) me.Held1To64 |= 4; - // buttons 4..64 + // buttons 4..32 + // https://developer.apple.com/library/mac/documentation/Carbon/Reference/QuartzEventServicesRef/index.html#//apple_ref/c/tdef/CGMouseButton says Quartz only supports up to 32 buttons max = 32; - // TODO are the upper 32 bits just mirrored? -// if (sizeof (NSUInteger) == 8) -// max = 64; for (i = 4; i <= max; i++) { uint64_t j; @@ -197,7 +201,12 @@ struct uiArea { me.Held1To64 |= j; } - (*(a->ah->MouseEvent))(a->ah, a, &me); + if (self->libui_enabled) { + // and allow dragging here + a->dragevent = e; + (*(a->ah->MouseEvent))(a->ah, a, &me); + a->dragevent = nil; + } } #define mouseEvent(name) \ @@ -220,14 +229,16 @@ mouseEvent(otherMouseUp) { uiArea *a = self->libui_a; - (*(a->ah->MouseCrossed))(a->ah, a, 0); + if (self->libui_enabled) + (*(a->ah->MouseCrossed))(a->ah, a, 0); } - (void)mouseExited:(NSEvent *)e { uiArea *a = self->libui_a; - (*(a->ah->MouseCrossed))(a->ah, a, 1); + if (self->libui_enabled) + (*(a->ah->MouseCrossed))(a->ah, a, 1); } // note: there is no equivalent to WM_CAPTURECHANGED on Mac OS X; there literally is no way to break a grab like that @@ -253,7 +264,7 @@ mouseEvent(otherMouseUp) ke.Up = up; - if (!fromKeycode([e keyCode], &ke)) + if (!uiprivFromKeycode([e keyCode], &ke)) return 0; return [self sendKeyEvent:&ke]; } @@ -278,7 +289,7 @@ mouseEvent(otherMouseUp) // Mac OS X sends this event on both key up and key down. // Fortunately -[e keyCode] IS valid here, so we can simply map from key code to Modifiers, get the value of [e modifierFlags], and check if the respective bit is set or not — that will give us the up/down state - if (!keycodeModifier([e keyCode], &whichmod)) + if (!uiprivKeycodeModifier([e keyCode], &whichmod)) return 0; ke.Modifier = whichmod; ke.Modifiers = [self parseModifiers:e]; @@ -298,15 +309,58 @@ mouseEvent(otherMouseUp) [self setNeedsDisplay:YES]; } +- (void)setScrollingSize:(NSSize)s +{ + self->libui_ss = s; + [self setFrameSize:s]; +} + +- (NSSize)intrinsicContentSize +{ + if (!self->libui_a->scrolling) + return [super intrinsicContentSize]; + return self->libui_ss; +} + +- (BOOL)becomeFirstResponder +{ + return [self isEnabled]; +} + +- (BOOL)isEnabled +{ + return self->libui_enabled; +} + +- (void)setEnabled:(BOOL)e +{ + self->libui_enabled = e; + if (!self->libui_enabled && [self window] != nil) + if ([[self window] firstResponder] == self) + [[self window] makeFirstResponder:nil]; +} + @end -uiDarwinControlAllDefaults(uiArea, view) +uiDarwinControlAllDefaultsExceptDestroy(uiArea, view) + +static void uiAreaDestroy(uiControl *c) +{ + uiArea *a = uiArea(c); + + if (a->scrolling) + uiprivScrollViewFreeData(a->sv, a->d); + [a->area release]; + if (a->scrolling) + [a->sv release]; + uiFreeControl(uiControl(a)); +} // called by subclasses of -[NSApplication sendEvent:] // by default, NSApplication eats some key events // this prevents that from happening with uiArea // see http://stackoverflow.com/questions/24099063/how-do-i-detect-keyup-in-my-nsview-with-the-command-key-held and http://lists.apple.com/archives/cocoa-dev/2003/Oct/msg00442.html -int sendAreaEvents(NSEvent *e) +int uiprivSendAreaEvents(NSEvent *e) { NSEventType type; id focused; @@ -332,11 +386,11 @@ int sendAreaEvents(NSEvent *e) return 0; } -void uiAreaSetSize(uiArea *a, intmax_t width, intmax_t height) +void uiAreaSetSize(uiArea *a, int width, int height) { if (!a->scrolling) - complain("attempt to call uiAreaSetSize() on a non-scrolling uiArea"); - [a->area setFrameSize:NSMakeSize(width, height)]; + uiprivUserBug("You cannot call uiAreaSetSize() on a non-scrolling uiArea. (area: %p)", a); + [a->area setScrollingSize:NSMakeSize(width, height)]; } void uiAreaQueueRedrawAll(uiArea *a) @@ -347,11 +401,35 @@ void uiAreaQueueRedrawAll(uiArea *a) void uiAreaScrollTo(uiArea *a, double x, double y, double width, double height) { if (!a->scrolling) - complain("attempt to call uiAreaScrollTo() on a non-scrolling uiArea"); + uiprivUserBug("You cannot call uiAreaScrollTo() on a non-scrolling uiArea. (area: %p)", a); [a->area scrollRectToVisible:NSMakeRect(x, y, width, height)]; // don't worry about the return value; it just says whether scrolling was needed } +void uiAreaBeginUserWindowMove(uiArea *a) +{ + uiprivNSWindow *w; + + w = (uiprivNSWindow *) [a->area window]; + if (w == nil) + return; // TODO + if (a->dragevent == nil) + return; // TODO + [w uiprivDoMove:a->dragevent]; +} + +void uiAreaBeginUserWindowResize(uiArea *a, uiWindowResizeEdge edge) +{ + uiprivNSWindow *w; + + w = (uiprivNSWindow *) [a->area window]; + if (w == nil) + return; // TODO + if (a->dragevent == nil) + return; // TODO + [w uiprivDoResize:a->dragevent on:edge]; +} + uiArea *uiNewArea(uiAreaHandler *ah) { uiArea *a; @@ -368,26 +446,29 @@ uiArea *uiNewArea(uiAreaHandler *ah) return a; } -uiArea *uiNewScrollingArea(uiAreaHandler *ah, intmax_t width, intmax_t height) +uiArea *uiNewScrollingArea(uiAreaHandler *ah, int width, int height) { uiArea *a; + uiprivScrollViewCreateParams p; uiDarwinNewControl(uiArea, a); a->ah = ah; a->scrolling = YES; - a->sv = [[NSScrollView alloc] initWithFrame:NSZeroRect]; - // TODO configure a->sv for real - [a->sv setHasHorizontalScroller:YES]; - [a->sv setHasVerticalScroller:YES]; - a->area = [[areaView alloc] initWithFrame:NSMakeRect(0, 0, width, height) area:a]; - a->view = a->sv; + memset(&p, 0, sizeof (uiprivScrollViewCreateParams)); + p.DocumentView = a->area; + p.BackgroundColor = [NSColor controlColor]; + p.DrawsBackground = 1; + p.Bordered = NO; + p.HScroll = YES; + p.VScroll = YES; + a->sv = uiprivMkScrollView(&p, &(a->d)); - [a->sv setDocumentView:a->area]; + a->view = a->sv; return a; } diff --git a/darwin/areaevents.m b/darwin/areaevents.m index d7ceaaad..27b5dd64 100644 --- a/darwin/areaevents.m +++ b/darwin/areaevents.m @@ -129,7 +129,7 @@ static const struct { { 0xFFFF, 0 }, }; -BOOL fromKeycode(unsigned short keycode, uiAreaKeyEvent *ke) +BOOL uiprivFromKeycode(unsigned short keycode, uiAreaKeyEvent *ke) { int i; @@ -146,7 +146,7 @@ BOOL fromKeycode(unsigned short keycode, uiAreaKeyEvent *ke) return NO; } -BOOL keycodeModifier(unsigned short keycode, uiModifiers *mod) +BOOL uiprivKeycodeModifier(unsigned short keycode, uiModifiers *mod) { int i; diff --git a/darwin/attrstr.h b/darwin/attrstr.h new file mode 100644 index 00000000..02a3418d --- /dev/null +++ b/darwin/attrstr.h @@ -0,0 +1,91 @@ +// 4 march 2018 +#import "../common/attrstr.h" + +// opentype.m +extern CFArrayRef uiprivOpenTypeFeaturesToCTFeatures(const uiOpenTypeFeatures *otf); + +// aat.m +typedef void (^uiprivAATBlock)(uint16_t type, uint16_t selector); +extern void uiprivOpenTypeToAAT(char a, char b, char c, char d, uint32_t value, uiprivAATBlock f); + +// fontmatch.m +@interface uiprivFontStyleData : NSObject { + CTFontRef font; + CTFontDescriptorRef desc; + CFDictionaryRef traits; + CTFontSymbolicTraits symbolic; + double weight; + double width; + BOOL didStyleName; + CFStringRef styleName; + BOOL didVariation; + CFDictionaryRef variation; + BOOL hasRegistrationScope; + CTFontManagerScope registrationScope; + BOOL didPostScriptName; + CFStringRef postScriptName; + CTFontFormat fontFormat; + BOOL didPreferredSubFamilyName; + CFStringRef preferredSubFamilyName; + BOOL didSubFamilyName; + CFStringRef subFamilyName; + BOOL didFullName; + CFStringRef fullName; + BOOL didPreferredFamilyName; + CFStringRef preferredFamilyName; + BOOL didFamilyName; + CFStringRef familyName; + BOOL didVariationAxes; + CFArrayRef variationAxes; +} +- (id)initWithFont:(CTFontRef)f; +- (id)initWithDescriptor:(CTFontDescriptorRef)d; +- (BOOL)prepare; +- (void)ensureFont; +- (CTFontSymbolicTraits)symbolicTraits; +- (double)weight; +- (double)width; +- (CFStringRef)styleName; +- (CFDictionaryRef)variation; +- (BOOL)hasRegistrationScope; +- (CTFontManagerScope)registrationScope; +- (CFStringRef)postScriptName; +- (CFDataRef)table:(CTFontTableTag)tag; +- (CTFontFormat)fontFormat; +- (CFStringRef)fontName:(CFStringRef)key; +- (CFStringRef)preferredSubFamilyName; +- (CFStringRef)subFamilyName; +- (CFStringRef)fullName; +- (CFStringRef)preferredFamilyName; +- (CFStringRef)familyName; +- (CFArrayRef)variationAxes; +@end +extern CTFontDescriptorRef uiprivFontDescriptorToCTFontDescriptor(uiFontDescriptor *fd); +extern CTFontDescriptorRef uiprivCTFontDescriptorAppendFeatures(CTFontDescriptorRef desc, const uiOpenTypeFeatures *otf); +extern void uiprivFontDescriptorFromCTFontDescriptor(CTFontDescriptorRef ctdesc, uiFontDescriptor *uidesc); + +// fonttraits.m +extern void uiprivProcessFontTraits(uiprivFontStyleData *d, uiFontDescriptor *out); + +// fontvariation.m +extern NSDictionary *uiprivMakeVariationAxisDict(CFArrayRef axes, CFDataRef avarTable); +extern void uiprivProcessFontVariation(uiprivFontStyleData *d, NSDictionary *axisDict, uiFontDescriptor *out); + +// attrstr.m +extern void uiprivInitUnderlineColors(void); +extern void uiprivUninitUnderlineColors(void); +extern CFAttributedStringRef uiprivAttributedStringToCFAttributedString(uiDrawTextLayoutParams *p, NSArray **backgroundParams); + +// drawtext.m +// TODO figure out where this type should *really* go in all the headers... +@interface uiprivDrawTextBackgroundParams : NSObject { + size_t start; + size_t end; + double r; + double g; + double b; + double a; +} +- (id)initWithStart:(size_t)s end:(size_t)e r:(double)red g:(double)green b:(double)blue a:(double)alpha; +- (void)draw:(CGContextRef)c layout:(uiDrawTextLayout *)layout at:(double)x y:(double)y utf8Mapping:(const size_t *)u16tou8; +@end diff --git a/darwin/attrstr.m b/darwin/attrstr.m new file mode 100644 index 00000000..36a180be --- /dev/null +++ b/darwin/attrstr.m @@ -0,0 +1,505 @@ +// 12 february 2017 +#import "uipriv_darwin.h" +#import "attrstr.h" + +// this is what AppKit does internally +// WebKit does this too; see https://github.com/adobe/webkit/blob/master/Source/WebCore/platform/graphics/mac/GraphicsContextMac.mm +static NSColor *spellingColor = nil; +static NSColor *grammarColor = nil; +static NSColor *auxiliaryColor = nil; + +static NSColor *tryColorNamed(NSString *name) +{ + NSImage *img; + + img = [NSImage imageNamed:name]; + if (img == nil) + return nil; + return [NSColor colorWithPatternImage:img]; +} + +void uiprivInitUnderlineColors(void) +{ + spellingColor = tryColorNamed(@"NSSpellingDot"); + if (spellingColor == nil) { + // WebKit says this is needed for "older systems"; not sure how old, but 10.11 AppKit doesn't look for this + spellingColor = tryColorNamed(@"SpellingDot"); + if (spellingColor == nil) + spellingColor = [NSColor redColor]; + } + [spellingColor retain]; // override autoreleasing + + grammarColor = tryColorNamed(@"NSGrammarDot"); + if (grammarColor == nil) { + // WebKit says this is needed for "older systems"; not sure how old, but 10.11 AppKit doesn't look for this + grammarColor = tryColorNamed(@"GrammarDot"); + if (grammarColor == nil) + grammarColor = [NSColor greenColor]; + } + [grammarColor retain]; // override autoreleasing + + auxiliaryColor = tryColorNamed(@"NSCorrectionDot"); + if (auxiliaryColor == nil) { + // WebKit says this is needed for "older systems"; not sure how old, but 10.11 AppKit doesn't look for this + auxiliaryColor = tryColorNamed(@"CorrectionDot"); + if (auxiliaryColor == nil) + auxiliaryColor = [NSColor blueColor]; + } + [auxiliaryColor retain]; // override autoreleasing +} + +void uiprivUninitUnderlineColors(void) +{ + [auxiliaryColor release]; + auxiliaryColor = nil; + [grammarColor release]; + grammarColor = nil; + [spellingColor release]; + spellingColor = nil; +} + +// TODO opentype features are lost when using uiFontDescriptor, so a handful of fonts in the font panel ("Titling" variants of some fonts and possibly others but those are the examples I know about) cannot be represented by uiFontDescriptor; what *can* we do about this since this info is NOT part of the font on other platforms? +// TODO see if we could use NSAttributedString? +// TODO consider renaming this struct and the fep variable(s) +// TODO restructure all this so the important details at the top are below with the combined font attributes type? +// TODO in fact I should just write something to explain everything in this file... +struct foreachParams { + CFMutableAttributedStringRef mas; + NSMutableArray *backgroundParams; +}; + +// unlike the other systems, Core Text rolls family, size, weight, italic, width, AND opentype features into the "font" attribute +// instead of incrementally adjusting CTFontRefs (which, judging from NSFontManager, seems finicky and UI-centric), we use a custom class to incrementally store attributes that go into a CTFontRef, and then convert everything to CTFonts en masse later +// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/AttributedStrings/Tasks/ChangingAttrStrings.html#//apple_ref/doc/uid/20000162-BBCBGCDG says we must have -hash and -isEqual: workign properly for this to work, so we must do that too, using a basic xor-based hash and leveraging Cocoa -hash implementations where useful and feasible (if not necessary) +// TODO structure and rewrite this part +// TODO re-find sources proving support of custom attributes +// TODO what if this is NULL? +static const CFStringRef combinedFontAttrName = CFSTR("libuiCombinedFontAttribute"); + +enum { + cFamily, + cSize, + cWeight, + cItalic, + cStretch, + cFeatures, + nc, +}; + +static const int toc[] = { + [uiAttributeTypeFamily] = cFamily, + [uiAttributeTypeSize] = cSize, + [uiAttributeTypeWeight] = cWeight, + [uiAttributeTypeItalic] = cItalic, + [uiAttributeTypeStretch] = cStretch, + [uiAttributeTypeFeatures] = cFeatures, +}; + +static uiForEach featuresHash(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value, void *data) +{ + NSUInteger *hash = (NSUInteger *) data; + uint32_t tag; + + tag = (((uint32_t) a) & 0xFF) << 24; + tag |= (((uint32_t) b) & 0xFF) << 16; + tag |= (((uint32_t) c) & 0xFF) << 8; + tag |= ((uint32_t) d) & 0xFF; + *hash ^= tag; + *hash ^= value; + return uiForEachContinue; +} + +@interface uiprivCombinedFontAttr : NSObject { + uiAttribute *attrs[nc]; + BOOL hasHash; + NSUInteger hash; +} +- (void)addAttribute:(uiAttribute *)attr; +- (CTFontRef)toCTFontWithDefaultFont:(uiFontDescriptor *)defaultFont; +@end + +@implementation uiprivCombinedFontAttr + +- (id)init +{ + self = [super init]; + if (self) { + memset(self->attrs, 0, nc * sizeof (uiAttribute *)); + self->hasHash = NO; + } + return self; +} + +- (void)dealloc +{ + int i; + + for (i = 0; i < nc; i++) + if (self->attrs[i] != NULL) { + uiprivAttributeRelease(self->attrs[i]); + self->attrs[i] = NULL; + } + [super dealloc]; +} + +- (id)copyWithZone:(NSZone *)zone +{ + uiprivCombinedFontAttr *ret; + int i; + + ret = [[uiprivCombinedFontAttr allocWithZone:zone] init]; + for (i = 0; i < nc; i++) + if (self->attrs[i] != NULL) + ret->attrs[i] = uiprivAttributeRetain(self->attrs[i]); + ret->hasHash = self->hasHash; + ret->hash = self->hash; + return ret; +} + +- (void)addAttribute:(uiAttribute *)attr +{ + int index; + + index = toc[uiAttributeGetType(attr)]; + if (self->attrs[index] != NULL) + uiprivAttributeRelease(self->attrs[index]); + self->attrs[index] = uiprivAttributeRetain(attr); + self->hasHash = NO; +} + +- (BOOL)isEqual:(id)bb +{ + uiprivCombinedFontAttr *b = (uiprivCombinedFontAttr *) bb; + int i; + + if (b == nil) + return NO; + for (i = 0; i < nc; i++) { + if (self->attrs[i] == NULL && b->attrs[i] == NULL) + continue; + if (self->attrs[i] == NULL || b->attrs[i] == NULL) + return NO; + if (!uiprivAttributeEqual(self->attrs[i], b->attrs[i])) + return NO; + } + return YES; +} + +- (NSUInteger)hash +{ + if (self->hasHash) + return self->hash; + @autoreleasepool { + NSString *family; + NSNumber *size; + + self->hash = 0; + if (self->attrs[cFamily] != NULL) { + family = [NSString stringWithUTF8String:uiAttributeFamily(self->attrs[cFamily])]; + // TODO make sure this aligns with case-insensitive compares when those are done in common/attribute.c + self->hash ^= [[family uppercaseString] hash]; + } + if (self->attrs[cSize] != NULL) { + size = [NSNumber numberWithDouble:uiAttributeSize(self->attrs[cSize])]; + self->hash ^= [size hash]; + } + if (self->attrs[cWeight] != NULL) + self->hash ^= (NSUInteger) uiAttributeWeight(self->attrs[cWeight]); + if (self->attrs[cItalic] != NULL) + self->hash ^= (NSUInteger) uiAttributeItalic(self->attrs[cItalic]); + if (self->attrs[cStretch] != NULL) + self->hash ^= (NSUInteger) uiAttributeStretch(self->attrs[cStretch]); + if (self->attrs[cFeatures] != NULL) + uiOpenTypeFeaturesForEach(uiAttributeFeatures(self->attrs[cFeatures]), featuresHash, &(self->hash)); + self->hasHash = YES; + } + return self->hash; +} + +- (CTFontRef)toCTFontWithDefaultFont:(uiFontDescriptor *)defaultFont +{ + uiFontDescriptor uidesc; + CTFontDescriptorRef desc; + CTFontRef font; + + uidesc = *defaultFont; + if (self->attrs[cFamily] != NULL) + // TODO const-correct uiFontDescriptor or change this function below + uidesc.Family = (char *) uiAttributeFamily(self->attrs[cFamily]); + if (self->attrs[cSize] != NULL) + uidesc.Size = uiAttributeSize(self->attrs[cSize]); + if (self->attrs[cWeight] != NULL) + uidesc.Weight = uiAttributeWeight(self->attrs[cWeight]); + if (self->attrs[cItalic] != NULL) + uidesc.Italic = uiAttributeItalic(self->attrs[cItalic]); + if (self->attrs[cStretch] != NULL) + uidesc.Stretch = uiAttributeStretch(self->attrs[cStretch]); + desc = uiprivFontDescriptorToCTFontDescriptor(&uidesc); + if (self->attrs[cFeatures] != NULL) + desc = uiprivCTFontDescriptorAppendFeatures(desc, uiAttributeFeatures(self->attrs[cFeatures])); + font = CTFontCreateWithFontDescriptor(desc, uidesc.Size, NULL); + CFRelease(desc); // TODO correct? + return font; +} + +@end + +static void addFontAttributeToRange(struct foreachParams *p, size_t start, size_t end, uiAttribute *attr) +{ + uiprivCombinedFontAttr *cfa; + CFRange range; + size_t diff; + + while (start < end) { + cfa = (uiprivCombinedFontAttr *) CFAttributedStringGetAttribute(p->mas, start, combinedFontAttrName, &range); + if (cfa == nil) + cfa = [uiprivCombinedFontAttr new]; + else + cfa = [cfa copy]; + [cfa addAttribute:attr]; + // clamp range within [start, end) + if (range.location < start) { + diff = start - range.location; + range.location = start; + range.length -= diff; + } + if ((range.location + range.length) > end) + range.length = end - range.location; + CFAttributedStringSetAttribute(p->mas, range, combinedFontAttrName, cfa); + [cfa release]; + start += range.length; + } +} + +static CGColorRef mkcolor(double r, double g, double b, double a) +{ + CGColorSpaceRef colorspace; + CGColorRef color; + CGFloat components[4]; + + // TODO we should probably just create this once and recycle it throughout program execution... + colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); + if (colorspace == NULL) { + // TODO + } + components[0] = r; + components[1] = g; + components[2] = b; + components[3] = a; + color = CGColorCreate(colorspace, components); + CFRelease(colorspace); + return color; +} + +static void addBackgroundAttribute(struct foreachParams *p, size_t start, size_t end, double r, double g, double b, double a) +{ + uiprivDrawTextBackgroundParams *dtb; + + // TODO make sure this works properly with line paragraph spacings (after figuring out what that means, of course) + if (uiprivFUTURE_kCTBackgroundColorAttributeName != NULL) { + CGColorRef color; + CFRange range; + + color = mkcolor(r, g, b, a); + range.location = start; + range.length = end - start; + CFAttributedStringSetAttribute(p->mas, range, *uiprivFUTURE_kCTBackgroundColorAttributeName, color); + CFRelease(color); + return; + } + + dtb = [[uiprivDrawTextBackgroundParams alloc] initWithStart:start end:end r:r g:g b:b a:a]; + [p->backgroundParams addObject:dtb]; + [dtb release]; +} + +static uiForEach processAttribute(const uiAttributedString *s, const uiAttribute *attr, size_t start, size_t end, void *data) +{ + struct foreachParams *p = (struct foreachParams *) data; + CFRange range; + CGColorRef color; + int32_t us; + CFNumberRef num; + double r, g, b, a; + uiUnderlineColor colorType; + + start = uiprivAttributedStringUTF8ToUTF16(s, start); + end = uiprivAttributedStringUTF8ToUTF16(s, end); + range.location = start; + range.length = end - start; + switch (uiAttributeGetType(attr)) { + case uiAttributeTypeFamily: + case uiAttributeTypeSize: + case uiAttributeTypeWeight: + case uiAttributeTypeItalic: + case uiAttributeTypeStretch: + case uiAttributeTypeFeatures: + addFontAttributeToRange(p, start, end, attr); + break; + case uiAttributeTypeColor: + uiAttributeColor(attr, &r, &g, &b, &a); + color = mkcolor(r, g, b, a); + CFAttributedStringSetAttribute(p->mas, range, kCTForegroundColorAttributeName, color); + CFRelease(color); + break; + case uiAttributeTypeBackground: + uiAttributeColor(attr, &r, &g, &b, &a); + addBackgroundAttribute(p, start, end, r, g, b, a); + break; + // TODO turn into a class, like we did with the font attributes, or even integrate *into* the font attributes + case uiAttributeTypeUnderline: + switch (uiAttributeUnderline(attr)) { + case uiUnderlineNone: + us = kCTUnderlineStyleNone; + break; + case uiUnderlineSingle: + us = kCTUnderlineStyleSingle; + break; + case uiUnderlineDouble: + us = kCTUnderlineStyleDouble; + break; + case uiUnderlineSuggestion: + // TODO incorrect if a solid color + us = kCTUnderlineStyleThick; + break; + } + num = CFNumberCreate(NULL, kCFNumberSInt32Type, &us); + CFAttributedStringSetAttribute(p->mas, range, kCTUnderlineStyleAttributeName, num); + CFRelease(num); + break; + case uiAttributeTypeUnderlineColor: + uiAttributeUnderlineColor(attr, &colorType, &r, &g, &b, &a); + switch (colorType) { + case uiUnderlineColorCustom: + color = mkcolor(r, g, b, a); + break; + case uiUnderlineColorSpelling: + color = [spellingColor CGColor]; + break; + case uiUnderlineColorGrammar: + color = [grammarColor CGColor]; + break; + case uiUnderlineColorAuxiliary: + color = [auxiliaryColor CGColor]; + break; + } + CFAttributedStringSetAttribute(p->mas, range, kCTUnderlineColorAttributeName, color); + if (colorType == uiUnderlineColorCustom) + CFRelease(color); + break; + } + return uiForEachContinue; +} + +static void applyFontAttributes(CFMutableAttributedStringRef mas, uiFontDescriptor *defaultFont) +{ + uiprivCombinedFontAttr *cfa; + CTFontRef font; + CFRange range; + CFIndex n; + + n = CFAttributedStringGetLength(mas); + + // first apply the default font to the entire string + // TODO is this necessary given the #if 0'd code in uiprivAttributedStringToCFAttributedString()? + cfa = [uiprivCombinedFontAttr new]; + font = [cfa toCTFontWithDefaultFont:defaultFont]; + [cfa release]; + range.location = 0; + range.length = n; + CFAttributedStringSetAttribute(mas, range, kCTFontAttributeName, font); + CFRelease(font); + + // now go through, replacing every uiprivCombinedFontAttr with the proper CTFontRef + // we are best off treating series of identical fonts as single ranges ourselves for parity across platforms, even if OS X does something similar itself + range.location = 0; + while (range.location < n) { + // TODO consider seeing if CFAttributedStringGetAttributeAndLongestEffectiveRange() can make things faster by reducing the number of potential iterations, either here or above + cfa = (uiprivCombinedFontAttr *) CFAttributedStringGetAttribute(mas, range.location, combinedFontAttrName, &range); + if (cfa != nil) { + font = [cfa toCTFontWithDefaultFont:defaultFont]; + CFAttributedStringSetAttribute(mas, range, kCTFontAttributeName, font); + CFRelease(font); + } + range.location += range.length; + } + + // and finally, get rid of all the uiprivCombinedFontAttrs as we won't need them anymore + range.location = 0; + range.length = 0; + CFAttributedStringRemoveAttribute(mas, range, combinedFontAttrName); +} + +static const CTTextAlignment ctaligns[] = { + [uiDrawTextAlignLeft] = kCTTextAlignmentLeft, + [uiDrawTextAlignCenter] = kCTTextAlignmentCenter, + [uiDrawTextAlignRight] = kCTTextAlignmentRight, +}; + +static CTParagraphStyleRef mkParagraphStyle(uiDrawTextLayoutParams *p) +{ + CTParagraphStyleRef ps; + CTParagraphStyleSetting settings[16]; + size_t nSettings = 0; + + settings[nSettings].spec = kCTParagraphStyleSpecifierAlignment; + settings[nSettings].valueSize = sizeof (CTTextAlignment); + settings[nSettings].value = ctaligns + p->Align; + nSettings++; + + ps = CTParagraphStyleCreate(settings, nSettings); + if (ps == NULL) { + // TODO + } + return ps; +} + +// TODO either rename this (on all platforms) to uiprivDrawTextLayoutParams... or rename this file or both or split the struct or something else... +CFAttributedStringRef uiprivAttributedStringToCFAttributedString(uiDrawTextLayoutParams *p, NSArray **backgroundParams) +{ + CFStringRef cfstr; + CFMutableDictionaryRef defaultAttrs; + CTParagraphStyleRef ps; + CFAttributedStringRef base; + CFMutableAttributedStringRef mas; + struct foreachParams fep; + + cfstr = CFStringCreateWithCharacters(NULL, uiprivAttributedStringUTF16String(p->String), uiprivAttributedStringUTF16Len(p->String)); + if (cfstr == NULL) { + // TODO + } + defaultAttrs = CFDictionaryCreateMutable(NULL, 0, + &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + if (defaultAttrs == NULL) { + // TODO + } +#if 0 /* TODO */ + ffp.desc = *(p->DefaultFont); + defaultCTFont = fontdescToCTFont(&ffp); + CFDictionaryAddValue(defaultAttrs, kCTFontAttributeName, defaultCTFont); + CFRelease(defaultCTFont); +#endif + ps = mkParagraphStyle(p); + CFDictionaryAddValue(defaultAttrs, kCTParagraphStyleAttributeName, ps); + CFRelease(ps); + + base = CFAttributedStringCreate(NULL, cfstr, defaultAttrs); + if (base == NULL) { + // TODO + } + CFRelease(cfstr); + CFRelease(defaultAttrs); + mas = CFAttributedStringCreateMutableCopy(NULL, 0, base); + CFRelease(base); + + CFAttributedStringBeginEditing(mas); + fep.mas = mas; + fep.backgroundParams = [NSMutableArray new]; + uiAttributedStringForEachAttribute(p->String, processAttribute, &fep); + applyFontAttributes(mas, p->DefaultFont); + CFAttributedStringEndEditing(mas); + + *backgroundParams = fep.backgroundParams; + return mas; +} diff --git a/darwin/autolayout.m b/darwin/autolayout.m index 1b1203b7..6bc5ce84 100644 --- a/darwin/autolayout.m +++ b/darwin/autolayout.m @@ -1,7 +1,7 @@ // 15 august 2015 #import "uipriv_darwin.h" -NSLayoutConstraint *mkConstraint(id view1, NSLayoutAttribute attr1, NSLayoutRelation relation, id view2, NSLayoutAttribute attr2, CGFloat multiplier, CGFloat c, NSString *desc) +NSLayoutConstraint *uiprivMkConstraint(id view1, NSLayoutAttribute attr1, NSLayoutRelation relation, id view2, NSLayoutAttribute attr2, CGFloat multiplier, CGFloat c, NSString *desc) { NSLayoutConstraint *constraint; @@ -12,16 +12,24 @@ NSLayoutConstraint *mkConstraint(id view1, NSLayoutAttribute attr1, NSLayoutRela attribute:attr2 multiplier:multiplier constant:c]; - // apparently only added in 10.9 - if ([constraint respondsToSelector:@selector(setIdentifier:)]) - [((id) constraint) setIdentifier:desc]; + uiprivFUTURE_NSLayoutConstraint_setIdentifier(constraint, desc); return constraint; } +CGFloat uiDarwinMarginAmount(void *reserved) +{ + return 20.0; +} + +CGFloat uiDarwinPaddingAmount(void *reserved) +{ + return 8.0; +} + // this is needed for NSSplitView to work properly; see http://stackoverflow.com/questions/34574478/how-can-i-set-the-position-of-a-nssplitview-nowadays-setpositionofdivideratind (stal in irc.freenode.net/#macdev came up with the exact combination) // turns out it also works on NSTabView and NSBox too, possibly others! // and for bonus points, it even seems to fix unsatisfiable-constraint-autoresizing-mask issues with NSTabView and NSBox too!!! this is nuts -void jiggleViewLayout(NSView *view) +void uiprivJiggleViewLayout(NSView *view) { [view setNeedsLayout:YES]; [view layoutSubtreeIfNeeded]; @@ -31,17 +39,16 @@ static CGFloat margins(int margined) { if (!margined) return 0.0; - return 20.0; // TODO named constant + return uiDarwinMarginAmount(NULL); } -void singleChildConstraintsEstablish(struct singleChildConstraints *c, NSView *contentView, NSView *childView, BOOL hugsTrailing, BOOL hugsBottom, int margined, NSString *desc) +void uiprivSingleChildConstraintsEstablish(uiprivSingleChildConstraints *c, NSView *contentView, NSView *childView, BOOL hugsTrailing, BOOL hugsBottom, int margined, NSString *desc) { CGFloat margin; - NSLayoutRelation relation; margin = margins(margined); - c->leadingConstraint = mkConstraint(contentView, NSLayoutAttributeLeading, + c->leadingConstraint = uiprivMkConstraint(contentView, NSLayoutAttributeLeading, NSLayoutRelationEqual, childView, NSLayoutAttributeLeading, 1, -margin, @@ -49,7 +56,7 @@ void singleChildConstraintsEstablish(struct singleChildConstraints *c, NSView *c [contentView addConstraint:c->leadingConstraint]; [c->leadingConstraint retain]; - c->topConstraint = mkConstraint(contentView, NSLayoutAttributeTop, + c->topConstraint = uiprivMkConstraint(contentView, NSLayoutAttributeTop, NSLayoutRelationEqual, childView, NSLayoutAttributeTop, 1, -margin, @@ -57,7 +64,7 @@ void singleChildConstraintsEstablish(struct singleChildConstraints *c, NSView *c [contentView addConstraint:c->topConstraint]; [c->topConstraint retain]; - c->trailingConstraintGreater = mkConstraint(contentView, NSLayoutAttributeTrailing, + c->trailingConstraintGreater = uiprivMkConstraint(contentView, NSLayoutAttributeTrailing, NSLayoutRelationGreaterThanOrEqual, childView, NSLayoutAttributeTrailing, 1, margin, @@ -67,7 +74,7 @@ void singleChildConstraintsEstablish(struct singleChildConstraints *c, NSView *c [contentView addConstraint:c->trailingConstraintGreater]; [c->trailingConstraintGreater retain]; - c->trailingConstraintEqual = mkConstraint(contentView, NSLayoutAttributeTrailing, + c->trailingConstraintEqual = uiprivMkConstraint(contentView, NSLayoutAttributeTrailing, NSLayoutRelationEqual, childView, NSLayoutAttributeTrailing, 1, margin, @@ -77,7 +84,7 @@ void singleChildConstraintsEstablish(struct singleChildConstraints *c, NSView *c [contentView addConstraint:c->trailingConstraintEqual]; [c->trailingConstraintEqual retain]; - c->bottomConstraintGreater = mkConstraint(contentView, NSLayoutAttributeBottom, + c->bottomConstraintGreater = uiprivMkConstraint(contentView, NSLayoutAttributeBottom, NSLayoutRelationGreaterThanOrEqual, childView, NSLayoutAttributeBottom, 1, margin, @@ -87,7 +94,7 @@ void singleChildConstraintsEstablish(struct singleChildConstraints *c, NSView *c [contentView addConstraint:c->bottomConstraintGreater]; [c->bottomConstraintGreater retain]; - c->bottomConstraintEqual = mkConstraint(contentView, NSLayoutAttributeBottom, + c->bottomConstraintEqual = uiprivMkConstraint(contentView, NSLayoutAttributeBottom, NSLayoutRelationEqual, childView, NSLayoutAttributeBottom, 1, margin, @@ -98,7 +105,7 @@ void singleChildConstraintsEstablish(struct singleChildConstraints *c, NSView *c [c->bottomConstraintEqual retain]; } -void singleChildConstraintsRemove(struct singleChildConstraints *c, NSView *cv) +void uiprivSingleChildConstraintsRemove(uiprivSingleChildConstraints *c, NSView *cv) { if (c->leadingConstraint != nil) { [cv removeConstraint:c->leadingConstraint]; @@ -132,7 +139,7 @@ void singleChildConstraintsRemove(struct singleChildConstraints *c, NSView *cv) } } -void singleChildConstraintsSetMargined(struct singleChildConstraints *c, int margined) +void uiprivSingleChildConstraintsSetMargined(uiprivSingleChildConstraints *c, int margined) { CGFloat margin; @@ -150,82 +157,3 @@ void singleChildConstraintsSetMargined(struct singleChildConstraints *c, int mar if (c->bottomConstraintEqual != nil) [c->bottomConstraintEqual setConstant:margin]; } - -// from http://blog.bjhomer.com/2014/08/nsscrollview-and-autolayout.html because (as pointed out there) Apple's official guide is really only for iOS -// TODO this doesn't quite work with NSTextView; it *mostly* works -void scrollViewConstraintsEstablish(struct scrollViewConstraints *c, NSScrollView *sv, NSString *desc) -{ - NSView *cv, *dv; - - scrollViewConstraintsRemove(c, sv); - cv = [sv contentView]; - dv = [sv documentView]; - - c->documentLeading = mkConstraint(dv, NSLayoutAttributeLeading, - NSLayoutRelationEqual, - cv, NSLayoutAttributeLeading, - 1, 0, - [desc stringByAppendingString:@"document leading constraint"]); - [sv addConstraint:c->documentLeading]; - [c->documentLeading retain]; - - c->documentTop = mkConstraint(dv, NSLayoutAttributeTop, - NSLayoutRelationEqual, - cv, NSLayoutAttributeTop, - 1, 0, - [desc stringByAppendingString:@"document top constraint"]); - [sv addConstraint:c->documentTop]; - [c->documentTop retain]; - - c->documentTrailing = mkConstraint(dv, NSLayoutAttributeTrailing, - NSLayoutRelationEqual, - cv, NSLayoutAttributeTrailing, - 1, 0, - [desc stringByAppendingString:@"document trailing constraint"]); - [sv addConstraint:c->documentTrailing]; - [c->documentTrailing retain]; - -#if 0 - c->documentBottom = mkConstraint(dv, NSLayoutAttributeBottom, - NSLayoutRelationEqual, - sv, NSLayoutAttributeBottom, - 1, 0, - [desc stringByAppendingString:@"document bottom constraint"]); - [sv addConstraint:c->documentBottom]; - [c->documentBottom retain]; -#endif -} - -void scrollViewConstraintsRemove(struct scrollViewConstraints *c, NSScrollView *sv) -{ - if (c->documentLeading != nil) { - [sv removeConstraint:c->documentLeading]; - [c->documentLeading release]; - c->documentLeading = nil; - } - if (c->documentTop != nil) { - [sv removeConstraint:c->documentTop]; - [c->documentTop release]; - c->documentTop = nil; - } - if (c->documentTrailing != nil) { - [sv removeConstraint:c->documentTrailing]; - [c->documentTrailing release]; - c->documentTrailing = nil; - } - if (c->documentBottom != nil) { - [sv removeConstraint:c->documentBottom]; - [c->documentBottom release]; - c->documentBottom = nil; - } - if (c->documentWidth != nil) { - [sv removeConstraint:c->documentWidth]; - [c->documentWidth release]; - c->documentWidth = nil; - } - if (c->documentHeight != nil) { - [sv removeConstraint:c->documentHeight]; - [c->documentHeight release]; - c->documentHeight = nil; - } -} diff --git a/darwin/box.m b/darwin/box.m index 0c05d070..72b5a71d 100644 --- a/darwin/box.m +++ b/darwin/box.m @@ -1,8 +1,7 @@ // 15 august 2015 #import "uipriv_darwin.h" -// TODOs to confirm -// - 10.8: if we switch to page 4, then switch back to page 1, check Spaced, and go back to page 4, some controls (progress bar, popup button) are clipped on the sides +// TODO hiding all stretchy controls still hugs trailing edge @interface boxChild : NSObject @property uiControl *c; @@ -17,7 +16,6 @@ NSMutableArray *children; BOOL vertical; int padded; - uintmax_t nStretchy; NSLayoutConstraint *first; NSMutableArray *inBetweens; @@ -39,11 +37,12 @@ - (CGFloat)paddingAmount; - (void)establishOurConstraints; - (void)append:(uiControl *)c stretchy:(int)stretchy; -- (void)delete:(uintmax_t)n; +- (void)delete:(int)n; - (int)isPadded; - (void)setPadded:(int)p; - (BOOL)hugsTrailing; - (BOOL)hugsBottom; +- (int)nStretchy; @end struct uiBox { @@ -71,7 +70,6 @@ struct uiBox { self->vertical = vert; self->padded = 0; self->children = [NSMutableArray new]; - self->nStretchy = 0; self->inBetweens = [NSMutableArray new]; self->otherConstraints = [NSMutableArray new]; @@ -147,10 +145,9 @@ struct uiBox { { if (!self->padded) return 0.0; - return 8.0; // TODO named constant + return uiDarwinPaddingAmount(NULL); } -// TODO something about spinbox hugging - (void)establishOurConstraints { boxChild *bc; @@ -167,8 +164,10 @@ struct uiBox { // first arrange in the primary direction prev = nil; for (bc in self->children) { + if (!uiControlVisible(bc.c)) + continue; if (prev == nil) { // first view - self->first = mkConstraint(self, self->primaryStart, + self->first = uiprivMkConstraint(self, self->primaryStart, NSLayoutRelationEqual, [bc view], self->primaryStart, 1, 0, @@ -179,7 +178,7 @@ struct uiBox { continue; } // not the first; link it - c = mkConstraint(prev, self->primaryEnd, + c = uiprivMkConstraint(prev, self->primaryEnd, NSLayoutRelationEqual, [bc view], self->primaryStart, 1, -padding, @@ -188,7 +187,9 @@ struct uiBox { [self->inBetweens addObject:c]; prev = [bc view]; } - self->last = mkConstraint(prev, self->primaryEnd, + if (prev == nil) // no control visible; act as if no controls + return; + self->last = uiprivMkConstraint(prev, self->primaryEnd, NSLayoutRelationEqual, self, self->primaryEnd, 1, 0, @@ -201,14 +202,16 @@ struct uiBox { if (!self->vertical) hugsSecondary = uiDarwinControlHugsBottom; for (bc in self->children) { - c = mkConstraint(self, self->secondaryStart, + if (!uiControlVisible(bc.c)) + continue; + c = uiprivMkConstraint(self, self->secondaryStart, NSLayoutRelationEqual, [bc view], self->secondaryStart, 1, 0, @"uiBox secondary start constraint"); [self addConstraint:c]; [self->otherConstraints addObject:c]; - c = mkConstraint([bc view], self->secondaryEnd, + c = uiprivMkConstraint([bc view], self->secondaryEnd, NSLayoutRelationLessThanOrEqual, self, self->secondaryEnd, 1, 0, @@ -217,7 +220,7 @@ struct uiBox { [c setPriority:NSLayoutPriorityDefaultLow]; [self addConstraint:c]; [self->otherConstraints addObject:c]; - c = mkConstraint([bc view], self->secondaryEnd, + c = uiprivMkConstraint([bc view], self->secondaryEnd, NSLayoutRelationEqual, self, self->secondaryEnd, 1, 0, @@ -229,17 +232,19 @@ struct uiBox { } // and make all stretchy controls the same size - if (self->nStretchy == 0) + if ([self nStretchy] == 0) return; prev = nil; // first stretchy view for (bc in self->children) { + if (!uiControlVisible(bc.c)) + continue; if (!bc.stretchy) continue; if (prev == nil) { prev = [bc view]; continue; } - c = mkConstraint(prev, self->primarySize, + c = uiprivMkConstraint(prev, self->primarySize, NSLayoutRelationEqual, [bc view], self->primarySize, 1, 0, @@ -253,7 +258,7 @@ struct uiBox { { boxChild *bc; NSLayoutPriority priority; - uintmax_t oldnStretchy; + int oldnStretchy; bc = [boxChild new]; bc.c = c; @@ -270,31 +275,28 @@ struct uiBox { if (bc.stretchy) priority = NSLayoutPriorityDefaultLow; else - // TODO will default high work? + // LONGTERM will default high work? priority = NSLayoutPriorityRequired; uiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), priority, self->primaryOrientation); // make sure controls don't hug their secondary direction so they fill the width of the view uiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), NSLayoutPriorityDefaultLow, self->secondaryOrientation); + oldnStretchy = [self nStretchy]; [self->children addObject:bc]; [self establishOurConstraints]; - if (bc.stretchy) { - oldnStretchy = self->nStretchy; - self->nStretchy++; + if (bc.stretchy) if (oldnStretchy == 0) uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->b)); - } [bc release]; // we don't need the initial reference now } -- (void)delete:(uintmax_t)n +- (void)delete:(int)n { boxChild *bc; int stretchy; - // TODO separate into a method? bc = (boxChild *) [self->children objectAtIndex:n]; stretchy = bc.stretchy; @@ -307,11 +309,9 @@ struct uiBox { [self->children removeObjectAtIndex:n]; [self establishOurConstraints]; - if (stretchy) { - self->nStretchy--; - if (self->nStretchy == 0) + if (stretchy) + if ([self nStretchy] == 0) uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->b)); - } } - (int)isPadded @@ -328,21 +328,35 @@ struct uiBox { padding = [self paddingAmount]; for (c in self->inBetweens) [c setConstant:-padding]; - // TODO call anything? } - (BOOL)hugsTrailing { if (self->vertical) // always hug if vertical return YES; - return self->nStretchy != 0; + return [self nStretchy] != 0; } - (BOOL)hugsBottom { if (!self->vertical) // always hug if horizontal return YES; - return self->nStretchy != 0; + return [self nStretchy] != 0; +} + +- (int)nStretchy +{ + boxChild *bc; + int n; + + n = 0; + for (bc in self->children) { + if (!uiControlVisible(bc.c)) + continue; + if (bc.stretchy) + n++; + } + return n; } @end @@ -402,12 +416,23 @@ static void uiBoxChildEdgeHuggingChanged(uiDarwinControl *c) uiDarwinControlDefaultHuggingPriority(uiBox, view) uiDarwinControlDefaultSetHuggingPriority(uiBox, view) +static void uiBoxChildVisibilityChanged(uiDarwinControl *c) +{ + uiBox *b = uiBox(c); + + [b->view establishOurConstraints]; +} + void uiBoxAppend(uiBox *b, uiControl *c, int stretchy) { + // LONGTERM on other platforms + // or at leat allow this and implicitly turn it into a spacer + if (c == NULL) + uiprivUserBug("You cannot add NULL to a uiBox."); [b->view append:c stretchy:stretchy]; } -void uiBoxDelete(uiBox *b, uintmax_t n) +void uiBoxDelete(uiBox *b, int n) { [b->view delete:n]; } diff --git a/darwin/button.m b/darwin/button.m index 01ba55f8..c3a6e075 100644 --- a/darwin/button.m +++ b/darwin/button.m @@ -9,7 +9,7 @@ struct uiButton { }; @interface buttonDelegateClass : NSObject { - struct mapTable *buttons; + uiprivMap *buttons; } - (IBAction)onClicked:(id)sender; - (void)registerButton:(uiButton *)b; @@ -22,13 +22,13 @@ struct uiButton { { self = [super init]; if (self) - self->buttons = newMap(); + self->buttons = uiprivNewMap(); return self; } - (void)dealloc { - mapDestroy(self->buttons); + uiprivMapDestroy(self->buttons); [super dealloc]; } @@ -36,13 +36,13 @@ struct uiButton { { uiButton *b; - b = (uiButton *) mapGet(self->buttons, sender); + b = (uiButton *) uiprivMapGet(self->buttons, sender); (*(b->onClicked))(b, b->onClickedData); } - (void)registerButton:(uiButton *)b { - mapSet(self->buttons, b->button, b); + uiprivMapSet(self->buttons, b->button, b); [b->button setTarget:self]; [b->button setAction:@selector(onClicked:)]; } @@ -50,7 +50,7 @@ struct uiButton { - (void)unregisterButton:(uiButton *)b { [b->button setTarget:nil]; - mapDelete(self->buttons, b->button); + uiprivMapDelete(self->buttons, b->button); } @end @@ -75,9 +75,7 @@ char *uiButtonText(uiButton *b) void uiButtonSetText(uiButton *b, const char *text) { - [b->button setTitle:toNSString(text)]; - // this may result in the size of the button changing - uiDarwinControlTriggerRelayout(uiDarwinControl(b)); + [b->button setTitle:uiprivToNSString(text)]; } void uiButtonOnClicked(uiButton *b, void (*f)(uiButton *, void *), void *data) @@ -98,15 +96,15 @@ uiButton *uiNewButton(const char *text) uiDarwinNewControl(uiButton, b); b->button = [[NSButton alloc] initWithFrame:NSZeroRect]; - [b->button setTitle:toNSString(text)]; + [b->button setTitle:uiprivToNSString(text)]; [b->button setButtonType:NSMomentaryPushInButton]; [b->button setBordered:YES]; [b->button setBezelStyle:NSRoundedBezelStyle]; uiDarwinSetControlFont(b->button, NSRegularControlSize); if (buttonDelegate == nil) { - buttonDelegate = [buttonDelegateClass new]; - [delegates addObject:buttonDelegate]; + buttonDelegate = [[buttonDelegateClass new] autorelease]; + [uiprivDelegates addObject:buttonDelegate]; } [buttonDelegate registerButton:b]; uiButtonOnClicked(b, defaultOnClicked, NULL); diff --git a/darwin/checkbox.m b/darwin/checkbox.m index 73aab165..d5a3d6e0 100644 --- a/darwin/checkbox.m +++ b/darwin/checkbox.m @@ -1,8 +1,6 @@ // 14 august 2015 #import "uipriv_darwin.h" -// TODO the intrinsic height of this seems to be wacky - struct uiCheckbox { uiDarwinControl c; NSButton *button; @@ -11,7 +9,7 @@ struct uiCheckbox { }; @interface checkboxDelegateClass : NSObject { - struct mapTable *buttons; + uiprivMap *buttons; } - (IBAction)onToggled:(id)sender; - (void)registerCheckbox:(uiCheckbox *)c; @@ -24,13 +22,13 @@ struct uiCheckbox { { self = [super init]; if (self) - self->buttons = newMap(); + self->buttons = uiprivNewMap(); return self; } - (void)dealloc { - mapDestroy(self->buttons); + uiprivMapDestroy(self->buttons); [super dealloc]; } @@ -38,13 +36,13 @@ struct uiCheckbox { { uiCheckbox *c; - c = (uiCheckbox *) mapGet(self->buttons, sender); + c = (uiCheckbox *) uiprivMapGet(self->buttons, sender); (*(c->onToggled))(c, c->onToggledData); } - (void)registerCheckbox:(uiCheckbox *)c { - mapSet(self->buttons, c->button, c); + uiprivMapSet(self->buttons, c->button, c); [c->button setTarget:self]; [c->button setAction:@selector(onToggled:)]; } @@ -52,7 +50,7 @@ struct uiCheckbox { - (void)unregisterCheckbox:(uiCheckbox *)c { [c->button setTarget:nil]; - mapDelete(self->buttons, c->button); + uiprivMapDelete(self->buttons, c->button); } @end @@ -77,11 +75,7 @@ char *uiCheckboxText(uiCheckbox *c) void uiCheckboxSetText(uiCheckbox *c, const char *text) { - [c->button setTitle:toNSString(text)]; - // this may result in the size of the checkbox changing - // TODO something somewhere is causing this to corrupt some memory so that, for instance, page7b's mouseExited: never triggers on 10.11; figure out what - // TODO is this related to map-related crashes? - uiDarwinControlTriggerRelayout(uiDarwinControl(c)); + [c->button setTitle:uiprivToNSString(text)]; } void uiCheckboxOnToggled(uiCheckbox *c, void (*f)(uiCheckbox *, void *), void *data) @@ -117,14 +111,16 @@ uiCheckbox *uiNewCheckbox(const char *text) uiDarwinNewControl(uiCheckbox, c); c->button = [[NSButton alloc] initWithFrame:NSZeroRect]; - [c->button setTitle:toNSString(text)]; + [c->button setTitle:uiprivToNSString(text)]; [c->button setButtonType:NSSwitchButton]; + // doesn't seem to have an associated bezel style [c->button setBordered:NO]; + [c->button setTransparent:NO]; uiDarwinSetControlFont(c->button, NSRegularControlSize); if (checkboxDelegate == nil) { - checkboxDelegate = [checkboxDelegateClass new]; - [delegates addObject:checkboxDelegate]; + checkboxDelegate = [[checkboxDelegateClass new] autorelease]; + [uiprivDelegates addObject:checkboxDelegate]; } [checkboxDelegate registerCheckbox:c]; uiCheckboxOnToggled(c, defaultOnToggled, NULL); diff --git a/darwin/colorbutton.m b/darwin/colorbutton.m new file mode 100644 index 00000000..f2bee775 --- /dev/null +++ b/darwin/colorbutton.m @@ -0,0 +1,159 @@ +// 15 may 2016 +#import "uipriv_darwin.h" + +// TODO no intrinsic height? + +@interface colorButton : NSColorWell { + uiColorButton *libui_b; + BOOL libui_changing; + BOOL libui_setting; +} +- (id)initWithFrame:(NSRect)frame libuiColorButton:(uiColorButton *)b; +- (void)deactivateOnClose:(NSNotification *)note; +- (void)libuiColor:(double *)r g:(double *)g b:(double *)b a:(double *)a; +- (void)libuiSetColor:(double)r g:(double)g b:(double)b a:(double)a; +@end + +// only one may be active at one time +static colorButton *activeColorButton = nil; + +struct uiColorButton { + uiDarwinControl c; + colorButton *button; + void (*onChanged)(uiColorButton *, void *); + void *onChangedData; +}; + +@implementation colorButton + +- (id)initWithFrame:(NSRect)frame libuiColorButton:(uiColorButton *)b +{ + self = [super initWithFrame:frame]; + if (self) { + // the default color is white; set it to black first (see -setColor: below for why we do it first) + [self libuiSetColor:0.0 g:0.0 b:0.0 a:1.0]; + + self->libui_b = b; + self->libui_changing = NO; + } + return self; +} + +- (void)activate:(BOOL)exclusive +{ + if (activeColorButton != nil) + activeColorButton->libui_changing = YES; + [NSColorPanel setPickerMask:NSColorPanelAllModesMask]; + [[NSColorPanel sharedColorPanel] setShowsAlpha:YES]; + [super activate:YES]; + activeColorButton = self; + // see stddialogs.m for details + [[NSColorPanel sharedColorPanel] setWorksWhenModal:NO]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(deactivateOnClose:) + name:NSWindowWillCloseNotification + object:[NSColorPanel sharedColorPanel]]; +} + +- (void)deactivate +{ + [super deactivate]; + activeColorButton = nil; + if (!self->libui_changing) + [[NSColorPanel sharedColorPanel] orderOut:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self + name:NSWindowWillCloseNotification + object:[NSColorPanel sharedColorPanel]]; + self->libui_changing = NO; +} + +- (void)deactivateOnClose:(NSNotification *)note +{ + [self deactivate]; +} + +- (void)setColor:(NSColor *)color +{ + uiColorButton *b = self->libui_b; + + [super setColor:color]; + // this is called by NSColorWell's init, so we have to guard + // also don't signal during a programmatic change + if (b != nil && !self->libui_setting) + (*(b->onChanged))(b, b->onChangedData); +} + +- (void)libuiColor:(double *)r g:(double *)g b:(double *)b a:(double *)a +{ + NSColor *rgba; + CGFloat cr, cg, cb, ca; + + // the given color may not be an RGBA color, which will cause the -getRed:green:blue:alpha: call to throw an exception + rgba = [[self color] colorUsingColorSpace:[NSColorSpace sRGBColorSpace]]; + [rgba getRed:&cr green:&cg blue:&cb alpha:&ca]; + *r = cr; + *g = cg; + *b = cb; + *a = ca; + // rgba will be autoreleased since it isn't a new or init call +} + +- (void)libuiSetColor:(double)r g:(double)g b:(double)b a:(double)a +{ + self->libui_setting = YES; + [self setColor:[NSColor colorWithSRGBRed:r green:g blue:b alpha:a]]; + self->libui_setting = NO; +} + +// NSColorWell has no intrinsic size by default; give it the default Interface Builder size. +- (NSSize)intrinsicContentSize +{ + return NSMakeSize(44, 23); +} + +@end + +uiDarwinControlAllDefaults(uiColorButton, button) + +// we do not want color change events to be sent to any controls other than the color buttons +// see main.m for more details +BOOL uiprivColorButtonInhibitSendAction(SEL sel, id from, id to) +{ + if (sel != @selector(changeColor:)) + return NO; + return ![to isKindOfClass:[colorButton class]]; +} + +static void defaultOnChanged(uiColorButton *b, void *data) +{ + // do nothing +} + +void uiColorButtonColor(uiColorButton *b, double *r, double *g, double *bl, double *a) +{ + [b->button libuiColor:r g:g b:bl a:a]; +} + +void uiColorButtonSetColor(uiColorButton *b, double r, double g, double bl, double a) +{ + [b->button libuiSetColor:r g:g b:bl a:a]; +} + +void uiColorButtonOnChanged(uiColorButton *b, void (*f)(uiColorButton *, void *), void *data) +{ + b->onChanged = f; + b->onChangedData = data; +} + +uiColorButton *uiNewColorButton(void) +{ + uiColorButton *b; + + uiDarwinNewControl(uiColorButton, b); + + b->button = [[colorButton alloc] initWithFrame:NSZeroRect libuiColorButton:b]; + + uiColorButtonOnChanged(b, defaultOnChanged, NULL); + + return b; +} diff --git a/darwin/combobox.m b/darwin/combobox.m index 9f63157a..cc2f330a 100644 --- a/darwin/combobox.m +++ b/darwin/combobox.m @@ -5,37 +5,17 @@ // NSPopUpButton is fine. #define comboboxWidth 96 -@interface libui_intrinsicWidthNSComboBox : NSComboBox -@end - -@implementation libui_intrinsicWidthNSComboBox - -- (NSSize)intrinsicContentSize -{ - NSSize s; - - s = [super intrinsicContentSize]; - s.width = comboboxWidth; - return s; -} - -@end - struct uiCombobox { uiDarwinControl c; - BOOL editable; NSPopUpButton *pb; NSArrayController *pbac; - NSComboBox *cb; - NSView *handle; // for uiControlHandle() void (*onSelected)(uiCombobox *, void *); void *onSelectedData; }; -@interface comboboxDelegateClass : NSObject { - struct mapTable *comboboxes; +@interface comboboxDelegateClass : NSObject { + uiprivMap *comboboxes; } -- (void)comboBoxSelectionDidChange:(NSNotification *)note; - (IBAction)onSelected:(id)sender; - (void)registerCombobox:(uiCombobox *)c; - (void)unregisterCombobox:(uiCombobox *)c; @@ -47,108 +27,67 @@ struct uiCombobox { { self = [super init]; if (self) - self->comboboxes = newMap(); + self->comboboxes = uiprivNewMap(); return self; } - (void)dealloc { - mapDestroy(self->comboboxes); + uiprivMapDestroy(self->comboboxes); [super dealloc]; } -// note: does not trigger when text changed -// TODO not perfect either: -// - triggered when keyboard navigating the open menu -// - does not trigger when the text is changed to a menu item (which normally selects that item; IDK how to inhibit that behavior - TODO) -- (void)comboBoxSelectionDidChange:(NSNotification *)note -{ - [self onSelected:[note object]]; -} - - (IBAction)onSelected:(id)sender { uiCombobox *c; - c = (uiCombobox *) mapGet(self->comboboxes, sender); + c = uiCombobox(uiprivMapGet(self->comboboxes, sender)); (*(c->onSelected))(c, c->onSelectedData); } - (void)registerCombobox:(uiCombobox *)c { - mapSet(self->comboboxes, c->handle, c); - if (c->editable) - [c->cb setDelegate:self]; - else { - [c->pb setTarget:self]; - [c->pb setAction:@selector(onSelected:)]; - } + uiprivMapSet(self->comboboxes, c->pb, c); + [c->pb setTarget:self]; + [c->pb setAction:@selector(onSelected:)]; } - (void)unregisterCombobox:(uiCombobox *)c { - if (c->editable) - [c->cb setDelegate:nil]; - else - [c->pb setTarget:nil]; - mapDelete(self->comboboxes, c->handle); + [c->pb setTarget:nil]; + uiprivMapDelete(self->comboboxes, c->pb); } @end static comboboxDelegateClass *comboboxDelegate = nil; -uiDarwinControlAllDefaultsExceptDestroy(uiCombobox, handle) +uiDarwinControlAllDefaultsExceptDestroy(uiCombobox, pb) static void uiComboboxDestroy(uiControl *cc) { uiCombobox *c = uiCombobox(cc); [comboboxDelegate unregisterCombobox:c]; - if (!c->editable) { - [c->pb unbind:@"contentObjects"]; - [c->pb unbind:@"selectedIndex"]; - [c->pbac release]; - } - [c->handle release]; + [c->pb unbind:@"contentObjects"]; + [c->pb unbind:@"selectedIndex"]; + [c->pbac release]; + [c->pb release]; uiFreeControl(uiControl(c)); } void uiComboboxAppend(uiCombobox *c, const char *text) { - if (c->editable) - [c->cb addItemWithObjectValue:toNSString(text)]; - else - [c->pbac addObject:toNSString(text)]; + [c->pbac addObject:uiprivToNSString(text)]; } -intmax_t uiComboboxSelected(uiCombobox *c) +int uiComboboxSelected(uiCombobox *c) { - if (c->editable) - return [c->cb indexOfSelectedItem]; return [c->pb indexOfSelectedItem]; } -void uiComboboxSetSelected(uiCombobox *c, intmax_t n) +void uiComboboxSetSelected(uiCombobox *c, int n) { - if (c->editable) { - // see https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ComboBox/Tasks/SettingComboBoxValue.html#//apple_ref/doc/uid/20000256 - id delegate; - - // this triggers the delegate; turn it off for now - delegate = [c->cb delegate]; - [c->cb setDelegate:nil]; - - // this seems to work fine for -1 too - [c->cb selectItemAtIndex:n]; - if (n == -1) - [c->cb setObjectValue:@""]; - else - [c->cb setObjectValue:[c->cb objectValueOfSelectedItem]]; - - [c->cb setDelegate:delegate]; - return; - } [c->pb selectItemAtIndex:n]; } @@ -163,64 +102,44 @@ static void defaultOnSelected(uiCombobox *c, void *data) // do nothing } -static uiCombobox *finishNewCombobox(BOOL editable) +uiCombobox *uiNewCombobox(void) { uiCombobox *c; + NSPopUpButtonCell *pbcell; uiDarwinNewControl(uiCombobox, c); - c->editable = editable; - if (c->editable) { - c->cb = [[libui_intrinsicWidthNSComboBox alloc] initWithFrame:NSZeroRect]; - [c->cb setUsesDataSource:NO]; - [c->cb setButtonBordered:YES]; - [c->cb setCompletes:NO]; - uiDarwinSetControlFont(c->cb, NSRegularControlSize); - c->handle = c->cb; - } else { - NSPopUpButtonCell *pbcell; + c->pb = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO]; + [c->pb setPreferredEdge:NSMinYEdge]; + pbcell = (NSPopUpButtonCell *) [c->pb cell]; + [pbcell setArrowPosition:NSPopUpArrowAtBottom]; + // the font defined by Interface Builder is Menu 13, which is lol + // just use the regular control size for consistency + uiDarwinSetControlFont(c->pb, NSRegularControlSize); - c->pb = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO]; - [c->pb setPreferredEdge:NSMinYEdge]; - pbcell = (NSPopUpButtonCell *) [c->pb cell]; - [pbcell setArrowPosition:NSPopUpArrowAtBottom]; - // TODO font - c->handle = c->pb; - - // NSPopUpButton doesn't work like a combobox - // - it automatically selects the first item - // - it doesn't support duplicates - // but we can use a NSArrayController and Cocoa bindings to bypass these restrictions - c->pbac = [NSArrayController new]; - [c->pbac setAvoidsEmptySelection:NO]; - [c->pbac setSelectsInsertedObjects:NO]; - [c->pbac setAutomaticallyRearrangesObjects:NO]; - [c->pb bind:@"contentValues" - toObject:c->pbac - withKeyPath:@"arrangedObjects" - options:nil]; - [c->pb bind:@"selectedIndex" - toObject:c->pbac - withKeyPath:@"selectionIndex" - options:nil]; - } + // NSPopUpButton doesn't work like a combobox + // - it automatically selects the first item + // - it doesn't support duplicates + // but we can use a NSArrayController and Cocoa bindings to bypass these restrictions + c->pbac = [NSArrayController new]; + [c->pbac setAvoidsEmptySelection:NO]; + [c->pbac setSelectsInsertedObjects:NO]; + [c->pbac setAutomaticallyRearrangesObjects:NO]; + [c->pb bind:@"contentValues" + toObject:c->pbac + withKeyPath:@"arrangedObjects" + options:nil]; + [c->pb bind:@"selectedIndex" + toObject:c->pbac + withKeyPath:@"selectionIndex" + options:nil]; if (comboboxDelegate == nil) { - comboboxDelegate = [comboboxDelegateClass new]; - [delegates addObject:comboboxDelegate]; + comboboxDelegate = [[comboboxDelegateClass new] autorelease]; + [uiprivDelegates addObject:comboboxDelegate]; } [comboboxDelegate registerCombobox:c]; uiComboboxOnSelected(c, defaultOnSelected, NULL); return c; } - -uiCombobox *uiNewCombobox(void) -{ - return finishNewCombobox(NO); -} - -uiCombobox *uiNewEditableCombobox(void) -{ - return finishNewCombobox(YES); -} diff --git a/darwin/control.m b/darwin/control.m index 4451ee9a..9eaf47a2 100644 --- a/darwin/control.m +++ b/darwin/control.m @@ -36,6 +36,11 @@ void uiDarwinControlSetHuggingPriority(uiDarwinControl *c, NSLayoutPriority prio (*(c->SetHuggingPriority))(c, priority, orientation); } +void uiDarwinControlChildVisibilityChanged(uiDarwinControl *c) +{ + (*(c->ChildVisibilityChanged))(c); +} + void uiDarwinSetControlFont(NSControl *c, NSControlSize size) { [c setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:size]]]; @@ -68,3 +73,12 @@ void uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl *c) if (parent != NULL) uiDarwinControlChildEdgeHuggingChanged(uiDarwinControl(parent)); } + +void uiDarwinNotifyVisibilityChanged(uiDarwinControl *c) +{ + uiControl *parent; + + parent = uiControlParent(uiControl(c)); + if (parent != NULL) + uiDarwinControlChildVisibilityChanged(uiDarwinControl(parent)); +} diff --git a/darwin/datetimepicker.m b/darwin/datetimepicker.m index 44364d9d..b786dea9 100644 --- a/darwin/datetimepicker.m +++ b/darwin/datetimepicker.m @@ -4,9 +4,131 @@ struct uiDateTimePicker { uiDarwinControl c; NSDatePicker *dp; + void (*onChanged)(uiDateTimePicker *, void *); + void *onChangedData; + BOOL blockSendOnce; }; -uiDarwinControlAllDefaults(uiDateTimePicker, dp) +// TODO see if target-action works here or not; I forgot what cody271@ originally said +// the primary advantage of the delegate is the ability to reject changes, but libui doesn't support that yet — we should consider that API option as well +@interface uiprivDatePickerDelegateClass : NSObject { + uiprivMap *pickers; +} +- (void)datePickerCell:(NSDatePickerCell *)aDatePickerCell validateProposedDateValue:(NSDate **)proposedDateValue timeInterval:(NSTimeInterval *)proposedTimeInterval; +- (void)doTimer:(NSTimer *)timer; +- (void)registerPicker:(uiDateTimePicker *)d; +- (void)unregisterPicker:(uiDateTimePicker *)d; +@end + +@implementation uiprivDatePickerDelegateClass + +- (id)init +{ + self = [super init]; + if (self) + self->pickers = uiprivNewMap(); + return self; +} + +- (void)dealloc +{ + uiprivMapDestroy(self->pickers); + [super dealloc]; +} + +- (void)datePickerCell:(NSDatePickerCell *)cell validateProposedDateValue:(NSDate **)proposedDateValue timeInterval:(NSTimeInterval *)proposedTimeInterval +{ + uiDateTimePicker *d; + + d = (uiDateTimePicker *) uiprivMapGet(self->pickers, cell); + [NSTimer scheduledTimerWithTimeInterval:0 + target:self + selector:@selector(doTimer:) + userInfo:[NSValue valueWithPointer:d] + repeats:NO]; +} + +- (void)doTimer:(NSTimer *)timer +{ + NSValue *v; + uiDateTimePicker *d; + + v = (NSValue *) [timer userInfo]; + d = (uiDateTimePicker *) [v pointerValue]; + if (d->blockSendOnce) { + d->blockSendOnce = NO; + return; + } + (*(d->onChanged))(d, d->onChangedData); +} + +- (void)registerPicker:(uiDateTimePicker *)d +{ + uiprivMapSet(self->pickers, d->dp.cell, d); + [d->dp setDelegate:self]; +} + +- (void)unregisterPicker:(uiDateTimePicker *)d +{ + [d->dp setDelegate:nil]; + uiprivMapDelete(self->pickers, d->dp.cell); +} + +@end + +static uiprivDatePickerDelegateClass *datePickerDelegate = nil; + +uiDarwinControlAllDefaultsExceptDestroy(uiDateTimePicker, dp) + +static void uiDateTimePickerDestroy(uiControl *c) +{ + uiDateTimePicker *d = uiDateTimePicker(c); + + [datePickerDelegate unregisterPicker:d]; + [d->dp release]; + uiFreeControl(uiControl(d)); +} + +static void defaultOnChanged(uiDateTimePicker *d, void *data) +{ + // do nothing +} + +// TODO consider using NSDateComponents iff we ever need the extra accuracy of not using NSTimeInterval +void uiDateTimePickerTime(uiDateTimePicker *d, struct tm *time) +{ + time_t t; + struct tm tmbuf; + NSDate *date; + + date = [d->dp dateValue]; + t = (time_t) [date timeIntervalSince1970]; + + // Copy time to minimize a race condition + // time.h functions use global non-thread-safe data + tmbuf = *localtime(&t); + memcpy(time, &tmbuf, sizeof (struct tm)); +} + +void uiDateTimePickerSetTime(uiDateTimePicker *d, const struct tm *time) +{ + time_t t; + struct tm tmbuf; + + // Copy time because mktime() modifies its argument + memcpy(&tmbuf, time, sizeof (struct tm)); + t = mktime(&tmbuf); + + // TODO get rid of the need for this + d->blockSendOnce = YES; + [d->dp setDateValue:[NSDate dateWithTimeIntervalSince1970:t]]; +} + +void uiDateTimePickerOnChanged(uiDateTimePicker *d, void (*f)(uiDateTimePicker *, void *), void *data) +{ + d->onChanged = f; + d->onChangedData = data; +} static uiDateTimePicker *finishNewDateTimePicker(NSDatePickerElementFlags elements) { @@ -15,6 +137,7 @@ static uiDateTimePicker *finishNewDateTimePicker(NSDatePickerElementFlags elemen uiDarwinNewControl(uiDateTimePicker, d); d->dp = [[NSDatePicker alloc] initWithFrame:NSZeroRect]; + [d->dp setDateValue:[NSDate date]]; [d->dp setBordered:NO]; [d->dp setBezeled:YES]; [d->dp setDrawsBackground:YES]; @@ -23,6 +146,13 @@ static uiDateTimePicker *finishNewDateTimePicker(NSDatePickerElementFlags elemen [d->dp setDatePickerMode:NSSingleDateMode]; uiDarwinSetControlFont(d->dp, NSRegularControlSize); + if (datePickerDelegate == nil) { + datePickerDelegate = [[uiprivDatePickerDelegateClass new] autorelease]; + [uiprivDelegates addObject:datePickerDelegate]; + } + [datePickerDelegate registerPicker:d]; + uiDateTimePickerOnChanged(d, defaultOnChanged, NULL); + return d; } diff --git a/darwin/debug.m b/darwin/debug.m new file mode 100644 index 00000000..aff66e0d --- /dev/null +++ b/darwin/debug.m @@ -0,0 +1,19 @@ +// 13 may 2016 +#import "uipriv_darwin.h" + +// LONGTERM don't halt on release builds + +void uiprivRealBug(const char *file, const char *line, const char *func, const char *prefix, const char *format, va_list ap) +{ + NSMutableString *str; + NSString *formatted; + + str = [NSMutableString new]; + [str appendString:[NSString stringWithFormat:@"[libui] %s:%s:%s() %s", file, line, func, prefix]]; + formatted = [[NSString alloc] initWithFormat:[NSString stringWithUTF8String:format] arguments:ap]; + [str appendString:formatted]; + [formatted release]; + NSLog(@"%@", str); + [str release]; + __builtin_trap(); +} diff --git a/darwin/draw.h b/darwin/draw.h new file mode 100644 index 00000000..382b7e77 --- /dev/null +++ b/darwin/draw.h @@ -0,0 +1,8 @@ +// 6 january 2017 + +// TODO why do we still have this file; should we just split draw.m or not + +struct uiDrawContext { + CGContextRef c; + CGFloat height; // needed for text; see below +}; diff --git a/darwin/draw.m b/darwin/draw.m index c94c073b..e54ecdd4 100644 --- a/darwin/draw.m +++ b/darwin/draw.m @@ -1,5 +1,6 @@ // 6 september 2015 #import "uipriv_darwin.h" +#import "draw.h" struct uiDrawPath { CGMutablePathRef path; @@ -11,7 +12,7 @@ uiDrawPath *uiDrawNewPath(uiDrawFillMode mode) { uiDrawPath *p; - p = uiNew(uiDrawPath); + p = uiprivNew(uiDrawPath); p->path = CGPathCreateMutable(); p->fillMode = mode; return p; @@ -20,13 +21,13 @@ uiDrawPath *uiDrawNewPath(uiDrawFillMode mode) void uiDrawFreePath(uiDrawPath *p) { CGPathRelease((CGPathRef) (p->path)); - uiFree(p); + uiprivFree(p); } void uiDrawPathNewFigure(uiDrawPath *p, double x, double y) { if (p->ended) - complain("attempt to add figure to ended path in uiDrawPathNewFigure()"); + uiprivUserBug("You cannot call uiDrawPathNewFigure() on a uiDrawPath that has already been ended. (path; %p)", p); CGPathMoveToPoint(p->path, NULL, x, y); } @@ -36,7 +37,7 @@ void uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, d double startx, starty; if (p->ended) - complain("attempt to add figure to ended path in uiDrawPathNewFigureWithArc()"); + uiprivUserBug("You cannot call uiDrawPathNewFigureWithArc() on a uiDrawPath that has already been ended. (path; %p)", p); sinStart = sin(startAngle); cosStart = cos(startAngle); startx = xCenter + radius * cosStart; @@ -47,8 +48,9 @@ void uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, d void uiDrawPathLineTo(uiDrawPath *p, double x, double y) { + // TODO refine this to require being in a path if (p->ended) - complain("attempt to add line to ended path in uiDrawPathLineTo()"); + uiprivImplBug("attempt to add line to ended path in uiDrawPathLineTo()"); CGPathAddLineToPoint(p->path, NULL, x, y); } @@ -56,10 +58,11 @@ void uiDrawPathArcTo(uiDrawPath *p, double xCenter, double yCenter, double radiu { bool cw; + // TODO likewise if (p->ended) - complain("attempt to add arc to ended path in uiDrawPathArcTo()"); - if (sweep > 2 * M_PI) - sweep = 2 * M_PI; + uiprivImplBug("attempt to add arc to ended path in uiDrawPathArcTo()"); + if (sweep > 2 * uiPi) + sweep = 2 * uiPi; cw = false; if (negative) cw = true; @@ -72,8 +75,9 @@ void uiDrawPathArcTo(uiDrawPath *p, double xCenter, double yCenter, double radiu void uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, double c2y, double endX, double endY) { + // TODO likewise if (p->ended) - complain("attempt to add bezier to ended path in uiDrawPathBezierTo()"); + uiprivImplBug("attempt to add bezier to ended path in uiDrawPathBezierTo()"); CGPathAddCurveToPoint(p->path, NULL, c1x, c1y, c2x, c2y, @@ -82,15 +86,16 @@ void uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, doubl void uiDrawPathCloseFigure(uiDrawPath *p) { + // TODO likewise if (p->ended) - complain("attempt to close figure of ended path in uiDrawPathCloseFigure()"); + uiprivImplBug("attempt to close figure of ended path in uiDrawPathCloseFigure()"); CGPathCloseSubpath(p->path); } void uiDrawPathAddRectangle(uiDrawPath *p, double x, double y, double width, double height) { if (p->ended) - complain("attempt to add rectangle to ended path in uiDrawPathAddRectangle()"); + uiprivUserBug("You cannot call uiDrawPathAddRectangle() on a uiDrawPath that has already been ended. (path; %p)", p); CGPathAddRect(p->path, NULL, CGRectMake(x, y, width, height)); } @@ -99,24 +104,19 @@ void uiDrawPathEnd(uiDrawPath *p) p->ended = TRUE; } -struct uiDrawContext { - CGContextRef c; - CGFloat height; // needed for text; see below -}; - -uiDrawContext *newContext(CGContextRef ctxt, CGFloat height) +uiDrawContext *uiprivDrawNewContext(CGContextRef ctxt, CGFloat height) { uiDrawContext *c; - c = uiNew(uiDrawContext); + c = uiprivNew(uiDrawContext); c->c = ctxt; c->height = height; return c; } -void freeContext(uiDrawContext *c) +void uiprivDrawFreeContext(uiDrawContext *c) { - uiFree(c); + uiprivFree(c); } // a stroke is identical to a fill of a stroked path @@ -132,7 +132,7 @@ void uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStro uiDrawPath p2; if (!path->ended) - complain("path not ended in uiDrawStroke()"); + uiprivUserBug("You cannot call uiDrawStroke() on a uiDrawPath that has not been ended. (path: %p)", path); switch (p->Cap) { case uiDrawLineCapFlat: @@ -160,7 +160,7 @@ void uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStro // create a temporary path identical to the previous one dashPath = (CGPathRef) path->path; if (p->NumDashes != 0) { - dashes = (CGFloat *) uiAlloc(p->NumDashes * sizeof (CGFloat), "CGFloat[]"); + dashes = (CGFloat *) uiprivAlloc(p->NumDashes * sizeof (CGFloat), "CGFloat[]"); for (i = 0; i < p->NumDashes; i++) dashes[i] = p->Dashes[i]; dashPath = CGPathCreateCopyByDashingPath(path->path, @@ -168,7 +168,7 @@ void uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStro p->DashPhase, dashes, p->NumDashes); - uiFree(dashes); + uiprivFree(dashes); } // the documentation is wrong: this produces a path suitable for calling CGPathCreateCopyByStrokingPath(), not for filling directly // the cast is safe; we never modify the CGPathRef and always cast it back to a CGPathRef anyway @@ -193,6 +193,7 @@ void uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStro // for a solid fill, we can merely have Core Graphics fill directly static void fillSolid(CGContextRef ctxt, uiDrawPath *p, uiDrawBrush *b) { + // TODO this uses DeviceRGB; switch to sRGB CGContextSetRGBFillColor(ctxt, b->R, b->G, b->B, b->A); switch (p->fillMode) { case uiDrawFillModeWinding: @@ -217,10 +218,14 @@ static void fillGradient(CGContextRef ctxt, uiDrawPath *p, uiDrawBrush *b) // gradients need a color space // for consistency with windows, use sRGB colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); + if (colorspace == NULL) { + // TODO + } + // TODO add NULL check to other uses of CGColorSpace // make the gradient - colors = uiAlloc(b->NumStops * 4 * sizeof (CGFloat), "CGFloat[]"); - locations = uiAlloc(b->NumStops * sizeof (CGFloat), "CGFloat[]"); + colors = uiprivAlloc(b->NumStops * 4 * sizeof (CGFloat), "CGFloat[]"); + locations = uiprivAlloc(b->NumStops * sizeof (CGFloat), "CGFloat[]"); for (i = 0; i < b->NumStops; i++) { colors[i * 4 + 0] = b->Stops[i].R; colors[i * 4 + 1] = b->Stops[i].G; @@ -229,8 +234,8 @@ static void fillGradient(CGContextRef ctxt, uiDrawPath *p, uiDrawBrush *b) locations[i] = b->Stops[i].Pos; } gradient = CGGradientCreateWithColorComponents(colorspace, colors, locations, b->NumStops); - uiFree(locations); - uiFree(colors); + uiprivFree(locations); + uiprivFree(colors); // because we're mucking with clipping, we need to save the graphics state and restore it later CGContextSaveGState(ctxt); @@ -275,7 +280,7 @@ static void fillGradient(CGContextRef ctxt, uiDrawPath *p, uiDrawBrush *b) void uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b) { if (!path->ended) - complain("path not ended in uiDrawFill()"); + uiprivUserBug("You cannot call uiDrawStroke() on a uiDrawPath that has not been ended. (path: %p)", path); CGContextAddPath(c->c, (CGPathRef) (path->path)); switch (b->Type) { case uiDrawBrushTypeSolid: @@ -289,7 +294,7 @@ void uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b) // TODO return; } - complain("unknown brush type %d in uiDrawFill()", b->Type); + uiprivUserBug("Unknown brush type %d passed to uiDrawFill().", b->Type); } static void m2c(uiDrawMatrix *m, CGAffineTransform *c) @@ -312,12 +317,6 @@ static void c2m(CGAffineTransform *c, uiDrawMatrix *m) m->M32 = c->ty; } -// TODO get rid of the separate setIdentity() -void uiDrawMatrixSetIdentity(uiDrawMatrix *m) -{ - setIdentity(m); -} - void uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y) { CGAffineTransform c; @@ -333,13 +332,12 @@ void uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x double xt, yt; m2c(m, &c); - // TODO explain why the translation must come first xt = x; yt = y; - scaleCenter(xCenter, yCenter, &xt, &yt); + uiprivScaleCenter(xCenter, yCenter, &xt, &yt); c = CGAffineTransformTranslate(c, xt, yt); c = CGAffineTransformScale(c, x, y); - // TODO undo the translation? + c = CGAffineTransformTranslate(c, -xt, -yt); c2m(&c, m); } @@ -356,7 +354,7 @@ void uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount) void uiDrawMatrixSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount) { - fallbackSkew(m, x, y, xamount, yamount); + uiprivFallbackSkew(m, x, y, xamount, yamount); } void uiDrawMatrixMultiply(uiDrawMatrix *dest, uiDrawMatrix *src) @@ -427,7 +425,7 @@ void uiDrawTransform(uiDrawContext *c, uiDrawMatrix *m) void uiDrawClip(uiDrawContext *c, uiDrawPath *path) { if (!path->ended) - complain("path not ended in uiDrawClip()"); + uiprivUserBug("You cannot call uiDrawCilp() on a uiDrawPath that has not been ended. (path: %p)", path); CGContextAddPath(c->c, (CGPathRef) (path->path)); switch (path->fillMode) { case uiDrawFillModeWinding: @@ -449,8 +447,3 @@ void uiDrawRestore(uiDrawContext *c) { CGContextRestoreGState(c->c); } - -void uiDrawText(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout) -{ - doDrawText(c->c, c->height, x, y, layout); -} diff --git a/darwin/drawtext.m b/darwin/drawtext.m index 86fcb753..c04b402b 100644 --- a/darwin/drawtext.m +++ b/darwin/drawtext.m @@ -1,626 +1,214 @@ -// 6 september 2015 +// 7 march 2018 #import "uipriv_darwin.h" - -// TODO for all relevant routines, make sure we are freeing memory correctly -// TODO make sure allocation failures throw exceptions? -struct uiDrawFontFamilies { - CFArrayRef fonts; -}; - -uiDrawFontFamilies *uiDrawListFontFamilies(void) -{ - uiDrawFontFamilies *ff; - - ff = uiNew(uiDrawFontFamilies); - // TODO is there a way to get an error reason? - ff->fonts = CTFontManagerCopyAvailableFontFamilyNames(); - if (ff->fonts == NULL) - complain("error getting available font names (no reason specified)"); - return ff; -} - -uintmax_t uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff) -{ - return CFArrayGetCount(ff->fonts); -} - -char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, uintmax_t n) -{ - CFStringRef familystr; - char *family; - - familystr = (CFStringRef) CFArrayGetValueAtIndex(ff->fonts, n); - // TODO create a uiDarwinCFStringToText()? - family = uiDarwinNSStringToText((NSString *) familystr); - // Get Rule means we do not free familystr - return family; -} - -void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff) -{ - CFRelease(ff->fonts); - uiFree(ff); -} - -struct uiDrawTextFont { - CTFontRef f; -}; - -uiDrawTextFont *mkTextFont(CTFontRef f, BOOL retain) -{ - uiDrawTextFont *font; - - font = uiNew(uiDrawTextFont); - font->f = f; - if (retain) - CFRetain(font->f); - return font; -} - -uiDrawTextFont *mkTextFontFromNSFont(NSFont *f) -{ - // toll-free bridging; we do retain, though - return mkTextFont((CTFontRef) f, YES); -} - -static CFMutableDictionaryRef newAttrList(void) -{ - CFMutableDictionaryRef attr; - - attr = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - if (attr == NULL) - complain("error creating attribute dictionary in newAttrList()()"); - return attr; -} - -static void addFontFamilyAttr(CFMutableDictionaryRef attr, const char *family) -{ - CFStringRef cfstr; - - cfstr = CFStringCreateWithCString(NULL, family, kCFStringEncodingUTF8); - if (cfstr == NULL) - complain("error creating font family name CFStringRef in addFontFamilyAttr()"); - CFDictionaryAddValue(attr, kCTFontFamilyNameAttribute, cfstr); - CFRelease(cfstr); // dictionary holds its own reference -} - -static void addFontSizeAttr(CFMutableDictionaryRef attr, double size) -{ - CFNumberRef n; - - n = CFNumberCreate(NULL, kCFNumberDoubleType, &size); - CFDictionaryAddValue(attr, kCTFontSizeAttribute, n); - CFRelease(n); -} - -#if 0 -TODO -// See http://stackoverflow.com/questions/4810409/does-coretext-support-small-caps/4811371#4811371 and https://git.gnome.org/browse/pango/tree/pango/pangocoretext-fontmap.c for what these do -// And fortunately, unlike the traits (see below), unmatched features are simply ignored without affecting the other features :D -static void addFontSmallCapsAttr(CFMutableDictionaryRef attr) -{ - CFMutableArrayRef outerArray; - CFMutableDictionaryRef innerDict; - CFNumberRef numType, numSelector; - int num; - - outerArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); - if (outerArray == NULL) - complain("error creating outer CFArray for adding small caps attributes in addFontSmallCapsAttr()"); - - // Apple's headers say these are deprecated, but a few fonts still rely on them - num = kLetterCaseType; - numType = CFNumberCreate(NULL, kCFNumberIntType, &num); - num = kSmallCapsSelector; - numSelector = CFNumberCreate(NULL, kCFNumberIntType, &num); - innerDict = newAttrList(); - CFDictionaryAddValue(innerDict, kCTFontFeatureTypeIdentifierKey, numType); - CFRelease(numType); - CFDictionaryAddValue(innerDict, kCTFontFeatureSelectorIdentifierKey, numSelector); - CFRelease(numSelector); - CFArrayAppendValue(outerArray, innerDict); - CFRelease(innerDict); // and likewise for CFArray - - // these are the non-deprecated versions of the above; some fonts have these instead - num = kLowerCaseType; - numType = CFNumberCreate(NULL, kCFNumberIntType, &num); - num = kLowerCaseSmallCapsSelector; - numSelector = CFNumberCreate(NULL, kCFNumberIntType, &num); - innerDict = newAttrList(); - CFDictionaryAddValue(innerDict, kCTFontFeatureTypeIdentifierKey, numType); - CFRelease(numType); - CFDictionaryAddValue(innerDict, kCTFontFeatureSelectorIdentifierKey, numSelector); - CFRelease(numSelector); - CFArrayAppendValue(outerArray, innerDict); - CFRelease(innerDict); // and likewise for CFArray - - CFDictionaryAddValue(attr, kCTFontFeatureSettingsAttribute, outerArray); - CFRelease(outerArray); -} -#endif - -// Named constants for these were NOT added until 10.11, and even then they were added as external symbols instead of macros, so we can't use them directly :( -// kode54 got these for me before I had access to El Capitan; thanks to him. -#define ourNSFontWeightUltraLight -0.800000 -#define ourNSFontWeightThin -0.600000 -#define ourNSFontWeightLight -0.400000 -#define ourNSFontWeightRegular 0.000000 -#define ourNSFontWeightMedium 0.230000 -#define ourNSFontWeightSemibold 0.300000 -#define ourNSFontWeightBold 0.400000 -#define ourNSFontWeightHeavy 0.560000 -#define ourNSFontWeightBlack 0.620000 -static const CGFloat ctWeights[] = { - // yeah these two have their names swapped; blame Pango - // TODO note that these names do not necessarily line up with their OS names - [uiDrawTextWeightThin] = ourNSFontWeightUltraLight, - [uiDrawTextWeightUltraLight] = ourNSFontWeightThin, - [uiDrawTextWeightLight] = ourNSFontWeightLight, - // for this one let's go between Light and Regular - // TODO figure out if we can rely on the order for these (and the one below) - [uiDrawTextWeightBook] = ourNSFontWeightLight + ((ourNSFontWeightRegular - ourNSFontWeightLight) / 2), - [uiDrawTextWeightNormal] = ourNSFontWeightRegular, - [uiDrawTextWeightMedium] = ourNSFontWeightMedium, - [uiDrawTextWeightSemiBold] = ourNSFontWeightSemibold, - [uiDrawTextWeightBold] = ourNSFontWeightBold, - // for this one let's go between Bold and Heavy - [uiDrawTextWeightUtraBold] = ourNSFontWeightBold + ((ourNSFontWeightHeavy - ourNSFontWeightBold) / 2), - [uiDrawTextWeightHeavy] = ourNSFontWeightHeavy, - [uiDrawTextWeightUltraHeavy] = ourNSFontWeightBlack, -}; - -// Unfortunately there are still no named constants for these. -// Let's just use normalized widths. -// As far as I can tell (OS X only has condensed fonts, not expanded fonts; TODO), regardless of condensed or expanded, negative means condensed and positive means expanded. -// TODO verify this is correct -static const CGFloat ctStretches[] = { - [uiDrawTextStretchUltraCondensed] = -1.0, - [uiDrawTextStretchExtraCondensed] = -0.75, - [uiDrawTextStretchCondensed] = -0.5, - [uiDrawTextStretchSemiCondensed] = -0.25, - [uiDrawTextStretchNormal] = 0.0, - [uiDrawTextStretchSemiExpanded] = 0.25, - [uiDrawTextStretchExpanded] = 0.5, - [uiDrawTextStretchExtraExpanded] = 0.75, - [uiDrawTextStretchUltraExpanded] = 1.0, -}; - -struct closeness { - CFIndex index; - CGFloat weight; - CGFloat italic; - CGFloat stretch; - CGFloat distance; -}; - -// Stupidity: CTFont requires an **exact match for the entire traits dictionary**, otherwise it will **drop ALL the traits**. -// We have to implement the closest match ourselves. -// Also we have to do this before adding the small caps flags, because the matching descriptors won't have those. -// TODO document that font matching is closest match but the search method is OS defined -CTFontDescriptorRef matchTraits(CTFontDescriptorRef against, uiDrawTextWeight weight, uiDrawTextItalic italic, uiDrawTextStretch stretch) -{ - CGFloat targetWeight; - CGFloat italicCloseness, obliqueCloseness, normalCloseness; - CGFloat targetStretch; - CFArrayRef matching; - CFIndex i, n; - struct closeness *closeness; - CTFontDescriptorRef current; - CTFontDescriptorRef out; - - targetWeight = ctWeights[weight]; - switch (italic) { - case uiDrawTextItalicNormal: - italicCloseness = 1; - obliqueCloseness = 1; - normalCloseness = 0; - break; - case uiDrawTextItalicOblique: - italicCloseness = 0.5; - obliqueCloseness = 0; - normalCloseness = 1; - break; - case uiDrawTextItalicItalic: - italicCloseness = 0; - obliqueCloseness = 0.5; - normalCloseness = 1; - break; - } - targetStretch = ctStretches[stretch]; - - matching = CTFontDescriptorCreateMatchingFontDescriptors(against, NULL); - if (matching == NULL) - // no matches; give the original back and hope for the best - return against; - n = CFArrayGetCount(matching); - if (n == 0) { - // likewise - CFRelease(matching); - return against; - } - - closeness = (struct closeness *) uiAlloc(n * sizeof (struct closeness), "struct closeness[]"); - for (i = 0; i < n; i++) { - CFDictionaryRef traits; - CFNumberRef cfnum; - CTFontSymbolicTraits symbolic; - - closeness[i].index = i; - - current = CFArrayGetValueAtIndex(matching, i); - traits = CTFontDescriptorCopyAttribute(current, kCTFontTraitsAttribute); - if (traits == NULL) { - // couldn't get traits; be safe by ranking it lowest - // TODO figure out what the longest possible distances are - closeness[i].weight = 3; - closeness[i].italic = 2; - closeness[i].stretch = 3; - continue; - } - - symbolic = 0; // assume no symbolic traits if none are listed - cfnum = CFDictionaryGetValue(traits, kCTFontSymbolicTrait); - if (cfnum != NULL) { - SInt32 s; - - if (CFNumberGetValue(cfnum, kCFNumberSInt32Type, &s) == false) - complain("error getting symbolic traits in matchTraits()"); - symbolic = (CTFontSymbolicTraits) s; - // Get rule; do not release cfnum - } - - // now try weight - cfnum = CFDictionaryGetValue(traits, kCTFontWeightTrait); - if (cfnum != NULL) { - CGFloat val; - - // TODO instead of complaining for this and width, should we just fall through to the default? - if (CFNumberGetValue(cfnum, kCFNumberCGFloatType, &val) == false) - complain("error getting weight value in matchTraits()"); - closeness[i].weight = val - targetWeight; - } else - // okay there's no weight key; let's try the literal meaning of the symbolic constant - // TODO is the weight key guaranteed? - if ((symbolic & kCTFontBoldTrait) != 0) - closeness[i].weight = ourNSFontWeightBold - targetWeight; - else - closeness[i].weight = ourNSFontWeightRegular - targetWeight; - - // italics is a bit harder because Core Text doesn't expose a concept of obliqueness - // Pango just does a g_strrstr() (backwards case-sensitive search) for "Oblique" in the font's style name (see https://git.gnome.org/browse/pango/tree/pango/pangocoretext-fontmap.c); let's do that too I guess - if ((symbolic & kCTFontItalicTrait) != 0) - closeness[i].italic = italicCloseness; - else { - CFStringRef styleName; - BOOL isOblique; - - isOblique = NO; // default value - styleName = CTFontDescriptorCopyAttribute(current, kCTFontStyleNameAttribute); - if (styleName != NULL) { - CFRange range; - - // note the use of the toll-free bridge for the string literal, since CFSTR() *can* return NULL - range = CFStringFind(styleName, (CFStringRef) @"Oblique", kCFCompareBackwards); - if (range.location != kCFNotFound) - isOblique = YES; - CFRelease(styleName); - } - if (isOblique) - closeness[i].italic = obliqueCloseness; - else - closeness[i].italic = normalCloseness; - } - - // now try width - // TODO this does not seem to be enough for Skia's extended variants; the width trait is 0 but the Expanded flag is on - // TODO verify the rest of this matrix - cfnum = CFDictionaryGetValue(traits, kCTFontWidthTrait); - if (cfnum != NULL) { - CGFloat val; - - if (CFNumberGetValue(cfnum, kCFNumberCGFloatType, &val) == false) - complain("error getting width value in matchTraits()"); - closeness[i].stretch = val - targetStretch; - } else - // okay there's no width key; let's try the literal meaning of the symbolic constant - // TODO is the width key guaranteed? - if ((symbolic & kCTFontExpandedTrait) != 0) - closeness[i].stretch = 1.0 - targetStretch; - else if ((symbolic & kCTFontCondensedTrait) != 0) - closeness[i].stretch = -1.0 - targetStretch; - else - closeness[i].stretch = 0.0 - targetStretch; - - CFRelease(traits); - } - - // now figure out the 3-space difference between the three and sort by that - for (i = 0; i < n; i++) { - CGFloat weight, italic, stretch; - - weight = closeness[i].weight; - weight *= weight; - italic = closeness[i].italic; - italic *= italic; - stretch = closeness[i].stretch; - stretch *= stretch; - closeness[i].distance = sqrt(weight + italic + stretch); - } - qsort_b(closeness, n, sizeof (struct closeness), ^(const void *aa, const void *bb) { - const struct closeness *a = (const struct closeness *) aa; - const struct closeness *b = (const struct closeness *) bb; - - // via http://www.gnu.org/software/libc/manual/html_node/Comparison-Functions.html#Comparison-Functions - // TODO is this really the best way? isn't it the same as if (*a < *b) return -1; if (*a > *b) return 1; return 0; ? - return (a->distance > b->distance) - (a->distance < b->distance); - }); - // and the first element of the sorted array is what we want - out = CFArrayGetValueAtIndex(matching, closeness[0].index); - CFRetain(out); // get rule - - // release everything - uiFree(closeness); - CFRelease(matching); - // and release the original descriptor since we no longer need it - CFRelease(against); - - return out; -} - -// Now remember what I said earlier about having to add the small caps traits after calling the above? This gets a dictionary back so we can do so. -CFMutableDictionaryRef extractAttributes(CTFontDescriptorRef desc) -{ - CFDictionaryRef dict; - CFMutableDictionaryRef mdict; - - dict = CTFontDescriptorCopyAttributes(desc); - // this might not be mutable, so make a mutable copy - mdict = CFDictionaryCreateMutableCopy(NULL, 0, dict); - CFRelease(dict); - return mdict; -} - -uiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc) -{ - CTFontRef f; - CFMutableDictionaryRef attr; - CTFontDescriptorRef cfdesc; - - attr = newAttrList(); - addFontFamilyAttr(attr, desc->Family); - addFontSizeAttr(attr, desc->Size); - - // now we have to do the traits matching, so create a descriptor, match the traits, and then get the attributes back - cfdesc = CTFontDescriptorCreateWithAttributes(attr); - // TODO release attr? - cfdesc = matchTraits(cfdesc, desc->Weight, desc->Italic, desc->Stretch); -/*TODO - attr = extractAttributes(cfdesc); - CFRelease(cfdesc); - - // and NOW create the final descriptor - cfdesc = CTFontDescriptorCreateWithAttributes(attr); - // TODO release attr? -*/ - // specify the initial size again just to be safe - f = CTFontCreateWithFontDescriptor(cfdesc, desc->Size, NULL); - // TODO release cfdesc? - - return mkTextFont(f, NO); // we hold the initial reference; no need to retain again -} - -void uiDrawFreeTextFont(uiDrawTextFont *font) -{ - CFRelease(font->f); - uiFree(font); -} - -uintptr_t uiDrawTextFontHandle(uiDrawTextFont *font) -{ - return (uintptr_t) (font->f); -} - -void uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc) -{ - // TODO TODO TODO TODO -} - -// text sizes and user space points are identical: -// - https://developer.apple.com/library/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/TypoFeatures/TextSystemFeatures.html#//apple_ref/doc/uid/TP40009459-CH6-51627-BBCCHIFF text points are 72 per inch -// - https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Transforms/Transforms.html#//apple_ref/doc/uid/TP40003290-CH204-SW5 user space points are 72 per inch -void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics) -{ - metrics->Ascent = CTFontGetAscent(font->f); - metrics->Descent = CTFontGetDescent(font->f); - metrics->Leading = CTFontGetLeading(font->f); - metrics->UnderlinePos = CTFontGetUnderlinePosition(font->f); - metrics->UnderlineThickness = CTFontGetUnderlineThickness(font->f); -} - -struct uiDrawTextLayout { - CFMutableAttributedStringRef mas; - double width; -}; - -uiDrawTextLayout *uiDrawNewTextLayout(const char *str, uiDrawTextFont *defaultFont, double width) -{ - uiDrawTextLayout *layout; -//TODO CFStringRef cfstr; - CFAttributedStringRef immutable; - CFMutableDictionaryRef attr; - - layout = uiNew(uiDrawTextLayout); - - attr = newAttrList(); - // this will retain defaultFont->f; no need to worry - CFDictionaryAddValue(attr, kCTFontAttributeName, defaultFont->f); - - // TODO convert the NSString call to a CFString call - immutable = CFAttributedStringCreate(NULL, (CFStringRef) [NSString stringWithUTF8String:str], attr); - if (immutable == NULL) - complain("error creating immutable attributed string in uiDrawNewTextLayout()"); -//TODO CFRelease(cfstr); - CFRelease(attr); - - layout->mas = CFAttributedStringCreateMutableCopy(NULL, 0, immutable); - if (layout->mas == NULL) - complain("error creating attributed string in uiDrawNewTextLayout()"); - CFRelease(immutable); - - uiDrawTextLayoutSetWidth(layout, width); - - return layout; -} - -void uiDrawFreeTextLayout(uiDrawTextLayout *layout) -{ - CFRelease(layout->mas); - uiFree(layout); -} - -void uiDrawTextLayoutSetWidth(uiDrawTextLayout *layout, double width) -{ - layout->width = width; -} - -struct framesetter { - CTFramesetterRef fs; - CFMutableDictionaryRef frameAttrib; - CGSize extents; -}; - -// TODO CTFrameProgression for RTL/LTR -// TODO kCTParagraphStyleSpecifierMaximumLineSpacing, kCTParagraphStyleSpecifierMinimumLineSpacing, kCTParagraphStyleSpecifierLineSpacingAdjustment for line spacing -static void mkFramesetter(uiDrawTextLayout *layout, struct framesetter *fs) -{ - CFRange fitRange; - CGFloat width; - - fs->fs = CTFramesetterCreateWithAttributedString(layout->mas); - if (fs->fs == NULL) - complain("error creating CTFramesetter object in mkFramesetter()"); - - // TODO kCTFramePathWidthAttributeName? - fs->frameAttrib = NULL; - - width = layout->width; - if (layout->width < 0) - width = CGFLOAT_MAX; - // TODO these seem to be floor()'d or truncated? - fs->extents = CTFramesetterSuggestFrameSizeWithConstraints(fs->fs, - CFRangeMake(0, 0), - fs->frameAttrib, - CGSizeMake(width, CGFLOAT_MAX), - &fitRange); // not documented as accepting NULL -} - -static void freeFramesetter(struct framesetter *fs) -{ - if (fs->frameAttrib != NULL) - CFRelease(fs->frameAttrib); - CFRelease(fs->fs); -} - -// TODO document that the extent width can be greater than the requested width if the requested width is small enough that only one character can fit -// TODO figure out how line separation and leading plays into this -// TODO reconcile differences in character wrapping on platforms -void uiDrawTextLayoutExtents(uiDrawTextLayout *layout, double *width, double *height) -{ - struct framesetter fs; - - mkFramesetter(layout, &fs); - *width = fs.extents.width; - *height = fs.extents.height; - freeFramesetter(&fs); -} - -// Core Text doesn't draw onto a flipped view correctly; we have to do this -// see the iOS bits of the first example at https://developer.apple.com/library/mac/documentation/StringsTextFonts/Conceptual/CoreText_Programming/LayoutOperations/LayoutOperations.html#//apple_ref/doc/uid/TP40005533-CH12-SW1 (iOS is naturally flipped) -// TODO how is this affected by the CTM? -static void prepareContextForText(CGContextRef c, CGFloat cheight, double *y) -{ - CGContextSaveGState(c); - CGContextTranslateCTM(c, 0, cheight); - CGContextScaleCTM(c, 1.0, -1.0); - CGContextSetTextMatrix(c, CGAffineTransformIdentity); - - // wait, that's not enough; we need to offset y values to account for our new flipping - *y = cheight - *y; -} - -// TODO placement is incorrect for Helvetica -void doDrawText(CGContextRef c, CGFloat cheight, double x, double y, uiDrawTextLayout *layout) -{ - struct framesetter fs; - CGRect rect; +#import "draw.h" +#import "attrstr.h" + +// problem: for a CTFrame made from an empty string, the CTLine array will be empty, and we will crash when doing anything requiring a CTLine +// solution: for those cases, maintain a separate framesetter just for computing those things +// in the usual case, the separate copy will just be identical to the regular one, with extra references to everything within +@interface uiprivTextFrame : NSObject { + CFAttributedStringRef attrstr; + NSArray *backgroundParams; + CTFramesetterRef framesetter; + CGSize size; CGPathRef path; CTFrameRef frame; - - prepareContextForText(c, cheight, &y); - mkFramesetter(layout, &fs); - - // oh, and since we're flipped, y is the bottom-left coordinate of the rectangle, not the top-left - // since we are flipped, we subtract - y -= fs.extents.height; - - rect.origin = CGPointMake(x, y); - rect.size = fs.extents; - path = CGPathCreateWithRect(rect, NULL); - - frame = CTFramesetterCreateFrame(fs.fs, - CFRangeMake(0, 0), - path, - fs.frameAttrib); - if (frame == NULL) - complain("error creating CTFrame object in doDrawText()"); - CTFrameDraw(frame, c); - CFRelease(frame); - - CFRelease(path); - - freeFramesetter(&fs); - CGContextRestoreGState(c); } +- (id)initWithLayoutParams:(uiDrawTextLayoutParams *)p; +- (void)draw:(uiDrawContext *)c textLayout:(uiDrawTextLayout *)tl at:(double)x y:(double)y; +- (void)returnWidth:(double *)width height:(double *)height; +- (CFArrayRef)lines; +@end -// TODO provide an equivalent to CTLineGetTypographicBounds() on uiDrawTextLayout? +@implementation uiprivDrawTextBackgroundParams -// TODO keep this for TODO and documentation purposes -#if 0 - w = CTLineGetTypographicBounds(line, &ascent, &descent, NULL); - // though CTLineGetTypographicBounds() returns 0 on error, it also returns 0 on an empty string, so we can't reasonably check for error - CFRelease(line); - - // TODO provide a way to get the image bounds as a separate function later - bounds = CTLineGetImageBounds(line, c); - // though CTLineGetImageBounds() returns CGRectNull on error, it also returns CGRectNull on an empty string, so we can't reasonably check for error - - // CGContextSetTextPosition() positions at the baseline in the case of CTLineDraw(); we need the top-left corner instead - CTLineGetTypographicBounds(line, &yoff, NULL, NULL); - // remember that we're flipped, so we subtract - y -= yoff; - CGContextSetTextPosition(c, x, y); -#endif - -#define rangeToCFRange() CFRangeMake(startChar, endChar - startChar) - -void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, intmax_t startChar, intmax_t endChar, double r, double g, double b, double a) +- (id)initWithStart:(size_t)s end:(size_t)e r:(double)red g:(double)green b:(double)blue a:(double)alpha { - CGColorSpaceRef colorspace; - CGFloat components[4]; - CGColorRef color; - - // for consistency with windows, use sRGB - colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); - components[0] = r; - components[1] = g; - components[2] = b; - components[3] = a; - color = CGColorCreate(colorspace, components); - CGColorSpaceRelease(colorspace); - - CFAttributedStringSetAttribute(layout->mas, - rangeToCFRange(), - kCTForegroundColorAttributeName, - color); - CGColorRelease(color); // TODO safe? + self = [super init]; + if (self) { + self->start = s; + self->end = e; + self->r = red; + self->g = green; + self->b = blue; + self->a = alpha; + } + return self; +} + +- (void)draw:(CGContextRef)c layout:(uiDrawTextLayout *)layout at:(double)x y:(double)y utf8Mapping:(const size_t *)u16tou8 +{ + // TODO +} + +@end + +@implementation uiprivTextFrame + +- (id)initWithLayoutParams:(uiDrawTextLayoutParams *)p +{ + CFRange range; + CGFloat cgwidth; + CFRange unused; + CGRect rect; + + self = [super init]; + if (self) { + self->attrstr = uiprivAttributedStringToCFAttributedString(p, &(self->backgroundParams)); + // TODO kCTParagraphStyleSpecifierMaximumLineSpacing, kCTParagraphStyleSpecifierMinimumLineSpacing, kCTParagraphStyleSpecifierLineSpacingAdjustment for line spacing + self->framesetter = CTFramesetterCreateWithAttributedString(self->attrstr); + if (self->framesetter == NULL) { + // TODO + } + + range.location = 0; + range.length = CFAttributedStringGetLength(self->attrstr); + + cgwidth = (CGFloat) (p->Width); + if (cgwidth < 0) + cgwidth = CGFLOAT_MAX; + self->size = CTFramesetterSuggestFrameSizeWithConstraints(self->framesetter, + range, + // TODO kCTFramePathWidthAttributeName? + NULL, + CGSizeMake(cgwidth, CGFLOAT_MAX), + &unused); // not documented as accepting NULL (TODO really?) + + rect.origin = CGPointZero; + rect.size = self->size; + self->path = CGPathCreateWithRect(rect, NULL); + self->frame = CTFramesetterCreateFrame(self->framesetter, + range, + self->path, + // TODO kCTFramePathWidthAttributeName? + NULL); + if (self->frame == NULL) { + // TODO + } + } + return self; +} + +- (void)dealloc +{ + CFRelease(self->frame); + CFRelease(self->path); + CFRelease(self->framesetter); + [self->backgroundParams release]; + CFRelease(self->attrstr); + [super dealloc]; +} + +- (void)draw:(uiDrawContext *)c textLayout:(uiDrawTextLayout *)tl at:(double)x y:(double)y +{ + uiprivDrawTextBackgroundParams *dtb; + CGAffineTransform textMatrix; + + CGContextSaveGState(c->c); + // save the text matrix because it's not part of the graphics state + textMatrix = CGContextGetTextMatrix(c->c); + + for (dtb in self->backgroundParams) + /* TODO */; + + // Core Text doesn't draw onto a flipped view correctly; we have to pretend it was unflipped + // see the iOS bits of the first example at https://developer.apple.com/library/mac/documentation/StringsTextFonts/Conceptual/CoreText_Programming/LayoutOperations/LayoutOperations.html#//apple_ref/doc/uid/TP40005533-CH12-SW1 (iOS is naturally flipped) + // TODO how is this affected by a non-identity CTM? + CGContextTranslateCTM(c->c, 0, c->height); + CGContextScaleCTM(c->c, 1.0, -1.0); + CGContextSetTextMatrix(c->c, CGAffineTransformIdentity); + + // wait, that's not enough; we need to offset y values to account for our new flipping + // TODO explain this calculation + y = c->height - self->size.height - y; + + // CTFrameDraw() draws in the path we specified when creating the frame + // this means that in our usage, CTFrameDraw() will draw at (0,0) + // so move the origin to be at (x,y) instead + // TODO are the signs correct? + CGContextTranslateCTM(c->c, x, y); + + CTFrameDraw(self->frame, c->c); + + CGContextSetTextMatrix(c->c, textMatrix); + CGContextRestoreGState(c->c); +} + +- (void)returnWidth:(double *)width height:(double *)height +{ + if (width != NULL) + *width = self->size.width; + if (height != NULL) + *height = self->size.height; +} + +- (CFArrayRef)lines +{ + return CTFrameGetLines(self->frame); +} + +@end + +struct uiDrawTextLayout { + uiprivTextFrame *frame; + uiprivTextFrame *forLines; + BOOL empty; + + // for converting CFAttributedString indices from/to byte offsets + size_t *u8tou16; + size_t nUTF8; + size_t *u16tou8; + size_t nUTF16; +}; + +uiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *p) +{ + uiDrawTextLayout *tl; + + tl = uiprivNew(uiDrawTextLayout); + tl->frame = [[uiprivTextFrame alloc] initWithLayoutParams:p]; + if (uiAttributedStringLen(p->String) != 0) + tl->forLines = [tl->frame retain]; + else { + uiAttributedString *space; + uiDrawTextLayoutParams p2; + + tl->empty = YES; + space = uiNewAttributedString(" "); + p2 = *p; + p2.String = space; + tl->forLines = [[uiprivTextFrame alloc] initWithLayoutParams:&p2]; + uiFreeAttributedString(space); + } + + // and finally copy the UTF-8/UTF-16 conversion tables + tl->u8tou16 = uiprivAttributedStringCopyUTF8ToUTF16Table(p->String, &(tl->nUTF8)); + tl->u16tou8 = uiprivAttributedStringCopyUTF16ToUTF8Table(p->String, &(tl->nUTF16)); + return tl; +} + +void uiDrawFreeTextLayout(uiDrawTextLayout *tl) +{ + uiprivFree(tl->u16tou8); + uiprivFree(tl->u8tou16); + [tl->forLines release]; + [tl->frame release]; + uiprivFree(tl); +} + +// TODO document that (x,y) is the top-left corner of the *entire frame* +void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y) +{ + [tl->frame draw:c textLayout:tl at:x y:y]; +} + +// TODO document that the width and height of a layout is not necessarily the sum of the widths and heights of its constituent lines +// TODO width doesn't include trailing whitespace... +// TODO figure out how paragraph spacing should play into this +// TODO standardize and document the behavior of this on an empty layout +void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height) +{ + // TODO explain this, given the above + [tl->frame returnWidth:width height:NULL]; + [tl->forLines returnWidth:NULL height:height]; } diff --git a/darwin/editablecombo.m b/darwin/editablecombo.m new file mode 100644 index 00000000..7b1a1134 --- /dev/null +++ b/darwin/editablecombo.m @@ -0,0 +1,186 @@ +// 14 august 2015 +#import "uipriv_darwin.h" + +// So why did I split uiCombobox into uiCombobox and uiEditableCombobox? Here's (90% of the; the other 10% is GTK+ events) answer: +// When you type a value into a NSComboBox that just happens to be in the list, it will autoselect that item! +// I can't seem to find a workaround. +// Fortunately, there's other weird behaviors that made this split worth it. +// And besides, selected items make little sense with editable comboboxes... you either separate or combine them with the text entry :V + +// NSComboBoxes have no intrinsic width; we'll use the default Interface Builder width for them. +#define comboboxWidth 96 + +@interface libui_intrinsicWidthNSComboBox : NSComboBox +@end + +@implementation libui_intrinsicWidthNSComboBox + +- (NSSize)intrinsicContentSize +{ + NSSize s; + + s = [super intrinsicContentSize]; + s.width = comboboxWidth; + return s; +} + +@end + +struct uiEditableCombobox { + uiDarwinControl c; + NSComboBox *cb; + void (*onChanged)(uiEditableCombobox *, void *); + void *onChangedData; +}; + +@interface editableComboboxDelegateClass : NSObject { + uiprivMap *comboboxes; +} +- (void)controlTextDidChange:(NSNotification *)note; +- (void)comboBoxSelectionDidChange:(NSNotification *)note; +- (void)registerCombobox:(uiEditableCombobox *)c; +- (void)unregisterCombobox:(uiEditableCombobox *)c; +@end + +@implementation editableComboboxDelegateClass + +- (id)init +{ + self = [super init]; + if (self) + self->comboboxes = uiprivNewMap(); + return self; +} + +- (void)dealloc +{ + uiprivMapDestroy(self->comboboxes); + [super dealloc]; +} + +- (void)controlTextDidChange:(NSNotification *)note +{ + uiEditableCombobox *c; + + // TODO normalize the cast styles in these calls + c = uiEditableCombobox(uiprivMapGet(self->comboboxes, [note object])); + (*(c->onChanged))(c, c->onChangedData); +} + +// the above doesn't handle when an item is selected; this will +- (void)comboBoxSelectionDidChange:(NSNotification *)note +{ + // except this is sent BEFORE the entry is changed, and that doesn't send the above, so + // this is via http://stackoverflow.com/a/21059819/3408572 - it avoids the need to manage selected items + // this still isn't perfect — I get residual changes to the same value while navigating the list — but it's good enough + [self performSelector:@selector(controlTextDidChange:) + withObject:note + afterDelay:0]; +} + +- (void)registerCombobox:(uiEditableCombobox *)c +{ + uiprivMapSet(self->comboboxes, c->cb, c); + [c->cb setDelegate:self]; +} + +- (void)unregisterCombobox:(uiEditableCombobox *)c +{ + [c->cb setDelegate:nil]; + uiprivMapDelete(self->comboboxes, c->cb); +} + +@end + +static editableComboboxDelegateClass *comboboxDelegate = nil; + +uiDarwinControlAllDefaultsExceptDestroy(uiEditableCombobox, cb) + +static void uiEditableComboboxDestroy(uiControl *cc) +{ + uiEditableCombobox *c = uiEditableCombobox(cc); + + [comboboxDelegate unregisterCombobox:c]; + [c->cb release]; + uiFreeControl(uiControl(c)); +} + +void uiEditableComboboxAppend(uiEditableCombobox *c, const char *text) +{ + [c->cb addItemWithObjectValue:uiprivToNSString(text)]; +} + +char *uiEditableComboboxText(uiEditableCombobox *c) +{ + return uiDarwinNSStringToText([c->cb stringValue]); +} + +void uiEditableComboboxSetText(uiEditableCombobox *c, const char *text) +{ + NSString *t; + + t = uiprivToNSString(text); + [c->cb setStringValue:t]; + // yes, let's imitate the behavior that caused uiEditableCombobox to be separate in the first place! + // just to avoid confusion when users see an option in the list in the text field but not selected in the list + [c->cb selectItemWithObjectValue:t]; +} + +#if 0 +// LONGTERM +void uiEditableComboboxSetSelected(uiEditableCombobox *c, int n) +{ + if (c->editable) { + // see https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ComboBox/Tasks/SettingComboBoxValue.html#//apple_ref/doc/uid/20000256 + id delegate; + + // this triggers the delegate; turn it off for now + delegate = [c->cb delegate]; + [c->cb setDelegate:nil]; + + // this seems to work fine for -1 too + [c->cb selectItemAtIndex:n]; + if (n == -1) + [c->cb setObjectValue:@""]; + else + [c->cb setObjectValue:[c->cb objectValueOfSelectedItem]]; + + [c->cb setDelegate:delegate]; + return; + } + [c->pb selectItemAtIndex:n]; +} +#endif + +void uiEditableComboboxOnChanged(uiEditableCombobox *c, void (*f)(uiEditableCombobox *c, void *data), void *data) +{ + c->onChanged = f; + c->onChangedData = data; +} + +static void defaultOnChanged(uiEditableCombobox *c, void *data) +{ + // do nothing +} + +uiEditableCombobox *uiNewEditableCombobox(void) +{ + uiEditableCombobox *c; + + uiDarwinNewControl(uiEditableCombobox, c); + + c->cb = [[libui_intrinsicWidthNSComboBox alloc] initWithFrame:NSZeroRect]; + [c->cb setUsesDataSource:NO]; + [c->cb setButtonBordered:YES]; + [c->cb setCompletes:NO]; + uiDarwinSetControlFont(c->cb, NSRegularControlSize); + + if (comboboxDelegate == nil) { + comboboxDelegate = [[editableComboboxDelegateClass new] autorelease]; + [uiprivDelegates addObject:comboboxDelegate]; + } + [comboboxDelegate registerCombobox:c]; + uiEditableComboboxOnChanged(c, defaultOnChanged, NULL); + + return c; +} diff --git a/darwin/entry.m b/darwin/entry.m index 43f450d4..99630264 100644 --- a/darwin/entry.m +++ b/darwin/entry.m @@ -20,6 +20,40 @@ @end +// TODO does this have one on its own? +@interface libui_intrinsicWidthNSSecureTextField : NSSecureTextField +@end + +@implementation libui_intrinsicWidthNSSecureTextField + +- (NSSize)intrinsicContentSize +{ + NSSize s; + + s = [super intrinsicContentSize]; + s.width = textfieldWidth; + return s; +} + +@end + +// TODO does this have one on its own? +@interface libui_intrinsicWidthNSSearchField : NSSearchField +@end + +@implementation libui_intrinsicWidthNSSearchField + +- (NSSize)intrinsicContentSize +{ + NSSize s; + + s = [super intrinsicContentSize]; + s.width = textfieldWidth; + return s; +} + +@end + struct uiEntry { uiDarwinControl c; NSTextField *textfield; @@ -27,10 +61,16 @@ struct uiEntry { void *onChangedData; }; +static BOOL isSearchField(NSTextField *tf) +{ + return [tf isKindOfClass:[NSSearchField class]]; +} + @interface entryDelegateClass : NSObject { - struct mapTable *entries; + uiprivMap *entries; } - (void)controlTextDidChange:(NSNotification *)note; +- (IBAction)onSearch:(id)sender; - (void)registerEntry:(uiEntry *)e; - (void)unregisterEntry:(uiEntry *)e; @end @@ -41,34 +81,46 @@ struct uiEntry { { self = [super init]; if (self) - self->entries = newMap(); + self->entries = uiprivNewMap(); return self; } - (void)dealloc { - mapDestroy(self->entries); + uiprivMapDestroy(self->entries); [super dealloc]; } - (void)controlTextDidChange:(NSNotification *)note +{ + [self onSearch:[note object]]; +} + +- (IBAction)onSearch:(id)sender { uiEntry *e; - e = (uiEntry *) mapGet(self->entries, [note object]); + e = (uiEntry *) uiprivMapGet(self->entries, sender); (*(e->onChanged))(e, e->onChangedData); } - (void)registerEntry:(uiEntry *)e { - mapSet(self->entries, e->textfield, e); - [e->textfield setDelegate:self]; + uiprivMapSet(self->entries, e->textfield, e); + if (isSearchField(e->textfield)) { + [e->textfield setTarget:self]; + [e->textfield setAction:@selector(onSearch:)]; + } else + [e->textfield setDelegate:self]; } - (void)unregisterEntry:(uiEntry *)e { - [e->textfield setDelegate:nil]; - mapDelete(self->entries, e->textfield); + if (isSearchField(e->textfield)) + [e->textfield setTarget:nil]; + else + [e->textfield setDelegate:nil]; + uiprivMapDelete(self->entries, e->textfield); } @end @@ -93,7 +145,7 @@ char *uiEntryText(uiEntry *e) void uiEntrySetText(uiEntry *e, const char *text) { - [e->textfield setStringValue:toNSString(text)]; + [e->textfield setStringValue:uiprivToNSString(text)]; // don't queue the control for resize; entry sizes are independent of their contents } @@ -124,7 +176,7 @@ static void defaultOnChanged(uiEntry *e, void *data) } // these are based on interface builder defaults; my comments in the old code weren't very good so I don't really know what talked about what, sorry :/ -void finishNewTextField(NSTextField *t, BOOL isEntry) +void uiprivFinishNewTextField(NSTextField *t, BOOL isEntry) { uiDarwinSetControlFont(t, NSRegularControlSize); @@ -139,30 +191,61 @@ void finishNewTextField(NSTextField *t, BOOL isEntry) [[t cell] setScrollable:YES]; } -NSTextField *newEditableTextField(void) +static NSTextField *realNewEditableTextField(Class class) { NSTextField *tf; - tf = [[libui_intrinsicWidthNSTextField alloc] initWithFrame:NSZeroRect]; + tf = [[class alloc] initWithFrame:NSZeroRect]; [tf setSelectable:YES]; // otherwise the setting is masked by the editable default of YES - finishNewTextField(tf, YES); + uiprivFinishNewTextField(tf, YES); return tf; } -uiEntry *uiNewEntry(void) +NSTextField *uiprivNewEditableTextField(void) +{ + return realNewEditableTextField([libui_intrinsicWidthNSTextField class]); +} + +static uiEntry *finishNewEntry(Class class) { uiEntry *e; uiDarwinNewControl(uiEntry, e); - e->textfield = newEditableTextField(); + e->textfield = realNewEditableTextField(class); if (entryDelegate == nil) { - entryDelegate = [entryDelegateClass new]; - [delegates addObject:entryDelegate]; + entryDelegate = [[entryDelegateClass new] autorelease]; + [uiprivDelegates addObject:entryDelegate]; } [entryDelegate registerEntry:e]; uiEntryOnChanged(e, defaultOnChanged, NULL); return e; } + +uiEntry *uiNewEntry(void) +{ + return finishNewEntry([libui_intrinsicWidthNSTextField class]); +} + +uiEntry *uiNewPasswordEntry(void) +{ + return finishNewEntry([libui_intrinsicWidthNSSecureTextField class]); +} + +uiEntry *uiNewSearchEntry(void) +{ + uiEntry *e; + NSSearchField *s; + + e = finishNewEntry([libui_intrinsicWidthNSSearchField class]); + s = (NSSearchField *) (e->textfield); + // TODO these are only on 10.10 +// [s setSendsSearchStringImmediately:NO]; +// [s setSendsWholeSearchString:NO]; + [s setBordered:NO]; + [s setBezelStyle:NSTextFieldRoundedBezel]; + [s setBezeled:YES]; + return e; +} diff --git a/darwin/fontbutton.m b/darwin/fontbutton.m index 3a61c823..0ef57d81 100644 --- a/darwin/fontbutton.m +++ b/darwin/fontbutton.m @@ -1,7 +1,8 @@ // 14 april 2016 #import "uipriv_darwin.h" +#import "attrstr.h" -@interface fontButton : NSButton { +@interface uiprivFontButton : NSButton { uiFontButton *libui_b; NSFont *libui_font; } @@ -11,20 +12,20 @@ - (void)activateFontButton; - (void)deactivateFontButton:(BOOL)activatingAnother; - (void)deactivateOnClose:(NSNotification *)note; -- (uiDrawTextFont *)libuiFont; +- (void)getfontdesc:(uiFontDescriptor *)uidesc; @end // only one may be active at one time -static fontButton *activeFontButton = nil; +static uiprivFontButton *activeFontButton = nil; struct uiFontButton { uiDarwinControl c; - fontButton *button; + uiprivFontButton *button; void (*onChanged)(uiFontButton *, void *); void *onChangedData; }; -@implementation fontButton +@implementation uiprivFontButton - (id)initWithFrame:(NSRect)frame libuiFontButton:(uiFontButton *)b { @@ -123,10 +124,10 @@ struct uiFontButton { fm = (NSFontManager *) sender; old = self->libui_font; self->libui_font = [sender convertFont:self->libui_font]; - // TODO do we get it back retained? - // TODO is it even retained when we get it, regardless of value? - if (self->libui_font != old) - [old release]; + // do this even if it returns the same; we don't own anything that isn't from a new or alloc/init + [self->libui_font retain]; + // do this second just in case + [old release]; [self updateFontButtonLabel]; (*(b->onChanged))(b, b->onChangedData); } @@ -138,9 +139,16 @@ struct uiFontButton { NSFontPanelCollectionModeMask; } -- (uiDrawTextFont *)libuiFont +- (void)getfontdesc:(uiFontDescriptor *)uidesc { - return mkTextFontFromNSFont(self->libui_font); + CTFontRef ctfont; + CTFontDescriptorRef ctdesc; + + ctfont = (CTFontRef) (self->libui_font); + ctdesc = CTFontCopyFontDescriptor(ctfont); + uiprivFontDescriptorFromCTFontDescriptor(ctdesc, uidesc); + CFRelease(ctdesc); + uidesc->Size = CTFontGetSize(ctfont); } @end @@ -149,16 +157,16 @@ uiDarwinControlAllDefaults(uiFontButton, button) // we do not want font change events to be sent to any controls other than the font buttons // see main.m for more details -BOOL fontButtonInhibitSendAction(SEL sel, id from, id to) +BOOL uiprivFontButtonInhibitSendAction(SEL sel, id from, id to) { if (sel != @selector(changeFont:)) return NO; - return ![to isKindOfClass:[fontButton class]]; + return ![to isKindOfClass:[uiprivFontButton class]]; } // we do not want NSFontPanelValidation messages to be sent to any controls other than the font buttons when a font button is active // see main.m for more details -BOOL fontButtonOverrideTargetForAction(SEL sel, id from, id to, id *override) +BOOL uiprivFontButtonOverrideTargetForAction(SEL sel, id from, id to, id *override) { if (activeFontButton == nil) return NO; @@ -168,14 +176,33 @@ BOOL fontButtonOverrideTargetForAction(SEL sel, id from, id to, id *override) return YES; } +// we also don't want the panel to be usable when there's a dialog running; see stddialogs.m for more details on that +// unfortunately the panel seems to ignore -setWorksWhenModal: so we'll have to do things ourselves +@interface uiprivNonModalFontPanel : NSFontPanel +@end + +@implementation uiprivNonModalFontPanel + +- (BOOL)worksWhenModal +{ + return NO; +} + +@end + +void uiprivSetupFontPanel(void) +{ + [NSFontManager setFontPanelFactory:[uiprivNonModalFontPanel class]]; +} + static void defaultOnChanged(uiFontButton *b, void *data) { // do nothing } -uiDrawTextFont *uiFontButtonFont(uiFontButton *b) +void uiFontButtonFont(uiFontButton *b, uiFontDescriptor *desc) { - return [b->button libuiFont]; + [b->button getfontdesc:desc]; } void uiFontButtonOnChanged(uiFontButton *b, void (*f)(uiFontButton *, void *), void *data) @@ -190,10 +217,16 @@ uiFontButton *uiNewFontButton(void) uiDarwinNewControl(uiFontButton, b); - b->button = [[fontButton alloc] initWithFrame:NSZeroRect libuiFontButton:b]; + b->button = [[uiprivFontButton alloc] initWithFrame:NSZeroRect libuiFontButton:b]; uiDarwinSetControlFont(b->button, NSRegularControlSize); uiFontButtonOnChanged(b, defaultOnChanged, NULL); return b; } + +void uiFreeFontButtonFont(uiFontDescriptor *desc) +{ + // TODO ensure this is synchronized with fontmatch.m + uiFreeText((char *) (desc->Family)); +} diff --git a/darwin/fontmatch.m b/darwin/fontmatch.m new file mode 100644 index 00000000..6daa1e8d --- /dev/null +++ b/darwin/fontmatch.m @@ -0,0 +1,494 @@ +// 3 january 2017 +#import "uipriv_darwin.h" +#import "attrstr.h" + +// TODOs: +// - switching from Skia to a non-fvar-based font crashes because the CTFontDescriptorRef we get has an empty variation dictionary for some reason... +// - Futura causes the Courier New in the drawtext example to be bold for some reason... + +// Core Text exposes font style info in two forms: +// - Fonts with a QuickDraw GX font variation (fvar) table, a feature +// adopted by OpenType, expose variations directly. +// - All other fonts have Core Text normalize the font style info +// into a traits dictionary. +// Of course this setup doesn't come without its hiccups and +// glitches. In particular, not only are the exact rules not well +// defined, but also font matching doesn't work as we want it to +// (exactly how varies based on the way the style info is exposed). +// So we'll have to implement style matching ourselves. +// We can use Core Text's matching to get a complete list of +// *possible* matches, and then we can filter out the ones we don't +// want ourselves. +// +// To make things easier for us, we'll match by converting Core +// Text's values back into libui values. This allows us to also use the +// normalization code for filling in uiFontDescriptors from +// Core Text fonts and font descriptors. +// +// Style matching needs to be done early in the font loading process; +// in particular, we have to do this before adding any features, +// because the descriptors returned by Core Text's own font +// matching won't have any. + +@implementation uiprivFontStyleData + +- (id)initWithFont:(CTFontRef)f +{ + self = [super init]; + if (self) { + self->font = f; + CFRetain(self->font); + self->desc = CTFontCopyFontDescriptor(self->font); + if (![self prepare]) { + [self release]; + return nil; + } + } + return self; +} + +- (id)initWithDescriptor:(CTFontDescriptorRef)d +{ + self = [super init]; + if (self) { + self->font = NULL; + self->desc = d; + CFRetain(self->desc); + if (![self prepare]) { + [self release]; + return nil; + } + } + return self; +} + +- (void)dealloc +{ +#define REL(x) if (x != NULL) { CFRelease(x); x = NULL; } + REL(self->variationAxes); + REL(self->familyName); + REL(self->preferredFamilyName); + REL(self->fullName); + REL(self->subFamilyName); + REL(self->preferredSubFamilyName); + REL(self->postScriptName); + REL(self->variation); + REL(self->styleName); + REL(self->traits); + CFRelease(self->desc); + REL(self->font); + [super dealloc]; +} + +- (BOOL)prepare +{ + CFNumberRef num; + Boolean success; + + self->traits = NULL; + self->symbolic = 0; + self->weight = 0; + self->width = 0; + self->didStyleName = NO; + self->styleName = NULL; + self->didVariation = NO; + self->variation = NULL; + self->hasRegistrationScope = NO; + self->registrationScope = 0; + self->didPostScriptName = NO; + self->postScriptName = NULL; + self->fontFormat = 0; + self->didPreferredSubFamilyName = NO; + self->preferredSubFamilyName = NULL; + self->didSubFamilyName = NO; + self->subFamilyName = NULL; + self->didFullName = NO; + self->fullName = NULL; + self->didPreferredFamilyName = NO; + self->preferredFamilyName = NULL; + self->didFamilyName = NO; + self->familyName = NULL; + self->didVariationAxes = NO; + self->variationAxes = NULL; + + self->traits = (CFDictionaryRef) CTFontDescriptorCopyAttribute(self->desc, kCTFontTraitsAttribute); + if (self->traits == NULL) + return NO; + + num = (CFNumberRef) CFDictionaryGetValue(self->traits, kCTFontSymbolicTrait); + if (num == NULL) + return NO; + if (CFNumberGetValue(num, kCFNumberSInt32Type, &(self->symbolic)) == false) + return NO; + + num = (CFNumberRef) CFDictionaryGetValue(self->traits, kCTFontWeightTrait); + if (num == NULL) + return NO; + if (CFNumberGetValue(num, kCFNumberDoubleType, &(self->weight)) == false) + return NO; + + num = (CFNumberRef) CFDictionaryGetValue(self->traits, kCTFontWidthTrait); + if (num == NULL) + return NO; + if (CFNumberGetValue(num, kCFNumberDoubleType, &(self->width)) == false) + return NO; + + // do these now for the sake of error checking + num = (CFNumberRef) CTFontDescriptorCopyAttribute(desc, kCTFontRegistrationScopeAttribute); + self->hasRegistrationScope = num != NULL; + if (self->hasRegistrationScope) { + success = CFNumberGetValue(num, kCFNumberSInt32Type, &(self->registrationScope)); + CFRelease(num); + if (success == false) + return NO; + } + + num = (CFNumberRef) CTFontDescriptorCopyAttribute(self->desc, kCTFontFormatAttribute); + if (num == NULL) + return NO; + success = CFNumberGetValue(num, kCFNumberSInt32Type, &(self->fontFormat)); + CFRelease(num); + if (success == false) + return NO; + + return YES; +} + +- (void)ensureFont +{ + if (self->font != NULL) + return; + self->font = CTFontCreateWithFontDescriptor(self->desc, 0.0, NULL); +} + +- (CTFontSymbolicTraits)symbolicTraits +{ + return self->symbolic; +} + +- (double)weight +{ + return self->weight; +} + +- (double)width +{ + return self->width; +} + +- (CFStringRef)styleName +{ + if (!self->didStyleName) { + self->didStyleName = YES; + self->styleName = (CFStringRef) CTFontDescriptorCopyAttribute(self->desc, kCTFontStyleNameAttribute); + // The code we use this for (guessItalicOblique() below) checks if this is NULL or not, so we're good. + } + return self->styleName; +} + +- (CFDictionaryRef)variation +{ + if (!self->didVariation) { + self->didVariation = YES; + self->variation = (CFDictionaryRef) CTFontDescriptorCopyAttribute(self->desc, kCTFontVariationAttribute); + // This being NULL is used to determine whether a font uses variations at all, so we don't need to worry now. + } + return self->variation; +} + +- (BOOL)hasRegistrationScope +{ + return self->hasRegistrationScope; +} + +- (CTFontManagerScope)registrationScope +{ + return self->registrationScope; +} + +- (CFStringRef)postScriptName +{ + if (!self->didPostScriptName) { + self->didPostScriptName = YES; + [self ensureFont]; + self->postScriptName = CTFontCopyPostScriptName(self->font); + } + return self->postScriptName; +} + +- (CFDataRef)table:(CTFontTableTag)tag +{ + [self ensureFont]; + return CTFontCopyTable(self->font, tag, kCTFontTableOptionNoOptions); +} + +- (CTFontFormat)fontFormat +{ + return self->fontFormat; +} + +// We don't need to worry if this or any of the functions that use it return NULL, because the code that uses it (libFontRegistry.dylib bug workarounds in fonttraits.m) checks for NULL. +- (CFStringRef)fontName:(CFStringRef)key +{ + [self ensureFont]; + return CTFontCopyName(self->font, key); +} + +#define FONTNAME(sel, did, var, key) \ + - (CFStringRef)sel \ + { \ + if (!did) { \ + did = YES; \ + var = [self fontName:key]; \ + } \ + return var; \ + } +FONTNAME(preferredSubFamilyName, + self->didPreferredSubFamilyName, + self->preferredSubFamilyName, + uiprivUNDOC_kCTFontPreferredSubFamilyNameKey) +FONTNAME(subFamilyName, + self->didSubFamilyName, + self->subFamilyName, + kCTFontSubFamilyNameKey) +FONTNAME(fullName, + self->didFullName, + self->fullName, + kCTFontFullNameKey) +FONTNAME(preferredFamilyName, + self->didPreferredFamilyName, + self->preferredFamilyName, + uiprivUNDOC_kCTFontPreferredFamilyNameKey) +FONTNAME(familyName, + self->didFamilyName, + self->familyName, + kCTFontFamilyNameKey) + +- (CFArrayRef)variationAxes +{ + if (!self->didVariationAxes) { + self->didVariationAxes = YES; + [self ensureFont]; + self->variationAxes = CTFontCopyVariationAxes(self->font); + // We don't care about the return value because we call this only on fonts that we know have variations anyway. + } + return self->variationAxes; +} + +@end + +struct closeness { + CFIndex index; + uiTextWeight weight; + double italic; + uiTextStretch stretch; + double distance; +}; + +// remember that in closeness, 0 means exact +// in this case, since we define the range, we use 0.5 to mean "close enough" (oblique for italic and italic for oblique) and 1 to mean "not a match" +static const double italicClosenessNormal[] = { 0, 1, 1 }; +static const double italicClosenessOblique[] = { 1, 0, 0.5 }; +static const double italicClosenessItalic[] = { 1, 0.5, 0 }; +static const double *italicClosenesses[] = { + [uiTextItalicNormal] = italicClosenessNormal, + [uiTextItalicOblique] = italicClosenessOblique, + [uiTextItalicItalic] = italicClosenessItalic, +}; + +// Core Text doesn't seem to differentiate between Italic and Oblique. +// Pango's Core Text code just does a g_strrstr() (backwards case-sensitive search) for "Oblique" in the font's style name (see https://git.gnome.org/browse/pango/tree/pango/pangocoretext-fontmap.c); let's do that too I guess +static uiTextItalic guessItalicOblique(uiprivFontStyleData *d) +{ + CFStringRef styleName; + BOOL isOblique; + + isOblique = NO; // default value + styleName = [d styleName]; + if (styleName != NULL) { + CFRange range; + + range = CFStringFind(styleName, CFSTR("Oblique"), kCFCompareBackwards); + if (range.location != kCFNotFound) + isOblique = YES; + } + if (isOblique) + return uiTextItalicOblique; + return uiTextItalicItalic; +} + +// Italics are hard because Core Text does NOT distinguish between italic and oblique. +// All Core Text provides is a slant value and the italic bit of the symbolic traits mask. +// However, Core Text does seem to guarantee (from experimentation; see below) that the slant will be nonzero if and only if the italic bit is set, so we don't need to use the slant value. +// Core Text also seems to guarantee that if a font lists itself as Italic or Oblique by name (font subfamily name, font style name, whatever), it will also have that bit set, so testing this bit does cover all fonts that name themselves as Italic and Oblique. (Again, this is from the below experimentation.) +// TODO there is still one catch that might matter from a user's POV: the reverse is not true — the italic bit can be set even if the style of the font face/subfamily/style isn't named as Italic (for example, script typefaces like Adobe's Palace Script MT Std); I don't know what to do about this... I know how to start: find a script font that has an italic form (Adobe's Palace Script MT Std does not; only Regular and Semibold) +static void setItalic(uiprivFontStyleData *d, uiFontDescriptor *out) +{ + out->Italic = uiTextItalicNormal; + if (([d symbolicTraits] & kCTFontItalicTrait) != 0) + out->Italic = guessItalicOblique(d); +} + +static void fillDescStyleFields(uiprivFontStyleData *d, NSDictionary *axisDict, uiFontDescriptor *out) +{ + setItalic(d, out); + if (axisDict != nil) + uiprivProcessFontVariation(d, axisDict, out); + else + uiprivProcessFontTraits(d, out); +} + +static CTFontDescriptorRef matchStyle(CTFontDescriptorRef against, uiFontDescriptor *styles) +{ + CFArrayRef matching; + CFIndex i, n; + struct closeness *closeness; + CTFontDescriptorRef current; + CTFontDescriptorRef out; + uiprivFontStyleData *d; + NSDictionary *axisDict; + + matching = CTFontDescriptorCreateMatchingFontDescriptors(against, NULL); + if (matching == NULL) + // no matches; give the original back and hope for the best + return against; + n = CFArrayGetCount(matching); + if (n == 0) { + // likewise + CFRelease(matching); + return against; + } + + current = (CTFontDescriptorRef) CFArrayGetValueAtIndex(matching, 0); + d = [[uiprivFontStyleData alloc] initWithDescriptor:current]; + axisDict = nil; + if ([d variation] != NULL) + axisDict = uiprivMakeVariationAxisDict([d variationAxes], [d table:kCTFontTableAvar]); + + closeness = (struct closeness *) uiprivAlloc(n * sizeof (struct closeness), "struct closeness[]"); + for (i = 0; i < n; i++) { + uiFontDescriptor fields; + + closeness[i].index = i; + if (i != 0) { + current = (CTFontDescriptorRef) CFArrayGetValueAtIndex(matching, i); + d = [[uiprivFontStyleData alloc] initWithDescriptor:current]; + } + fillDescStyleFields(d, axisDict, &fields); + closeness[i].weight = fields.Weight - styles->Weight; + closeness[i].italic = italicClosenesses[styles->Italic][fields.Italic]; + closeness[i].stretch = fields.Stretch - styles->Stretch; + [d release]; + } + + // now figure out the 3-space difference between the three and sort by that + // TODO merge this loop with the previous loop? + for (i = 0; i < n; i++) { + double weight, italic, stretch; + + weight = (double) (closeness[i].weight); + weight *= weight; + italic = closeness[i].italic; + italic *= italic; + stretch = (double) (closeness[i].stretch); + stretch *= stretch; + closeness[i].distance = sqrt(weight + italic + stretch); + } + qsort_b(closeness, n, sizeof (struct closeness), ^(const void *aa, const void *bb) { + const struct closeness *a = (const struct closeness *) aa; + const struct closeness *b = (const struct closeness *) bb; + + // via http://www.gnu.org/software/libc/manual/html_node/Comparison-Functions.html#Comparison-Functions + // LONGTERM is this really the best way? isn't it the same as if (*a < *b) return -1; if (*a > *b) return 1; return 0; ? + return (a->distance > b->distance) - (a->distance < b->distance); + }); + // and the first element of the sorted array is what we want + out = CFArrayGetValueAtIndex(matching, closeness[0].index); + CFRetain(out); // get rule + + // release everything + if (axisDict != nil) + [axisDict release]; + uiprivFree(closeness); + CFRelease(matching); + // and release the original descriptor since we no longer need it + CFRelease(against); + + return out; +} + +CTFontDescriptorRef uiprivFontDescriptorToCTFontDescriptor(uiFontDescriptor *fd) +{ + CFMutableDictionaryRef attrs; + CFStringRef cffamily; + CFNumberRef cfsize; + CTFontDescriptorRef basedesc; + + attrs = CFDictionaryCreateMutable(NULL, 2, + // TODO are these correct? + &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + if (attrs == NULL) { + // TODO + } + cffamily = CFStringCreateWithCString(NULL, fd->Family, kCFStringEncodingUTF8); + if (cffamily == NULL) { + // TODO + } + CFDictionaryAddValue(attrs, kCTFontFamilyNameAttribute, cffamily); + CFRelease(cffamily); + cfsize = CFNumberCreate(NULL, kCFNumberDoubleType, &(fd->Size)); + CFDictionaryAddValue(attrs, kCTFontSizeAttribute, cfsize); + CFRelease(cfsize); + + basedesc = CTFontDescriptorCreateWithAttributes(attrs); + CFRelease(attrs); // TODO correct? + return matchStyle(basedesc, fd); +} + +// fortunately features that aren't supported are simply ignored, so we can copy them all in +CTFontDescriptorRef uiprivCTFontDescriptorAppendFeatures(CTFontDescriptorRef desc, const uiOpenTypeFeatures *otf) +{ + CTFontDescriptorRef new; + CFArrayRef featuresArray; + CFDictionaryRef attrs; + const void *keys[1], *values[1]; + + featuresArray = uiprivOpenTypeFeaturesToCTFeatures(otf); + keys[0] = kCTFontFeatureSettingsAttribute; + values[0] = featuresArray; + attrs = CFDictionaryCreate(NULL, + keys, values, 1, + // TODO are these correct? + &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CFRelease(featuresArray); + new = CTFontDescriptorCreateCopyWithAttributes(desc, attrs); + CFRelease(attrs); + CFRelease(desc); + return new; +} + +void uiprivFontDescriptorFromCTFontDescriptor(CTFontDescriptorRef ctdesc, uiFontDescriptor *uidesc) +{ + CFStringRef cffamily; + uiprivFontStyleData *d; + NSDictionary *axisDict; + + cffamily = (CFStringRef) CTFontDescriptorCopyAttribute(ctdesc, kCTFontFamilyNameAttribute); + if (cffamily == NULL) { + // TODO + } + // TODO normalize this by adding a uiDarwinCFStringToText() + uidesc->Family = uiDarwinNSStringToText((NSString *) cffamily); + CFRelease(cffamily); + + d = [[uiprivFontStyleData alloc] initWithDescriptor:ctdesc]; + axisDict = nil; + if ([d variation] != NULL) + axisDict = uiprivMakeVariationAxisDict([d variationAxes], [d table:kCTFontTableAvar]); + fillDescStyleFields(d, axisDict, uidesc); + if (axisDict != nil) + [axisDict release]; + [d release]; +} diff --git a/darwin/fonttraits.m b/darwin/fonttraits.m new file mode 100644 index 00000000..a51492c9 --- /dev/null +++ b/darwin/fonttraits.m @@ -0,0 +1,223 @@ +// 1 november 2017 +#import "uipriv_darwin.h" +#import "attrstr.h" + +// This is the part of the font style matching and normalization code +// that handles fonts that use a traits dictionary. +// +// Matching stupidity: Core Text requires an **exact match for the +// entire traits dictionary**, otherwise it will **drop ALL the traits**. +// +// Normalization stupidity: Core Text uses its own scaled values for +// weight and width, but the values are different if the font is not +// registered and if said font is TrueType or OpenType. The values +// for all other cases do have some named constants starting with +// OS X 10.11, but even these aren't very consistent in practice. +// +// Of course, none of this is documented anywhere, so I had to do +// both trial-and-error AND reverse engineering to figure out what's +// what. We'll just convert Core Text's values into libui constants +// and use those for matching. + +static BOOL fontRegistered(uiprivFontStyleData *d) +{ + if (![d hasRegistrationScope]) + // header says this should be treated as the same as not registered + return NO; + // examination of Core Text shows this is accurate + return [d registrationScope] != kCTFontManagerScopeNone; +} + +// Core Text does (usWidthClass / 10) - 0.5 here. +// This roughly maps to our values with increments of 0.1, except for the fact 0 and 10 are allowed by Core Text, despite being banned by TrueType and OpenType themselves. +// We'll just treat them as identical to 1 and 9, respectively. +static const uiTextStretch os2WidthsToStretches[] = { + uiTextStretchUltraCondensed, + uiTextStretchUltraCondensed, + uiTextStretchExtraCondensed, + uiTextStretchCondensed, + uiTextStretchSemiCondensed, + uiTextStretchNormal, + uiTextStretchSemiExpanded, + uiTextStretchExpanded, + uiTextStretchExtraExpanded, + uiTextStretchUltraExpanded, + uiTextStretchUltraExpanded, +}; + +static const CFStringRef exceptions[] = { + CFSTR("LucidaGrande"), + CFSTR(".LucidaGrandeUI"), + CFSTR("STHeiti"), + CFSTR("STXihei"), + CFSTR("TimesNewRomanPSMT"), + NULL, +}; + +static void trySecondaryOS2Values(uiprivFontStyleData *d, uiFontDescriptor *out, BOOL *hasWeight, BOOL *hasWidth) +{ + CFDataRef os2; + uint16_t usWeightClass, usWidthClass; + CFStringRef psname; + const CFStringRef *ex; + + *hasWeight = NO; + *hasWidth = NO; + + // only applies to unregistered fonts + if (fontRegistered(d)) + return; + + os2 = [d table:kCTFontTableOS2]; + if (os2 == NULL) + // no OS2 table, so no secondary values + return; + + if (CFDataGetLength(os2) > 77) { + const UInt8 *b; + + b = CFDataGetBytePtr(os2); + + usWeightClass = ((uint16_t) (b[4])) << 8; + usWeightClass |= (uint16_t) (b[5]); + if (usWeightClass <= 1000) { + if (usWeightClass < 11) + usWeightClass *= 100; + *hasWeight = YES; + } + + usWidthClass = ((uint16_t) (b[6])) << 8; + usWidthClass |= (uint16_t) (b[7]); + if (usWidthClass <= 10) + *hasWidth = YES; + } else { + usWeightClass = 0; + *hasWeight = YES; + + usWidthClass = 0; + *hasWidth = YES; + } + if (*hasWeight) + // we can just use this directly + out->Weight = usWeightClass; + if (*hasWidth) + out->Stretch = os2WidthsToStretches[usWidthClass]; + CFRelease(os2); + + // don't use secondary weights in the event of special predefined names + psname = [d postScriptName]; + for (ex = exceptions; *ex != NULL; ex++) + if (CFEqual(psname, *ex)) { + *hasWeight = NO; + break; + } +} + +static BOOL testTTFOTFSubfamilyName(CFStringRef name, CFStringRef want) +{ + CFRange range; + + if (name == NULL) + return NO; + range.location = 0; + range.length = CFStringGetLength(name); + return CFStringFindWithOptions(name, want, range, + (kCFCompareCaseInsensitive | kCFCompareBackwards | kCFCompareNonliteral), NULL) != false; +} + +static BOOL testTTFOTFSubfamilyNames(uiprivFontStyleData *d, CFStringRef want) +{ + switch ([d fontFormat]) { + case kCTFontFormatOpenTypePostScript: + case kCTFontFormatOpenTypeTrueType: + case kCTFontFormatTrueType: + break; + default: + return NO; + } + + if (testTTFOTFSubfamilyName([d preferredSubFamilyName], want)) + return YES; + if (testTTFOTFSubfamilyName([d subFamilyName], want)) + return YES; + if (testTTFOTFSubfamilyName([d fullName], want)) + return YES; + if (testTTFOTFSubfamilyName([d preferredFamilyName], want)) + return YES; + return testTTFOTFSubfamilyName([d familyName], want); +} + +// work around a bug in libFontRegistry.dylib +static BOOL shouldReallyBeThin(uiprivFontStyleData *d) +{ + return testTTFOTFSubfamilyNames(d, CFSTR("W1")); +} + +// work around a bug in libFontRegistry.dylib +static BOOL shouldReallyBeSemiCondensed(uiprivFontStyleData *d) +{ + return testTTFOTFSubfamilyNames(d, CFSTR("Semi Condensed")); +} + +void uiprivProcessFontTraits(uiprivFontStyleData *d, uiFontDescriptor *out) +{ + double weight, width; + BOOL hasWeight, hasWidth; + + hasWeight = NO; + hasWidth = NO; + trySecondaryOS2Values(d, out, &hasWeight, &hasWidth); + + weight = [d weight]; + width = [d width]; + + if (!hasWeight) + // TODO this scale is a bit lopsided + if (weight <= -0.7) + out->Weight = uiTextWeightThin; + else if (weight <= -0.5) + out->Weight = uiTextWeightUltraLight; + else if (weight <= -0.3) + out->Weight = uiTextWeightLight; + else if (weight <= -0.23) { + out->Weight = uiTextWeightBook; + if (shouldReallyBeThin(d)) + out->Weight = uiTextWeightThin; + } else if (weight <= 0.0) + out->Weight = uiTextWeightNormal; + else if (weight <= 0.23) + out->Weight = uiTextWeightMedium; + else if (weight <= 0.3) + out->Weight = uiTextWeightSemiBold; + else if (weight <= 0.4) + out->Weight = uiTextWeightBold; + else if (weight <= 0.5) + out->Weight = uiTextWeightUltraBold; + else if (weight <= 0.7) + out->Weight = uiTextWeightHeavy; + else + out->Weight = uiTextWeightUltraHeavy; + + if (!hasWidth) + // TODO this scale is a bit lopsided + if (width <= -0.7) { + out->Stretch = uiTextStretchUltraCondensed; + if (shouldReallyBeSemiCondensed(d)) + out->Stretch = uiTextStretchSemiCondensed; + } else if (width <= -0.5) + out->Stretch = uiTextStretchExtraCondensed; + else if (width <= -0.2) + out->Stretch = uiTextStretchCondensed; + else if (width <= -0.1) + out->Stretch = uiTextStretchSemiCondensed; + else if (width <= 0.0) + out->Stretch = uiTextStretchNormal; + else if (width <= 0.1) + out->Stretch = uiTextStretchSemiExpanded; + else if (width <= 0.2) + out->Stretch = uiTextStretchExpanded; + else if (width <= 0.6) + out->Stretch = uiTextStretchExtraExpanded; + else + out->Stretch = uiTextStretchUltraExpanded; +} diff --git a/darwin/fontvariation.m b/darwin/fontvariation.m new file mode 100644 index 00000000..6dfb3ab1 --- /dev/null +++ b/darwin/fontvariation.m @@ -0,0 +1,336 @@ +// 2 november 2017 +#import "uipriv_darwin.h" +#import "attrstr.h" + +// This is the part of the font style matching and normalization code +// that handles fonts that use the fvar table. +// +// Matching stupidity: Core Text **doesn't even bother** matching +// these, even if you tell it to do so explicitly. It'll always return +// all variations for a given font. +// +// Normalization stupidity: Core Text doesn't normalize the fvar +// table values for us, so we'll have to do it ourselves. Furthermore, +// Core Text doesn't provide an API for accessing the avar table, if +// any, so we must do so ourselves. (TODO does Core Text even +// follow the avar table if a font has it?) +// +// Thankfully, normalization is well-defined in both TrueType and +// OpenType and seems identical in both, so we can just normalize +// the values and then convert them linearly to libui values for +// matching. +// +// References: +// - https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6fvar.html +// - https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6avar.html +// - https://www.microsoft.com/typography/otspec/fvar.htm +// - https://www.microsoft.com/typography/otspec/otvaroverview.htm#CSN +// - https://www.microsoft.com/typography/otspec/otff.htm +// - https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6.html#Types +// - https://www.microsoft.com/typography/otspec/avar.htm + +// TODO Skia doesn't quite map correctly; notice what passes for condensed in the drawtext example +// TODO also investigate Marker Felt not working right in Thin and Wide modes (but that's probably the other file, putting it here just so I don't forget) + +#define fvarWeight 0x77676874 +#define fvarWidth 0x77647468 + +// TODO explain why these are signed +typedef int32_t fixed1616; +typedef int16_t fixed214; + +// note that Microsoft's data type list implies that *all* fixed-point types have the same format; it only gives specific examples for the 2.14 format, which confused me because I thought 16.16 worked differently, but eh +static fixed1616 doubleToFixed1616(double d) +{ + double ipart, fpart; + long flong; + int16_t i16; + uint32_t ret; + + fpart = modf(d, &ipart); + // fpart must be unsigned; modf() gives us fpart with the same sign as d (so we have to adjust both ipart and fpart appropriately) + if (fpart < 0) { + ipart -= 1; + fpart = 1 + fpart; + } + fpart *= 65536; + flong = lround(fpart); + i16 = (int16_t) ipart; + ret = (uint32_t) ((uint16_t) i16); + ret <<= 16; + ret |= (uint16_t) (flong & 0xFFFF); + return (fixed1616) ret; +} + +// see also https://stackoverflow.com/questions/8506317/fixed-point-unsigned-division-in-c and freetype's FT_DivFix() +// TODO figure out the specifics of freetype's more complex implementation that shifts b and juggles signs +static fixed1616 fixed1616Divide(fixed1616 a, fixed1616 b) +{ + uint32_t u; + int64_t a64; + + u = (uint32_t) a; + a64 = (int64_t) (((uint64_t) u) << 16); + return (fixed1616) (a64 / b); +} + +static fixed214 fixed1616ToFixed214(fixed1616 f) +{ + uint32_t t; + uint32_t topbit; + + t = (uint32_t) (f + 0x00000002); + topbit = t & 0x80000000; + t >>= 2; + if (topbit != 0) + t |= 0xC000000; + return (fixed214) (t & 0xFFFF); +} + +static double fixed214ToDouble(fixed214 f) +{ + uint16_t u; + double base; + double frac; + + u = (uint16_t) f; + switch ((u >> 14) & 0x3) { + case 0: + base = 0; + break; + case 1: + base = 1; + break; + case 2: + base = -2; + break; + case 3: + base = -1; + } + frac = ((double) (u & 0x3FFF)) / 16384; + return base + frac; +} + +static fixed1616 fixed214ToFixed1616(fixed214 f) +{ + int32_t t; + + t = (int32_t) ((int16_t) f); + t <<= 2; + return (fixed1616) (t - 0x00000002); +} + +static const fixed1616 fixed1616Negative1 = (int32_t) ((uint32_t) 0xFFFF0000); +static const fixed1616 fixed1616Zero = 0x00000000; +static const fixed1616 fixed1616Positive1 = 0x00010000; + +static fixed1616 fixed1616Normalize(fixed1616 val, fixed1616 min, fixed1616 max, fixed1616 def) +{ + if (val < min) + val = min; + if (val > max) + val = max; + if (val < def) + return fixed1616Divide(-(def - val), (def - min)); + if (val > def) + return fixed1616Divide((val - def), (max - def)); + return fixed1616Zero; +} + +static fixed214 normalizedTo214(fixed1616 val, const fixed1616 *avarMappings, size_t avarCount) +{ + if (val < fixed1616Negative1) + val = fixed1616Negative1; + if (val > fixed1616Positive1) + val = fixed1616Positive1; + if (avarCount != 0) { + size_t start, end; + fixed1616 startFrom, endFrom; + fixed1616 startTo, endTo; + + for (end = 0; end < avarCount; end += 2) { + endFrom = avarMappings[end]; + endTo = avarMappings[end + 1]; + if (endFrom >= val) + break; + } + if (endFrom == val) + val = endTo; + else { + start = end - 2; + startFrom = avarMappings[start]; + startTo = avarMappings[start + 1]; + val = fixed1616Divide((val - startFrom), (endFrom - startFrom)); + // TODO find a font with an avar table and make sure this works, or if we need to use special code for this too + val *= (endTo - startTo); + val += startTo; + } + } + return fixed1616ToFixed214(val); +} + +static fixed1616 *avarExtract(CFDataRef table, CFIndex index, size_t *n) +{ + const UInt8 *b; + size_t off; + size_t i, nEntries; + fixed1616 *entries; + fixed1616 *p; + + b = CFDataGetBytePtr(table); + off = 8; +#define nextuint16be() ((((uint16_t) (b[off])) << 8) | ((uint16_t) (b[off + 1]))) + for (; index > 0; index--) { + nEntries = (size_t) nextuint16be(); + off += 2; + off += 4 * nEntries; + } + nEntries = nextuint16be(); + *n = nEntries * 2; + entries = (fixed1616 *) uiprivAlloc(*n * sizeof (fixed1616), "fixed1616[]"); + p = entries; + for (i = 0; i < *n; i++) { + *p++ = fixed214ToFixed1616((fixed214) nextuint16be()); + off += 2; + } + return entries; +} + +static BOOL extractAxisDictValue(CFDictionaryRef dict, CFStringRef key, fixed1616 *out) +{ + CFNumberRef num; + double v; + + num = (CFNumberRef) CFDictionaryGetValue(dict, key); + if (CFNumberGetValue(num, kCFNumberDoubleType, &v) == false) + return NO; + *out = doubleToFixed1616(v); + return YES; +} + +// TODO here and elsewhere: make sure all Objective-C classes and possibly also custom method names have uipriv prefixes +@interface fvarAxis : NSObject { + fixed1616 min; + fixed1616 max; + fixed1616 def; + fixed1616 *avarMappings; + size_t avarCount; +} +- (id)initWithIndex:(CFIndex)i dict:(CFDictionaryRef)dict avarTable:(CFDataRef)table; +- (double)normalize:(double)v; +@end + +@implementation fvarAxis + +- (id)initWithIndex:(CFIndex)i dict:(CFDictionaryRef)dict avarTable:(CFDataRef)table +{ + self = [super init]; + if (self) { + self->avarMappings = NULL; + self->avarCount = 0; + if (!extractAxisDictValue(dict, kCTFontVariationAxisMinimumValueKey, &(self->min))) + goto fail; + if (!extractAxisDictValue(dict, kCTFontVariationAxisMaximumValueKey, &(self->max))) + goto fail; + if (!extractAxisDictValue(dict, kCTFontVariationAxisDefaultValueKey, &(self->def))) + goto fail; + if (table != NULL) + self->avarMappings = avarExtract(table, i, &(self->avarCount)); + } + return self; + +fail: + [self release]; + return nil; +} + +- (void)dealloc +{ + if (self->avarMappings != NULL) { + uiprivFree(self->avarMappings); + self->avarMappings = NULL; + } + [super dealloc]; +} + +- (double)normalize:(double)d +{ + fixed1616 n; + fixed214 n2; + + n = doubleToFixed1616(d); + n = fixed1616Normalize(n, self->min, self->max, self->def); + n2 = normalizedTo214(n, self->avarMappings, self->avarCount); + return fixed214ToDouble(n2); +} + +@end + +NSDictionary *uiprivMakeVariationAxisDict(CFArrayRef axes, CFDataRef avarTable) +{ + CFDictionaryRef axis; + CFIndex i, n; + NSMutableDictionary *out; + + n = CFArrayGetCount(axes); + out = [NSMutableDictionary new]; + for (i = 0; i < n; i++) { + CFNumberRef key; + + axis = (CFDictionaryRef) CFArrayGetValueAtIndex(axes, i); + key = (CFNumberRef) CFDictionaryGetValue(axis, kCTFontVariationAxisIdentifierKey); + [out setObject:[[fvarAxis alloc] initWithIndex:i dict:axis avarTable:avarTable] + forKey:((NSNumber *) key)]; + } + if (avarTable != NULL) + CFRelease(avarTable); + return out; +} + +#define fvarAxisKey(n) [NSNumber numberWithUnsignedInteger:n] + +static BOOL tryAxis(NSDictionary *axisDict, CFDictionaryRef var, NSNumber *key, double *out) +{ + fvarAxis *axis; + CFNumberRef num; + + axis = (fvarAxis *) [axisDict objectForKey:key]; + if (axis == nil) + return NO; + num = (CFNumberRef) CFDictionaryGetValue(var, (CFNumberRef) key); + if (num == nil) + return NO; + if (CFNumberGetValue(num, kCFNumberDoubleType, out) == false) { + // TODO + return NO; + } + *out = [axis normalize:*out]; + return YES; +} + +void uiprivProcessFontVariation(uiprivFontStyleData *d, NSDictionary *axisDict, uiFontDescriptor *out) +{ + CFDictionaryRef var; + double v; + + out->Weight = uiTextWeightNormal; + out->Stretch = uiTextStretchNormal; + + var = [d variation]; + + if (tryAxis(axisDict, var, fvarAxisKey(fvarWeight), &v)) { + // v is now a value between -1 and 1 scaled linearly between discrete points + // we want a linear value between 0 and 1000 with 400 being normal + if (v < 0) { + v += 1; + out->Weight = (uiTextWeight) (v * 400); + } else if (v > 0) + out->Weight += (uiTextWeight) (v * 600); + } + + if (tryAxis(axisDict, var, fvarAxisKey(fvarWidth), &v)) { + // likewise, but with stretches, we go from 0 to 8 with 4 being directly between the two, so this is sufficient + v += 1; + out->Stretch = (uiTextStretch) (v * 4); + } +} diff --git a/darwin/form.m b/darwin/form.m new file mode 100644 index 00000000..af50e363 --- /dev/null +++ b/darwin/form.m @@ -0,0 +1,561 @@ +// 7 june 2016 +#import "uipriv_darwin.h" + +// TODO in the test program, sometimes one of the radio buttons can disappear (try when spaced) + +@interface formChild : NSView +@property uiControl *c; +@property (strong) NSTextField *label; +@property BOOL stretchy; +@property NSLayoutPriority oldHorzHuggingPri; +@property NSLayoutPriority oldVertHuggingPri; +@property (strong) NSLayoutConstraint *baseline; +@property (strong) NSLayoutConstraint *leading; +@property (strong) NSLayoutConstraint *top; +@property (strong) NSLayoutConstraint *trailing; +@property (strong) NSLayoutConstraint *bottom; +- (id)initWithLabel:(NSTextField *)l; +- (void)onDestroy; +- (NSView *)view; +@end + +@interface formView : NSView { + uiForm *f; + NSMutableArray *children; + int padded; + + NSLayoutConstraint *first; + NSMutableArray *inBetweens; + NSLayoutConstraint *last; + NSMutableArray *widths; + NSMutableArray *leadings; + NSMutableArray *middles; + NSMutableArray *trailings; +} +- (id)initWithF:(uiForm *)ff; +- (void)onDestroy; +- (void)removeOurConstraints; +- (void)syncEnableStates:(int)enabled; +- (CGFloat)paddingAmount; +- (void)establishOurConstraints; +- (void)append:(NSString *)label c:(uiControl *)c stretchy:(int)stretchy; +- (void)delete:(int)n; +- (int)isPadded; +- (void)setPadded:(int)p; +- (BOOL)hugsTrailing; +- (BOOL)hugsBottom; +- (int)nStretchy; +@end + +struct uiForm { + uiDarwinControl c; + formView *view; +}; + +@implementation formChild + +- (id)initWithLabel:(NSTextField *)l +{ + self = [super initWithFrame:NSZeroRect]; + if (self) { + self.label = l; + [self.label setTranslatesAutoresizingMaskIntoConstraints:NO]; + [self.label setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal]; + [self.label setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationVertical]; + [self.label setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal]; + [self.label setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationVertical]; + [self addSubview:self.label]; + + self.leading = uiprivMkConstraint(self.label, NSLayoutAttributeLeading, + NSLayoutRelationGreaterThanOrEqual, + self, NSLayoutAttributeLeading, + 1, 0, + @"uiForm label leading"); + [self addConstraint:self.leading]; + self.top = uiprivMkConstraint(self.label, NSLayoutAttributeTop, + NSLayoutRelationEqual, + self, NSLayoutAttributeTop, + 1, 0, + @"uiForm label top"); + [self addConstraint:self.top]; + self.trailing = uiprivMkConstraint(self.label, NSLayoutAttributeTrailing, + NSLayoutRelationEqual, + self, NSLayoutAttributeTrailing, + 1, 0, + @"uiForm label trailing"); + [self addConstraint:self.trailing]; + self.bottom = uiprivMkConstraint(self.label, NSLayoutAttributeBottom, + NSLayoutRelationEqual, + self, NSLayoutAttributeBottom, + 1, 0, + @"uiForm label bottom"); + [self addConstraint:self.bottom]; + } + return self; +} + +- (void)onDestroy +{ + [self removeConstraint:self.trailing]; + self.trailing = nil; + [self removeConstraint:self.top]; + self.top = nil; + [self removeConstraint:self.bottom]; + self.bottom = nil; + + [self.label removeFromSuperview]; + self.label = nil; +} + +- (NSView *)view +{ + return (NSView *) uiControlHandle(self.c); +} + +@end + +@implementation formView + +- (id)initWithF:(uiForm *)ff +{ + self = [super initWithFrame:NSZeroRect]; + if (self != nil) { + self->f = ff; + self->padded = 0; + self->children = [NSMutableArray new]; + + self->inBetweens = [NSMutableArray new]; + self->widths = [NSMutableArray new]; + self->leadings = [NSMutableArray new]; + self->middles = [NSMutableArray new]; + self->trailings = [NSMutableArray new]; + } + return self; +} + +- (void)onDestroy +{ + formChild *fc; + + [self removeOurConstraints]; + [self->inBetweens release]; + [self->widths release]; + [self->leadings release]; + [self->middles release]; + [self->trailings release]; + + for (fc in self->children) { + [self removeConstraint:fc.baseline]; + fc.baseline = nil; + uiControlSetParent(fc.c, NULL); + uiDarwinControlSetSuperview(uiDarwinControl(fc.c), nil); + uiControlDestroy(fc.c); + [fc onDestroy]; + [fc removeFromSuperview]; + } + [self->children release]; +} + +- (void)removeOurConstraints +{ + if (self->first != nil) { + [self removeConstraint:self->first]; + [self->first release]; + self->first = nil; + } + if ([self->inBetweens count] != 0) { + [self removeConstraints:self->inBetweens]; + [self->inBetweens removeAllObjects]; + } + if (self->last != nil) { + [self removeConstraint:self->last]; + [self->last release]; + self->last = nil; + } + if ([self->widths count] != 0) { + [self removeConstraints:self->widths]; + [self->widths removeAllObjects]; + } + if ([self->leadings count] != 0) { + [self removeConstraints:self->leadings]; + [self->leadings removeAllObjects]; + } + if ([self->middles count] != 0) { + [self removeConstraints:self->middles]; + [self->middles removeAllObjects]; + } + if ([self->trailings count] != 0) { + [self removeConstraints:self->trailings]; + [self->trailings removeAllObjects]; + } +} + +- (void)syncEnableStates:(int)enabled +{ + formChild *fc; + + for (fc in self->children) + uiDarwinControlSyncEnableState(uiDarwinControl(fc.c), enabled); +} + +- (CGFloat)paddingAmount +{ + if (!self->padded) + return 0.0; + return uiDarwinPaddingAmount(NULL); +} + +- (void)establishOurConstraints +{ + formChild *fc; + CGFloat padding; + NSView *prev, *prevlabel; + NSLayoutConstraint *c; + + [self removeOurConstraints]; + if ([self->children count] == 0) + return; + padding = [self paddingAmount]; + + // first arrange the children vertically and make them the same width + prev = nil; + for (fc in self->children) { + [fc setHidden:!uiControlVisible(fc.c)]; + if (!uiControlVisible(fc.c)) + continue; + if (prev == nil) { // first view + self->first = uiprivMkConstraint(self, NSLayoutAttributeTop, + NSLayoutRelationEqual, + [fc view], NSLayoutAttributeTop, + 1, 0, + @"uiForm first vertical constraint"); + [self addConstraint:self->first]; + [self->first retain]; + prev = [fc view]; + prevlabel = fc; + continue; + } + // not the first; link it + c = uiprivMkConstraint(prev, NSLayoutAttributeBottom, + NSLayoutRelationEqual, + [fc view], NSLayoutAttributeTop, + 1, -padding, + @"uiForm in-between vertical constraint"); + [self addConstraint:c]; + [self->inBetweens addObject:c]; + // and make the same width + c = uiprivMkConstraint(prev, NSLayoutAttributeWidth, + NSLayoutRelationEqual, + [fc view], NSLayoutAttributeWidth, + 1, 0, + @"uiForm control width constraint"); + [self addConstraint:c]; + [self->widths addObject:c]; + c = uiprivMkConstraint(prevlabel, NSLayoutAttributeWidth, + NSLayoutRelationEqual, + fc, NSLayoutAttributeWidth, + 1, 0, + @"uiForm label lwidth constraint"); + [self addConstraint:c]; + [self->widths addObject:c]; + prev = [fc view]; + prevlabel = fc; + } + if (prev == nil) // all hidden; act as if nothing there + return; + self->last = uiprivMkConstraint(prev, NSLayoutAttributeBottom, + NSLayoutRelationEqual, + self, NSLayoutAttributeBottom, + 1, 0, + @"uiForm last vertical constraint"); + [self addConstraint:self->last]; + [self->last retain]; + + // now arrange the controls horizontally + for (fc in self->children) { + if (!uiControlVisible(fc.c)) + continue; + c = uiprivMkConstraint(self, NSLayoutAttributeLeading, + NSLayoutRelationEqual, + fc, NSLayoutAttributeLeading, + 1, 0, + @"uiForm leading constraint"); + [self addConstraint:c]; + [self->leadings addObject:c]; + // coerce the control to be as wide as possible + // see http://stackoverflow.com/questions/37710892/in-auto-layout-i-set-up-labels-that-shouldnt-grow-horizontally-and-controls-th + c = uiprivMkConstraint(self, NSLayoutAttributeLeading, + NSLayoutRelationEqual, + [fc view], NSLayoutAttributeLeading, + 1, 0, + @"uiForm leading constraint"); + [c setPriority:NSLayoutPriorityDefaultHigh]; + [self addConstraint:c]; + [self->leadings addObject:c]; + c = uiprivMkConstraint(fc, NSLayoutAttributeTrailing, + NSLayoutRelationEqual, + [fc view], NSLayoutAttributeLeading, + 1, -padding, + @"uiForm middle constraint"); + [self addConstraint:c]; + [self->middles addObject:c]; + c = uiprivMkConstraint([fc view], NSLayoutAttributeTrailing, + NSLayoutRelationEqual, + self, NSLayoutAttributeTrailing, + 1, 0, + @"uiForm trailing constraint"); + [self addConstraint:c]; + [self->trailings addObject:c]; + // TODO + c = uiprivMkConstraint(fc, NSLayoutAttributeBottom, + NSLayoutRelationLessThanOrEqual, + self, NSLayoutAttributeBottom, + 1, 0, + @"TODO"); + [self addConstraint:c]; + [self->trailings addObject:c]; + } + + // and make all stretchy controls have the same height + prev = nil; + for (fc in self->children) { + if (!uiControlVisible(fc.c)) + continue; + if (!fc.stretchy) + continue; + if (prev == nil) { + prev = [fc view]; + continue; + } + c = uiprivMkConstraint([fc view], NSLayoutAttributeHeight, + NSLayoutRelationEqual, + prev, NSLayoutAttributeHeight, + 1, 0, + @"uiForm stretchy constraint"); + [self addConstraint:c]; + // TODO make a dedicated array for this + [self->leadings addObject:c]; + } + + // we don't arrange the labels vertically; that's done when we add the control since those constraints don't need to change (they just need to be at their baseline) +} + +- (void)append:(NSString *)label c:(uiControl *)c stretchy:(int)stretchy +{ + formChild *fc; + NSLayoutPriority priority; + NSLayoutAttribute attribute; + int oldnStretchy; + + fc = [[formChild alloc] initWithLabel:uiprivNewLabel(label)]; + fc.c = c; + fc.stretchy = stretchy; + fc.oldHorzHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(fc.c), NSLayoutConstraintOrientationHorizontal); + fc.oldVertHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(fc.c), NSLayoutConstraintOrientationVertical); + [fc setTranslatesAutoresizingMaskIntoConstraints:NO]; + [self addSubview:fc]; + + uiControlSetParent(fc.c, uiControl(self->f)); + uiDarwinControlSetSuperview(uiDarwinControl(fc.c), self); + uiDarwinControlSyncEnableState(uiDarwinControl(fc.c), uiControlEnabledToUser(uiControl(self->f))); + + // if a control is stretchy, it should not hug vertically + // otherwise, it should *forcibly* hug + if (fc.stretchy) + priority = NSLayoutPriorityDefaultLow; + else + // LONGTERM will default high work? + priority = NSLayoutPriorityRequired; + uiDarwinControlSetHuggingPriority(uiDarwinControl(fc.c), priority, NSLayoutConstraintOrientationVertical); + // make sure controls don't hug their horizontal direction so they fill the width of the view + uiDarwinControlSetHuggingPriority(uiDarwinControl(fc.c), NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationHorizontal); + + // and constrain the baselines to position the label vertically + // if the view is a scroll view, align tops, not baselines + // this is what Interface Builder does + attribute = NSLayoutAttributeBaseline; + if ([[fc view] isKindOfClass:[NSScrollView class]]) + attribute = NSLayoutAttributeTop; + fc.baseline = uiprivMkConstraint(fc.label, attribute, + NSLayoutRelationEqual, + [fc view], attribute, + 1, 0, + @"uiForm baseline constraint"); + [self addConstraint:fc.baseline]; + + oldnStretchy = [self nStretchy]; + [self->children addObject:fc]; + + [self establishOurConstraints]; + if (fc.stretchy) + if (oldnStretchy == 0) + uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->f)); + + [fc release]; // we don't need the initial reference now +} + +- (void)delete:(int)n +{ + formChild *fc; + int stretchy; + + fc = (formChild *) [self->children objectAtIndex:n]; + stretchy = fc.stretchy; + + uiControlSetParent(fc.c, NULL); + uiDarwinControlSetSuperview(uiDarwinControl(fc.c), nil); + + uiDarwinControlSetHuggingPriority(uiDarwinControl(fc.c), fc.oldHorzHuggingPri, NSLayoutConstraintOrientationHorizontal); + uiDarwinControlSetHuggingPriority(uiDarwinControl(fc.c), fc.oldVertHuggingPri, NSLayoutConstraintOrientationVertical); + + [fc onDestroy]; + [self->children removeObjectAtIndex:n]; + + [self establishOurConstraints]; + if (stretchy) + if ([self nStretchy] == 0) + uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->f)); +} + +- (int)isPadded +{ + return self->padded; +} + +- (void)setPadded:(int)p +{ + CGFloat padding; + NSLayoutConstraint *c; + + self->padded = p; + padding = [self paddingAmount]; + for (c in self->inBetweens) + [c setConstant:-padding]; + for (c in self->middles) + [c setConstant:-padding]; +} + +- (BOOL)hugsTrailing +{ + return YES; // always hug trailing +} + +- (BOOL)hugsBottom +{ + // only hug if we have stretchy + return [self nStretchy] != 0; +} + +- (int)nStretchy +{ + formChild *fc; + int n; + + n = 0; + for (fc in self->children) { + if (!uiControlVisible(fc.c)) + continue; + if (fc.stretchy) + n++; + } + return n; +} + +@end + +static void uiFormDestroy(uiControl *c) +{ + uiForm *f = uiForm(c); + + [f->view onDestroy]; + [f->view release]; + uiFreeControl(uiControl(f)); +} + +uiDarwinControlDefaultHandle(uiForm, view) +uiDarwinControlDefaultParent(uiForm, view) +uiDarwinControlDefaultSetParent(uiForm, view) +uiDarwinControlDefaultToplevel(uiForm, view) +uiDarwinControlDefaultVisible(uiForm, view) +uiDarwinControlDefaultShow(uiForm, view) +uiDarwinControlDefaultHide(uiForm, view) +uiDarwinControlDefaultEnabled(uiForm, view) +uiDarwinControlDefaultEnable(uiForm, view) +uiDarwinControlDefaultDisable(uiForm, view) + +static void uiFormSyncEnableState(uiDarwinControl *c, int enabled) +{ + uiForm *f = uiForm(c); + + if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(f), enabled)) + return; + [f->view syncEnableStates:enabled]; +} + +uiDarwinControlDefaultSetSuperview(uiForm, view) + +static BOOL uiFormHugsTrailingEdge(uiDarwinControl *c) +{ + uiForm *f = uiForm(c); + + return [f->view hugsTrailing]; +} + +static BOOL uiFormHugsBottom(uiDarwinControl *c) +{ + uiForm *f = uiForm(c); + + return [f->view hugsBottom]; +} + +static void uiFormChildEdgeHuggingChanged(uiDarwinControl *c) +{ + uiForm *f = uiForm(c); + + [f->view establishOurConstraints]; +} + +uiDarwinControlDefaultHuggingPriority(uiForm, view) +uiDarwinControlDefaultSetHuggingPriority(uiForm, view) + +static void uiFormChildVisibilityChanged(uiDarwinControl *c) +{ + uiForm *f = uiForm(c); + + [f->view establishOurConstraints]; +} + +void uiFormAppend(uiForm *f, const char *label, uiControl *c, int stretchy) +{ + // LONGTERM on other platforms + // or at leat allow this and implicitly turn it into a spacer + if (c == NULL) + uiprivUserBug("You cannot add NULL to a uiForm."); + [f->view append:uiprivToNSString(label) c:c stretchy:stretchy]; +} + +void uiFormDelete(uiForm *f, int n) +{ + [f->view delete:n]; +} + +int uiFormPadded(uiForm *f) +{ + return [f->view isPadded]; +} + +void uiFormSetPadded(uiForm *f, int padded) +{ + [f->view setPadded:padded]; +} + +uiForm *uiNewForm(void) +{ + uiForm *f; + + uiDarwinNewControl(uiForm, f); + + f->view = [[formView alloc] initWithF:f]; + + return f; +} diff --git a/darwin/future.m b/darwin/future.m new file mode 100644 index 00000000..e6d05ef4 --- /dev/null +++ b/darwin/future.m @@ -0,0 +1,53 @@ +// 19 may 2017 +#import "uipriv_darwin.h" + +// functions and constants FROM THE FUTURE! +// note: for constants, dlsym() returns the address of the constant itself, as if we had done &constantName + +// added in OS X 10.10; we need 10.8 +CFStringRef *uiprivFUTURE_kCTFontOpenTypeFeatureTag = NULL; +CFStringRef *uiprivFUTURE_kCTFontOpenTypeFeatureValue = NULL; + +// added in OS X 10.12; we need 10.8 +CFStringRef *uiprivFUTURE_kCTBackgroundColorAttributeName = NULL; + +// note that we treat any error as "the symbols aren't there" (and don't care if dlclose() failed) +void uiprivLoadFutures(void) +{ + void *handle; + + // dlsym() walks the dependency chain, so opening the current process should be sufficient + handle = dlopen(NULL, RTLD_LAZY); + if (handle == NULL) + return; +#define GET(var, fn) *((void **) (&var)) = dlsym(handle, #fn) + GET(uiprivFUTURE_kCTFontOpenTypeFeatureTag, kCTFontOpenTypeFeatureTag); + GET(uiprivFUTURE_kCTFontOpenTypeFeatureValue, kCTFontOpenTypeFeatureValue); + GET(uiprivFUTURE_kCTBackgroundColorAttributeName, kCTBackgroundColorAttributeName); + dlclose(handle); +} + +// wrappers for methods that exist in the future that we can check for with respondsToSelector: +// keep them in one place for convenience + +// apparently only added in 10.9; we need 10.8 +void uiprivFUTURE_NSLayoutConstraint_setIdentifier(NSLayoutConstraint *constraint, NSString *identifier) +{ + id cid = (id) constraint; + + if ([constraint respondsToSelector:@selector(setIdentifier:)]) + [cid setIdentifier:identifier]; +} + +// added in 10.11; we need 10.8 +// return whether this was done because we recreate its effects if not (see winmoveresize.m) +BOOL uiprivFUTURE_NSWindow_performWindowDragWithEvent(NSWindow *w, NSEvent *initialEvent) +{ + id cw = (id) w; + + if ([w respondsToSelector:@selector(performWindowDragWithEvent:)]) { + [cw performWindowDragWithEvent:initialEvent]; + return YES; + } + return NO; +} diff --git a/darwin/graphemes.m b/darwin/graphemes.m new file mode 100644 index 00000000..a92534f3 --- /dev/null +++ b/darwin/graphemes.m @@ -0,0 +1,59 @@ +// 3 december 2016 +#import "uipriv_darwin.h" +#import "attrstr.h" + +// CFStringGetRangeOfComposedCharactersAtIndex() is the function for grapheme clusters +// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Strings/Articles/stringsClusters.html says that this does work on all multi-codepoint graphemes (despite the name), and that this is the preferred function for this particular job anyway + +int uiprivGraphemesTakesUTF16(void) +{ + return 1; +} + +uiprivGraphemes *uiprivNewGraphemes(void *s, size_t len) +{ + uiprivGraphemes *g; + UniChar *str = (UniChar *) s; + CFStringRef cfstr; + size_t ppos, gpos; + CFRange range; + size_t i; + + g = uiprivNew(uiprivGraphemes); + + cfstr = CFStringCreateWithCharactersNoCopy(NULL, str, len, kCFAllocatorNull); + if (cfstr == NULL) { + // TODO + } + + // first figure out how many graphemes there are + g->len = 0; + ppos = 0; + while (ppos < len) { + range = CFStringGetRangeOfComposedCharactersAtIndex(cfstr, ppos); + g->len++; + ppos = range.location + range.length; + } + + g->pointsToGraphemes = (size_t *) uiprivAlloc((len + 1) * sizeof (size_t), "size_t[] (graphemes)"); + g->graphemesToPoints = (size_t *) uiprivAlloc((g->len + 1) * sizeof (size_t), "size_t[] (graphemes)"); + + // now calculate everything + // fortunately due to the use of CFRange we can do this in one loop trivially! + ppos = 0; + gpos = 0; + while (ppos < len) { + range = CFStringGetRangeOfComposedCharactersAtIndex(cfstr, ppos); + for (i = 0; i < range.length; i++) + g->pointsToGraphemes[range.location + i] = gpos; + g->graphemesToPoints[gpos] = range.location; + gpos++; + ppos = range.location + range.length; + } + // and set the last one + g->pointsToGraphemes[ppos] = gpos; + g->graphemesToPoints[gpos] = ppos; + + CFRelease(cfstr); + return g; +} diff --git a/darwin/grid.m b/darwin/grid.m new file mode 100644 index 00000000..4cbf34c2 --- /dev/null +++ b/darwin/grid.m @@ -0,0 +1,800 @@ +// 11 june 2016 +#import "uipriv_darwin.h" + +// TODO the assorted test doesn't work right at all + +@interface gridChild : NSView +@property uiControl *c; +@property int left; +@property int top; +@property int xspan; +@property int yspan; +@property int hexpand; +@property uiAlign halign; +@property int vexpand; +@property uiAlign valign; + +@property (strong) NSLayoutConstraint *leadingc; +@property (strong) NSLayoutConstraint *topc; +@property (strong) NSLayoutConstraint *trailingc; +@property (strong) NSLayoutConstraint *bottomc; +@property (strong) NSLayoutConstraint *xcenterc; +@property (strong) NSLayoutConstraint *ycenterc; + +@property NSLayoutPriority oldHorzHuggingPri; +@property NSLayoutPriority oldVertHuggingPri; +- (void)setC:(uiControl *)c grid:(uiGrid *)g; +- (void)onDestroy; +- (NSView *)view; +@end + +@interface gridView : NSView { + uiGrid *g; + NSMutableArray *children; + int padded; + + NSMutableArray *edges; + NSMutableArray *inBetweens; + + NSMutableArray *emptyCellViews; +} +- (id)initWithG:(uiGrid *)gg; +- (void)onDestroy; +- (void)removeOurConstraints; +- (void)syncEnableStates:(int)enabled; +- (CGFloat)paddingAmount; +- (void)establishOurConstraints; +- (void)append:(gridChild *)gc; +- (void)insert:(gridChild *)gc after:(uiControl *)c at:(uiAt)at; +- (int)isPadded; +- (void)setPadded:(int)p; +- (BOOL)hugsTrailing; +- (BOOL)hugsBottom; +- (int)nhexpand; +- (int)nvexpand; +@end + +struct uiGrid { + uiDarwinControl c; + gridView *view; +}; + +@implementation gridChild + +- (void)setC:(uiControl *)c grid:(uiGrid *)g +{ + self.c = c; + self.oldHorzHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(self.c), NSLayoutConstraintOrientationHorizontal); + self.oldVertHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(self.c), NSLayoutConstraintOrientationVertical); + + uiControlSetParent(self.c, uiControl(g)); + uiDarwinControlSetSuperview(uiDarwinControl(self.c), self); + uiDarwinControlSyncEnableState(uiDarwinControl(self.c), uiControlEnabledToUser(uiControl(g))); + + if (self.halign == uiAlignStart || self.halign == uiAlignFill) { + self.leadingc = uiprivMkConstraint(self, NSLayoutAttributeLeading, + NSLayoutRelationEqual, + [self view], NSLayoutAttributeLeading, + 1, 0, + @"uiGrid child horizontal alignment start constraint"); + [self addConstraint:self.leadingc]; + } + if (self.halign == uiAlignCenter) { + self.xcenterc = uiprivMkConstraint(self, NSLayoutAttributeCenterX, + NSLayoutRelationEqual, + [self view], NSLayoutAttributeCenterX, + 1, 0, + @"uiGrid child horizontal alignment center constraint"); + [self addConstraint:self.xcenterc]; + } + if (self.halign == uiAlignEnd || self.halign == uiAlignFill) { + self.trailingc = uiprivMkConstraint(self, NSLayoutAttributeTrailing, + NSLayoutRelationEqual, + [self view], NSLayoutAttributeTrailing, + 1, 0, + @"uiGrid child horizontal alignment end constraint"); + [self addConstraint:self.trailingc]; + } + + if (self.valign == uiAlignStart || self.valign == uiAlignFill) { + self.topc = uiprivMkConstraint(self, NSLayoutAttributeTop, + NSLayoutRelationEqual, + [self view], NSLayoutAttributeTop, + 1, 0, + @"uiGrid child vertical alignment start constraint"); + [self addConstraint:self.topc]; + } + if (self.valign == uiAlignCenter) { + self.ycenterc = uiprivMkConstraint(self, NSLayoutAttributeCenterY, + NSLayoutRelationEqual, + [self view], NSLayoutAttributeCenterY, + 1, 0, + @"uiGrid child vertical alignment center constraint"); + [self addConstraint:self.ycenterc]; + } + if (self.valign == uiAlignEnd || self.valign == uiAlignFill) { + self.bottomc = uiprivMkConstraint(self, NSLayoutAttributeBottom, + NSLayoutRelationEqual, + [self view], NSLayoutAttributeBottom, + 1, 0, + @"uiGrid child vertical alignment end constraint"); + [self addConstraint:self.bottomc]; + } +} + +- (void)onDestroy +{ + if (self.leadingc != nil) { + [self removeConstraint:self.leadingc]; + self.leadingc = nil; + } + if (self.topc != nil) { + [self removeConstraint:self.topc]; + self.topc = nil; + } + if (self.trailingc != nil) { + [self removeConstraint:self.trailingc]; + self.trailingc = nil; + } + if (self.bottomc != nil) { + [self removeConstraint:self.bottomc]; + self.bottomc = nil; + } + if (self.xcenterc != nil) { + [self removeConstraint:self.xcenterc]; + self.xcenterc = nil; + } + if (self.ycenterc != nil) { + [self removeConstraint:self.ycenterc]; + self.ycenterc = nil; + } + + uiControlSetParent(self.c, NULL); + uiDarwinControlSetSuperview(uiDarwinControl(self.c), nil); + uiDarwinControlSetHuggingPriority(uiDarwinControl(self.c), self.oldHorzHuggingPri, NSLayoutConstraintOrientationHorizontal); + uiDarwinControlSetHuggingPriority(uiDarwinControl(self.c), self.oldVertHuggingPri, NSLayoutConstraintOrientationVertical); +} + +- (NSView *)view +{ + return (NSView *) uiControlHandle(self.c); +} + +@end + +@implementation gridView + +- (id)initWithG:(uiGrid *)gg +{ + self = [super initWithFrame:NSZeroRect]; + if (self != nil) { + self->g = gg; + self->padded = 0; + self->children = [NSMutableArray new]; + + self->edges = [NSMutableArray new]; + self->inBetweens = [NSMutableArray new]; + + self->emptyCellViews = [NSMutableArray new]; + } + return self; +} + +- (void)onDestroy +{ + gridChild *gc; + + [self removeOurConstraints]; + [self->edges release]; + [self->inBetweens release]; + + [self->emptyCellViews release]; + + for (gc in self->children) { + [gc onDestroy]; + uiControlDestroy(gc.c); + [gc removeFromSuperview]; + } + [self->children release]; +} + +- (void)removeOurConstraints +{ + NSView *v; + + if ([self->edges count] != 0) { + [self removeConstraints:self->edges]; + [self->edges removeAllObjects]; + } + if ([self->inBetweens count] != 0) { + [self removeConstraints:self->inBetweens]; + [self->inBetweens removeAllObjects]; + } + + for (v in self->emptyCellViews) + [v removeFromSuperview]; + [self->emptyCellViews removeAllObjects]; +} + +- (void)syncEnableStates:(int)enabled +{ + gridChild *gc; + + for (gc in self->children) + uiDarwinControlSyncEnableState(uiDarwinControl(gc.c), enabled); +} + +- (CGFloat)paddingAmount +{ + if (!self->padded) + return 0.0; + return uiDarwinPaddingAmount(NULL); +} + +// LONGTERM stop early if all controls are hidden +- (void)establishOurConstraints +{ + gridChild *gc; + CGFloat padding; + int xmin, ymin; + int xmax, ymax; + int xcount, ycount; + BOOL first; + int **gg; + NSView ***gv; + BOOL **gspan; + int x, y; + int i; + NSLayoutConstraint *c; + int firstx, firsty; + BOOL *hexpand, *vexpand; + BOOL doit; + BOOL onlyEmptyAndSpanning; + + [self removeOurConstraints]; + if ([self->children count] == 0) + return; + padding = [self paddingAmount]; + + // first, figure out the minimum and maximum row and column numbers + // ignore hidden controls + first = YES; + for (gc in self->children) { + // this bit is important: it ensures row ymin and column xmin have at least one cell to draw, so the onlyEmptyAndSpanning logic below will never run on those rows + if (!uiControlVisible(gc.c)) + continue; + if (first) { + xmin = gc.left; + ymin = gc.top; + xmax = gc.left + gc.xspan; + ymax = gc.top + gc.yspan; + first = NO; + continue; + } + if (xmin > gc.left) + xmin = gc.left; + if (ymin > gc.top) + ymin = gc.top; + if (xmax < (gc.left + gc.xspan)) + xmax = gc.left + gc.xspan; + if (ymax < (gc.top + gc.yspan)) + ymax = gc.top + gc.yspan; + } + if (first != NO) // the entire grid is hidden; do nothing + return; + xcount = xmax - xmin; + ycount = ymax - ymin; + + // now build a topological map of the grid gg[y][x] + // also figure out which cells contain spanned views so they can be ignored later + // treat hidden controls by keeping the indices -1 + gg = (int **) uiprivAlloc(ycount * sizeof (int *), "int[][]"); + gspan = (BOOL **) uiprivAlloc(ycount * sizeof (BOOL *), "BOOL[][]"); + for (y = 0; y < ycount; y++) { + gg[y] = (int *) uiprivAlloc(xcount * sizeof (int), "int[]"); + gspan[y] = (BOOL *) uiprivAlloc(xcount * sizeof (BOOL), "BOOL[]"); + for (x = 0; x < xcount; x++) + gg[y][x] = -1; // empty + } + for (i = 0; i < [self->children count]; i++) { + gc = (gridChild *) [self->children objectAtIndex:i]; + if (!uiControlVisible(gc.c)) + continue; + for (y = gc.top; y < gc.top + gc.yspan; y++) + for (x = gc.left; x < gc.left + gc.xspan; x++) { + gg[y - ymin][x - xmin] = i; + if (x != gc.left || y != gc.top) + gspan[y - ymin][x - xmin] = YES; + } + } + + // if a row or column only contains emptys and spanning cells of a opposite-direction spannings, remove it by duplicating the previous row or column + for (y = 0; y < ycount; y++) { + onlyEmptyAndSpanning = YES; + for (x = 0; x < xcount; x++) + if (gg[y][x] != -1) { + gc = (gridChild *) [self->children objectAtIndex:gg[y][x]]; + if (gc.yspan == 1 || gc.top - ymin == y) { + onlyEmptyAndSpanning = NO; + break; + } + } + if (onlyEmptyAndSpanning) + for (x = 0; x < xcount; x++) { + gg[y][x] = gg[y - 1][x]; + gspan[y][x] = YES; + } + } + for (x = 0; x < xcount; x++) { + onlyEmptyAndSpanning = YES; + for (y = 0; y < ycount; y++) + if (gg[y][x] != -1) { + gc = (gridChild *) [self->children objectAtIndex:gg[y][x]]; + if (gc.xspan == 1 || gc.left - xmin == x) { + onlyEmptyAndSpanning = NO; + break; + } + } + if (onlyEmptyAndSpanning) + for (y = 0; y < ycount; y++) { + gg[y][x] = gg[y][x - 1]; + gspan[y][x] = YES; + } + } + + // now build a topological map of the grid's views gv[y][x] + // for any empty cell, create a dummy view + gv = (NSView ***) uiprivAlloc(ycount * sizeof (NSView **), "NSView *[][]"); + for (y = 0; y < ycount; y++) { + gv[y] = (NSView **) uiprivAlloc(xcount * sizeof (NSView *), "NSView *[]"); + for (x = 0; x < xcount; x++) + if (gg[y][x] == -1) { + gv[y][x] = [[NSView alloc] initWithFrame:NSZeroRect]; + [gv[y][x] setTranslatesAutoresizingMaskIntoConstraints:NO]; + [self addSubview:gv[y][x]]; + [self->emptyCellViews addObject:gv[y][x]]; + } else { + gc = (gridChild *) [self->children objectAtIndex:gg[y][x]]; + gv[y][x] = gc; + } + } + + // now figure out which rows and columns really expand + hexpand = (BOOL *) uiprivAlloc(xcount * sizeof (BOOL), "BOOL[]"); + vexpand = (BOOL *) uiprivAlloc(ycount * sizeof (BOOL), "BOOL[]"); + // first, which don't span + for (gc in self->children) { + if (!uiControlVisible(gc.c)) + continue; + if (gc.hexpand && gc.xspan == 1) + hexpand[gc.left - xmin] = YES; + if (gc.vexpand && gc.yspan == 1) + vexpand[gc.top - ymin] = YES; + } + // second, which do span + // the way we handle this is simple: if none of the spanned rows/columns expand, make all rows/columns expand + for (gc in self->children) { + if (!uiControlVisible(gc.c)) + continue; + if (gc.hexpand && gc.xspan != 1) { + doit = YES; + for (x = gc.left; x < gc.left + gc.xspan; x++) + if (hexpand[x - xmin]) { + doit = NO; + break; + } + if (doit) + for (x = gc.left; x < gc.left + gc.xspan; x++) + hexpand[x - xmin] = YES; + } + if (gc.vexpand && gc.yspan != 1) { + doit = YES; + for (y = gc.top; y < gc.top + gc.yspan; y++) + if (vexpand[y - ymin]) { + doit = NO; + break; + } + if (doit) + for (y = gc.top; y < gc.top + gc.yspan; y++) + vexpand[y - ymin] = YES; + } + } + + // now establish all the edge constraints + // leading and trailing edges + for (y = 0; y < ycount; y++) { + c = uiprivMkConstraint(self, NSLayoutAttributeLeading, + NSLayoutRelationEqual, + gv[y][0], NSLayoutAttributeLeading, + 1, 0, + @"uiGrid leading edge constraint"); + [self addConstraint:c]; + [self->edges addObject:c]; + c = uiprivMkConstraint(self, NSLayoutAttributeTrailing, + NSLayoutRelationEqual, + gv[y][xcount - 1], NSLayoutAttributeTrailing, + 1, 0, + @"uiGrid trailing edge constraint"); + [self addConstraint:c]; + [self->edges addObject:c]; + } + // top and bottom edges + for (x = 0; x < xcount; x++) { + c = uiprivMkConstraint(self, NSLayoutAttributeTop, + NSLayoutRelationEqual, + gv[0][x], NSLayoutAttributeTop, + 1, 0, + @"uiGrid top edge constraint"); + [self addConstraint:c]; + [self->edges addObject:c]; + c = uiprivMkConstraint(self, NSLayoutAttributeBottom, + NSLayoutRelationEqual, + gv[ycount - 1][x], NSLayoutAttributeBottom, + 1, 0, + @"uiGrid bottom edge constraint"); + [self addConstraint:c]; + [self->edges addObject:c]; + } + + // now align leading and top edges + // do NOT align spanning cells! + for (x = 0; x < xcount; x++) { + for (y = 0; y < ycount; y++) + if (!gspan[y][x]) + break; + firsty = y; + for (y++; y < ycount; y++) { + if (gspan[y][x]) + continue; + c = uiprivMkConstraint(gv[firsty][x], NSLayoutAttributeLeading, + NSLayoutRelationEqual, + gv[y][x], NSLayoutAttributeLeading, + 1, 0, + @"uiGrid column leading constraint"); + [self addConstraint:c]; + [self->edges addObject:c]; + } + } + for (y = 0; y < ycount; y++) { + for (x = 0; x < xcount; x++) + if (!gspan[y][x]) + break; + firstx = x; + for (x++; x < xcount; x++) { + if (gspan[y][x]) + continue; + c = uiprivMkConstraint(gv[y][firstx], NSLayoutAttributeTop, + NSLayoutRelationEqual, + gv[y][x], NSLayoutAttributeTop, + 1, 0, + @"uiGrid row top constraint"); + [self addConstraint:c]; + [self->edges addObject:c]; + } + } + + // now string adjacent views together + for (y = 0; y < ycount; y++) + for (x = 1; x < xcount; x++) + if (gv[y][x - 1] != gv[y][x]) { + c = uiprivMkConstraint(gv[y][x - 1], NSLayoutAttributeTrailing, + NSLayoutRelationEqual, + gv[y][x], NSLayoutAttributeLeading, + 1, -padding, + @"uiGrid internal horizontal constraint"); + [self addConstraint:c]; + [self->inBetweens addObject:c]; + } + for (x = 0; x < xcount; x++) + for (y = 1; y < ycount; y++) + if (gv[y - 1][x] != gv[y][x]) { + c = uiprivMkConstraint(gv[y - 1][x], NSLayoutAttributeBottom, + NSLayoutRelationEqual, + gv[y][x], NSLayoutAttributeTop, + 1, -padding, + @"uiGrid internal vertical constraint"); + [self addConstraint:c]; + [self->inBetweens addObject:c]; + } + + // now set priorities for all widgets that expand or not + // if a cell is in an expanding row, OR If it spans, then it must be willing to stretch + // otherwise, it tries not to + // note we don't use NSLayoutPriorityRequired as that will cause things to squish when they shouldn't + for (gc in self->children) { + NSLayoutPriority priority; + + if (!uiControlVisible(gc.c)) + continue; + if (hexpand[gc.left - xmin] || gc.xspan != 1) + priority = NSLayoutPriorityDefaultLow; + else + priority = NSLayoutPriorityDefaultHigh; + uiDarwinControlSetHuggingPriority(uiDarwinControl(gc.c), priority, NSLayoutConstraintOrientationHorizontal); + // same for vertical direction + if (vexpand[gc.top - ymin] || gc.yspan != 1) + priority = NSLayoutPriorityDefaultLow; + else + priority = NSLayoutPriorityDefaultHigh; + uiDarwinControlSetHuggingPriority(uiDarwinControl(gc.c), priority, NSLayoutConstraintOrientationVertical); + } + + // TODO make all expanding rows/columns the same height/width + + // and finally clean up + uiprivFree(hexpand); + uiprivFree(vexpand); + for (y = 0; y < ycount; y++) { + uiprivFree(gg[y]); + uiprivFree(gv[y]); + uiprivFree(gspan[y]); + } + uiprivFree(gg); + uiprivFree(gv); + uiprivFree(gspan); +} + +- (void)append:(gridChild *)gc +{ + BOOL update; + int oldnh, oldnv; + + [gc setTranslatesAutoresizingMaskIntoConstraints:NO]; + [self addSubview:gc]; + + // no need to set priority here; that's done in establishOurConstraints + + oldnh = [self nhexpand]; + oldnv = [self nvexpand]; + [self->children addObject:gc]; + + [self establishOurConstraints]; + update = NO; + if (gc.hexpand) + if (oldnh == 0) + update = YES; + if (gc.vexpand) + if (oldnv == 0) + update = YES; + if (update) + uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->g)); + + [gc release]; // we don't need the initial reference now +} + +- (void)insert:(gridChild *)gc after:(uiControl *)c at:(uiAt)at +{ + gridChild *other; + BOOL found; + + found = NO; + for (other in self->children) + if (other.c == c) { + found = YES; + break; + } + if (!found) + uiprivUserBug("Existing control %p is not in grid %p; you cannot add other controls next to it", c, self->g); + + switch (at) { + case uiAtLeading: + gc.left = other.left - gc.xspan; + gc.top = other.top; + break; + case uiAtTop: + gc.left = other.left; + gc.top = other.top - gc.yspan; + break; + case uiAtTrailing: + gc.left = other.left + other.xspan; + gc.top = other.top; + break; + case uiAtBottom: + gc.left = other.left; + gc.top = other.top + other.yspan; + break; + // TODO add error checks to ALL enums + } + + [self append:gc]; +} + +- (int)isPadded +{ + return self->padded; +} + +- (void)setPadded:(int)p +{ + CGFloat padding; + NSLayoutConstraint *c; + +#if 0 /* TODO */ +dispatch_after( +dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), +dispatch_get_main_queue(), +^{ [[self window] visualizeConstraints:[self constraints]]; } +); +#endif + self->padded = p; + padding = [self paddingAmount]; + for (c in self->inBetweens) + switch ([c firstAttribute]) { + case NSLayoutAttributeLeading: + case NSLayoutAttributeTop: + [c setConstant:padding]; + break; + case NSLayoutAttributeTrailing: + case NSLayoutAttributeBottom: + [c setConstant:-padding]; + break; + } +} + +- (BOOL)hugsTrailing +{ + // only hug if we have horizontally expanding + return [self nhexpand] != 0; +} + +- (BOOL)hugsBottom +{ + // only hug if we have vertically expanding + return [self nvexpand] != 0; +} + +- (int)nhexpand +{ + gridChild *gc; + int n; + + n = 0; + for (gc in self->children) { + if (!uiControlVisible(gc.c)) + continue; + if (gc.hexpand) + n++; + } + return n; +} + +- (int)nvexpand +{ + gridChild *gc; + int n; + + n = 0; + for (gc in self->children) { + if (!uiControlVisible(gc.c)) + continue; + if (gc.vexpand) + n++; + } + return n; +} + +@end + +static void uiGridDestroy(uiControl *c) +{ + uiGrid *g = uiGrid(c); + + [g->view onDestroy]; + [g->view release]; + uiFreeControl(uiControl(g)); +} + +uiDarwinControlDefaultHandle(uiGrid, view) +uiDarwinControlDefaultParent(uiGrid, view) +uiDarwinControlDefaultSetParent(uiGrid, view) +uiDarwinControlDefaultToplevel(uiGrid, view) +uiDarwinControlDefaultVisible(uiGrid, view) +uiDarwinControlDefaultShow(uiGrid, view) +uiDarwinControlDefaultHide(uiGrid, view) +uiDarwinControlDefaultEnabled(uiGrid, view) +uiDarwinControlDefaultEnable(uiGrid, view) +uiDarwinControlDefaultDisable(uiGrid, view) + +static void uiGridSyncEnableState(uiDarwinControl *c, int enabled) +{ + uiGrid *g = uiGrid(c); + + if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(g), enabled)) + return; + [g->view syncEnableStates:enabled]; +} + +uiDarwinControlDefaultSetSuperview(uiGrid, view) + +static BOOL uiGridHugsTrailingEdge(uiDarwinControl *c) +{ + uiGrid *g = uiGrid(c); + + return [g->view hugsTrailing]; +} + +static BOOL uiGridHugsBottom(uiDarwinControl *c) +{ + uiGrid *g = uiGrid(c); + + return [g->view hugsBottom]; +} + +static void uiGridChildEdgeHuggingChanged(uiDarwinControl *c) +{ + uiGrid *g = uiGrid(c); + + [g->view establishOurConstraints]; +} + +uiDarwinControlDefaultHuggingPriority(uiGrid, view) +uiDarwinControlDefaultSetHuggingPriority(uiGrid, view) + +static void uiGridChildVisibilityChanged(uiDarwinControl *c) +{ + uiGrid *g = uiGrid(c); + + [g->view establishOurConstraints]; +} + +static gridChild *toChild(uiControl *c, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign, uiGrid *g) +{ + gridChild *gc; + + if (xspan < 0) + uiprivUserBug("You cannot have a negative xspan in a uiGrid cell."); + if (yspan < 0) + uiprivUserBug("You cannot have a negative yspan in a uiGrid cell."); + gc = [gridChild new]; + gc.xspan = xspan; + gc.yspan = yspan; + gc.hexpand = hexpand; + gc.halign = halign; + gc.vexpand = vexpand; + gc.valign = valign; + [gc setC:c grid:g]; + return gc; +} + +void uiGridAppend(uiGrid *g, uiControl *c, int left, int top, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign) +{ + gridChild *gc; + + // LONGTERM on other platforms + // or at leat allow this and implicitly turn it into a spacer + if (c == NULL) + uiprivUserBug("You cannot add NULL to a uiGrid."); + gc = toChild(c, xspan, yspan, hexpand, halign, vexpand, valign, g); + gc.left = left; + gc.top = top; + [g->view append:gc]; +} + +void uiGridInsertAt(uiGrid *g, uiControl *c, uiControl *existing, uiAt at, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign) +{ + gridChild *gc; + + gc = toChild(c, xspan, yspan, hexpand, halign, vexpand, valign, g); + [g->view insert:gc after:existing at:at]; +} + +int uiGridPadded(uiGrid *g) +{ + return [g->view isPadded]; +} + +void uiGridSetPadded(uiGrid *g, int padded) +{ + [g->view setPadded:padded]; +} + +uiGrid *uiNewGrid(void) +{ + uiGrid *g; + + uiDarwinNewControl(uiGrid, g); + + g->view = [[gridView alloc] initWithG:g]; + + return g; +} diff --git a/darwin/group.m b/darwin/group.m index 6a2cbe62..2cfcdf47 100644 --- a/darwin/group.m +++ b/darwin/group.m @@ -1,8 +1,6 @@ // 14 august 2015 #import "uipriv_darwin.h" -// TODO test empty groups - struct uiGroup { uiDarwinControl c; NSBox *box; @@ -10,7 +8,7 @@ struct uiGroup { NSLayoutPriority oldHorzHuggingPri; NSLayoutPriority oldVertHuggingPri; int margined; - struct singleChildConstraints constraints; + uiprivSingleChildConstraints constraints; NSLayoutPriority horzHuggingPri; NSLayoutPriority vertHuggingPri; }; @@ -18,7 +16,7 @@ struct uiGroup { static void removeConstraints(uiGroup *g) { // set to contentView instead of to the box itself, otherwise we get clipping underneath the label - singleChildConstraintsRemove(&(g->constraints), [g->box contentView]); + uiprivSingleChildConstraintsRemove(&(g->constraints), [g->box contentView]); } static void uiGroupDestroy(uiControl *c) @@ -66,14 +64,14 @@ static void groupRelayout(uiGroup *g) if (g->child == NULL) return; childView = (NSView *) uiControlHandle(g->child); - singleChildConstraintsEstablish(&(g->constraints), + uiprivSingleChildConstraintsEstablish(&(g->constraints), [g->box contentView], childView, uiDarwinControlHugsTrailingEdge(uiDarwinControl(g->child)), uiDarwinControlHugsBottom(uiDarwinControl(g->child)), g->margined, @"uiGroup"); // needed for some very rare drawing errors... - jiggleViewLayout(g->box); + uiprivJiggleViewLayout(g->box); } // TODO rename these since I'm starting to get confused by what they mean by hugging @@ -119,6 +117,13 @@ static void uiGroupSetHuggingPriority(uiDarwinControl *c, NSLayoutPriority prior uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(g)); } +static void uiGroupChildVisibilityChanged(uiDarwinControl *c) +{ + uiGroup *g = uiGroup(c); + + groupRelayout(g); +} + char *uiGroupTitle(uiGroup *g) { return uiDarwinNSStringToText([g->box title]); @@ -126,9 +131,7 @@ char *uiGroupTitle(uiGroup *g) void uiGroupSetTitle(uiGroup *g, const char *title) { - [g->box setTitle:toNSString(title)]; - // changing the text might necessitate a change in the groupbox's size - uiDarwinControlTriggerRelayout(uiDarwinControl(g)); + [g->box setTitle:uiprivToNSString(title)]; } void uiGroupSetChild(uiGroup *g, uiControl *child) @@ -165,8 +168,7 @@ int uiGroupMargined(uiGroup *g) void uiGroupSetMargined(uiGroup *g, int margined) { g->margined = margined; - singleChildConstraintsSetMargined(&(g->constraints), g->margined); - // TODO issue a relayout command? + uiprivSingleChildConstraintsSetMargined(&(g->constraints), g->margined); } uiGroup *uiNewGroup(const char *title) @@ -176,7 +178,7 @@ uiGroup *uiNewGroup(const char *title) uiDarwinNewControl(uiGroup, g); g->box = [[NSBox alloc] initWithFrame:NSZeroRect]; - [g->box setTitle:toNSString(title)]; + [g->box setTitle:uiprivToNSString(title)]; [g->box setBoxType:NSBoxPrimary]; [g->box setBorderType:NSLineBorder]; [g->box setTransparent:NO]; diff --git a/darwin/image.m b/darwin/image.m new file mode 100644 index 00000000..0b10cb09 --- /dev/null +++ b/darwin/image.m @@ -0,0 +1,84 @@ +// 25 june 2016 +#import "uipriv_darwin.h" + +struct uiImage { + NSImage *i; + NSSize size; +}; + +uiImage *uiNewImage(double width, double height) +{ + uiImage *i; + + i = uiprivNew(uiImage); + i->size = NSMakeSize(width, height); + i->i = [[NSImage alloc] initWithSize:i->size]; + return i; +} + +void uiFreeImage(uiImage *i) +{ + [i->i release]; + uiprivFree(i); +} + +void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int byteStride) +{ + NSBitmapImageRep *repCalibrated, *repsRGB; + int x, y; + uint8_t *pix, *data; + NSInteger realStride; + + repCalibrated = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL + pixelsWide:pixelWidth + pixelsHigh:pixelHeight + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSCalibratedRGBColorSpace + bitmapFormat:0 + bytesPerRow:0 + bitsPerPixel:32]; + + // Apple doesn't explicitly document this, but we apparently need to use native system endian for the data :| + // TODO split this into a utility routine? + // TODO find proper documentation + // TODO test this on a big-endian system somehow; I have a feeling the above comment is wrong about the diagnosis since the order we are specifying is now 0xAABBGGRR + pix = (uint8_t *) pixels; + data = (uint8_t *) [repCalibrated bitmapData]; + realStride = [repCalibrated bytesPerRow]; + for (y = 0; y < pixelHeight; y++) { + for (x = 0; x < pixelWidth * 4; x += 4) { + union { + uint32_t v32; + uint8_t v8[4]; + } v; + + v.v32 = ((uint32_t) (pix[x + 3])) << 24; + v.v32 |= ((uint32_t) (pix[x + 2])) << 16; + v.v32 |= ((uint32_t) (pix[x + 1])) << 8; + v.v32 |= ((uint32_t) (pix[x])); + data[x] = v.v8[0]; + data[x + 1] = v.v8[1]; + data[x + 2] = v.v8[2]; + data[x + 3] = v.v8[3]; + } + pix += byteStride; + data += realStride; + } + + // we can't call the constructor with this, but we can retag (NOT convert) + repsRGB = [repCalibrated bitmapImageRepByRetaggingWithColorSpace:[NSColorSpace sRGBColorSpace]]; + + [i->i addRepresentation:repsRGB]; + [repsRGB setSize:i->size]; + // don't release repsRGB; it may be equivalent to repCalibrated + // do release repCalibrated though; NSImage has a ref to either it or to repsRGB + [repCalibrated release]; +} + +NSImage *uiprivImageNSImage(uiImage *i) +{ + return i->i; +} diff --git a/darwin/label.m b/darwin/label.m index 3e90a5f8..942b153c 100644 --- a/darwin/label.m +++ b/darwin/label.m @@ -15,9 +15,20 @@ char *uiLabelText(uiLabel *l) void uiLabelSetText(uiLabel *l, const char *text) { - [l->textfield setStringValue:toNSString(text)]; - // changing the text might necessitate a change in the label's size - uiDarwinControlTriggerRelayout(uiDarwinControl(l)); + [l->textfield setStringValue:uiprivToNSString(text)]; +} + +NSTextField *uiprivNewLabel(NSString *str) +{ + NSTextField *tf; + + tf = [[NSTextField alloc] initWithFrame:NSZeroRect]; + [tf setStringValue:str]; + [tf setEditable:NO]; + [tf setSelectable:NO]; + [tf setDrawsBackground:NO]; + uiprivFinishNewTextField(tf, NO); + return tf; } uiLabel *uiNewLabel(const char *text) @@ -26,12 +37,7 @@ uiLabel *uiNewLabel(const char *text) uiDarwinNewControl(uiLabel, l); - l->textfield = [[NSTextField alloc] initWithFrame:NSZeroRect]; - [l->textfield setStringValue:toNSString(text)]; - [l->textfield setEditable:NO]; - [l->textfield setSelectable:NO]; - [l->textfield setDrawsBackground:NO]; - finishNewTextField(l->textfield, NO); + l->textfield = uiprivNewLabel(uiprivToNSString(text)); return l; } diff --git a/darwin/main.m b/darwin/main.m index 07db4459..0d02d642 100644 --- a/darwin/main.m +++ b/darwin/main.m @@ -1,13 +1,20 @@ // 6 april 2015 #import "uipriv_darwin.h" +#import "attrstr.h" static BOOL canQuit = NO; +static NSAutoreleasePool *globalPool; +static uiprivApplicationClass *app; +static uiprivAppDelegate *delegate; -@implementation applicationClass +static BOOL (^isRunning)(void); +static BOOL stepsIsRunning; + +@implementation uiprivApplicationClass - (void)sendEvent:(NSEvent *)e { - if (sendAreaEvents(e) != 0) + if (uiprivSendAreaEvents(e) != 0) return; [super sendEvent:e]; } @@ -18,7 +25,9 @@ static BOOL canQuit = NO; // it turns out NSFontManager also sends changeFont: through this; let's inhibit that here too (see fontbutton.m) - (BOOL)sendAction:(SEL)sel to:(id)to from:(id)from { - if (fontButtonInhibitSendAction(sel, from, to)) + if (uiprivColorButtonInhibitSendAction(sel, from, to)) + return NO; + if (uiprivFontButtonInhibitSendAction(sel, from, to)) return NO; return [super sendAction:sel to:to from:from]; } @@ -30,7 +39,7 @@ static BOOL canQuit = NO; { id override; - if (fontButtonOverrideTargetForAction(sel, from, to, &override)) + if (uiprivFontButtonOverrideTargetForAction(sel, from, to, &override)) return override; return [super targetForAction:sel to:to from:from]; } @@ -48,9 +57,9 @@ static BOOL canQuit = NO; NSEvent *e; if (!canQuit) - complain("call to [NSApp terminate:] when not ready to terminate"); + uiprivImplBug("call to [NSApp terminate:] when not ready to terminate; definitely contact andlabs"); - [realNSApp() stop:realNSApp()]; + [uiprivNSApp() stop:uiprivNSApp()]; // stop: won't register until another event has passed; let's synthesize one e = [NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint @@ -61,16 +70,20 @@ static BOOL canQuit = NO; subtype:0 data1:0 data2:0]; - [realNSApp() postEvent:e atStart:NO]; // let pending events take priority (this is what PostQuitMessage() on Windows does so we have to do it here too for parity; thanks to mikeash in irc.freenode.net/#macdev for confirming that this parameter should indeed be NO) + [uiprivNSApp() postEvent:e atStart:NO]; // let pending events take priority (this is what PostQuitMessage() on Windows does so we have to do it here too for parity; thanks to mikeash in irc.freenode.net/#macdev for confirming that this parameter should indeed be NO) + + // and in case uiMainSteps() was called + stepsIsRunning = NO; } @end -@implementation appDelegate +@implementation uiprivAppDelegate - (void)dealloc { - [self.menuManager release]; + // Apple docs: "Don't Use Accessor Methods in Initializer Methods and dealloc" + [_menuManager release]; [super dealloc]; } @@ -78,7 +91,7 @@ static BOOL canQuit = NO; { // for debugging NSLog(@"in applicationShouldTerminate:"); - if (shouldQuit()) { + if (uiprivShouldQuit()) { canQuit = YES; // this will call terminate:, which is the same as uiQuit() return NSTerminateNow; @@ -93,33 +106,50 @@ static BOOL canQuit = NO; @end -uiInitOptions options; +uiInitOptions uiprivOptions; const char *uiInit(uiInitOptions *o) { - options = *o; - [applicationClass sharedApplication]; - // don't check for a NO return; something (launch services?) causes running from application bundles to always return NO when asking to change activation policy, even if the change is to the same activation policy! - // see https://github.com/andlabs/ui/issues/6 - [realNSApp() setActivationPolicy:NSApplicationActivationPolicyRegular]; - [realNSApp() setDelegate:[appDelegate new]]; + @autoreleasepool { + uiprivOptions = *o; + app = [[uiprivApplicationClass sharedApplication] retain]; + // don't check for a NO return; something (launch services?) causes running from application bundles to always return NO when asking to change activation policy, even if the change is to the same activation policy! + // see https://github.com/andlabs/ui/issues/6 + [uiprivNSApp() setActivationPolicy:NSApplicationActivationPolicyRegular]; + delegate = [uiprivAppDelegate new]; + [uiprivNSApp() setDelegate:delegate]; - initAlloc(); + uiprivInitAlloc(); + uiprivLoadFutures(); + uiprivLoadUndocumented(); - // always do this so we always have an application menu - appDelegate().menuManager = [menuManager new]; - [realNSApp() setMainMenu:[appDelegate().menuManager makeMenubar]]; + // always do this so we always have an application menu + uiprivAppDelegate().menuManager = [[uiprivMenuManager new] autorelease]; + [uiprivNSApp() setMainMenu:[uiprivAppDelegate().menuManager makeMenubar]]; + + uiprivSetupFontPanel(); + + uiprivInitUnderlineColors(); + } + + globalPool = [[NSAutoreleasePool alloc] init]; return NULL; } void uiUninit(void) { - uninitMenus(); - [realNSApp() setDelegate:nil]; - [appDelegate() release]; - [realNSApp() release]; - uninitAlloc(); + if (!globalPool) + uiprivUserBug("You must call uiInit() first!"); + [globalPool release]; + + @autoreleasepool { + uiprivUninitUnderlineColors(); + [delegate release]; + [uiprivNSApp() setDelegate:nil]; + [app release]; + uiprivUninitAlloc(); + } } void uiFreeInitError(const char *err) @@ -128,21 +158,131 @@ void uiFreeInitError(const char *err) void uiMain(void) { - [realNSApp() run]; + isRunning = ^{ + return [uiprivNSApp() isRunning]; + }; + [uiprivNSApp() run]; +} + +void uiMainSteps(void) +{ + // SDL does this and it seems to be necessary for the menubar to work (see #182) + [uiprivNSApp() finishLaunching]; + isRunning = ^{ + return stepsIsRunning; + }; + stepsIsRunning = YES; +} + +int uiMainStep(int wait) +{ + uiprivNextEventArgs nea; + + nea.mask = NSAnyEventMask; + + // ProPuke did this in his original PR requesting this + // I'm not sure if this will work, but I assume it will... + nea.duration = [NSDate distantPast]; + if (wait) // but this is normal so it will work + nea.duration = [NSDate distantFuture]; + + nea.mode = NSDefaultRunLoopMode; + nea.dequeue = YES; + + return uiprivMainStep(&nea, ^(NSEvent *e) { + return NO; + }); +} + +// see also: +// - http://www.cocoawithlove.com/2009/01/demystifying-nsapplication-by.html +// - https://github.com/gnustep/gui/blob/master/Source/NSApplication.m +int uiprivMainStep(uiprivNextEventArgs *nea, BOOL (^interceptEvent)(NSEvent *e)) +{ + NSDate *expire; + NSEvent *e; + NSEventType type; + + @autoreleasepool { + if (!isRunning()) + return 0; + + e = [uiprivNSApp() nextEventMatchingMask:nea->mask + untilDate:nea->duration + inMode:nea->mode + dequeue:nea->dequeue]; + if (e == nil) + return 1; + + type = [e type]; + if (!interceptEvent(e)) + [uiprivNSApp() sendEvent:e]; + [uiprivNSApp() updateWindows]; + + // GNUstep does this + // it also updates the Services menu but there doesn't seem to be a public API for that so + if (type != NSPeriodic && type != NSMouseMoved) + [[uiprivNSApp() mainMenu] update]; + + return 1; + } } -// TODO make this delayed void uiQuit(void) { canQuit = YES; - [realNSApp() terminate:realNSApp()]; + [uiprivNSApp() terminate:uiprivNSApp()]; } // thanks to mikeash in irc.freenode.net/#macdev for suggesting the use of Grand Central Dispatch for this -// TODO will dispatch_get_main_queue() break after _CFRunLoopSetCurrent()? +// LONGTERM will dispatch_get_main_queue() break after _CFRunLoopSetCurrent()? void uiQueueMain(void (*f)(void *data), void *data) { // dispatch_get_main_queue() is a serial queue so it will not execute multiple uiQueueMain() functions concurrently // the signature of f matches dispatch_function_t dispatch_async_f(dispatch_get_main_queue(), data, f); } + +@interface uiprivTimerDelegate : NSObject { + int (*f)(void *data); + void *data; +} +- (id)initWithCallback:(int (*)(void *))callback data:(void *)callbackData; +- (void)doTimer:(NSTimer *)timer; +@end + +@implementation uiprivTimerDelegate + +- (id)initWithCallback:(int (*)(void *))callback data:(void *)callbackData +{ + self = [super init]; + if (self) { + self->f = callback; + self->data = callbackData; + } + return self; +} + +- (void)doTimer:(NSTimer *)timer +{ + if (!(*(self->f))(self->data)) + [timer invalidate]; +} + +@end + +void uiTimer(int milliseconds, int (*f)(void *data), void *data) +{ + uiprivTimerDelegate *delegate; + + delegate = [[uiprivTimerDelegate alloc] initWithCallback:f data:data]; + [NSTimer scheduledTimerWithTimeInterval:(milliseconds / 1000.0) + target:delegate + selector:@selector(doTimer:) + userInfo:nil + repeats:YES]; + [delegate release]; +} + +// TODO figure out the best way to clean the above up in uiUninit(), if it's even necessary +// TODO that means figure out if timers can still fire without the main loop diff --git a/darwin/map.m b/darwin/map.m index 9fa2de96..a9774170 100644 --- a/darwin/map.m +++ b/darwin/map.m @@ -4,40 +4,58 @@ // unfortunately NSMutableDictionary copies its keys, meaning we can't use it for pointers // hence, this file // we could expose a NSMapTable directly, but let's treat all pointers as opaque and hide the implementation, just to be safe and prevent even more rewrites later -struct mapTable { +struct uiprivMap { NSMapTable *m; }; -struct mapTable *newMap(void) +uiprivMap *uiprivNewMap(void) { - struct mapTable *m; + uiprivMap *m; - m = uiNew(struct mapTable); + m = uiprivNew(uiprivMap); m->m = [[NSMapTable alloc] initWithKeyOptions:(NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality) valueOptions:(NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality) capacity:0]; return m; } -void mapDestroy(struct mapTable *m) +void uiprivMapDestroy(uiprivMap *m) { if ([m->m count] != 0) - complain("attempt to destroy map with items inside; did you forget to deallocate something?"); + uiprivImplBug("attempt to destroy map with items inside"); [m->m release]; - uiFree(m); + uiprivFree(m); } -void *mapGet(struct mapTable *m, void *key) +void *uiprivMapGet(uiprivMap *m, void *key) { return NSMapGet(m->m, key); } -void mapSet(struct mapTable *m, void *key, void *value) +void uiprivMapSet(uiprivMap *m, void *key, void *value) { NSMapInsert(m->m, key, value); } -void mapDelete(struct mapTable *m, void *key) +void uiprivMapDelete(uiprivMap *m, void *key) { NSMapRemove(m->m, key); } + +void uiprivMapWalk(uiprivMap *m, void (*f)(void *key, void *value)) +{ + NSMapEnumerator e; + void *k, *v; + + e = NSEnumerateMapTable(m->m); + k = NULL; + v = NULL; + while (NSNextMapEnumeratorPair(&e, &k, &v)) + f(k, v); + NSEndMapTableEnumeration(&e); +} + +void uiprivMapReset(uiprivMap *m) +{ + NSResetMapTable(m->m); +} diff --git a/darwin/menu.m b/darwin/menu.m index 49bf2cdc..153255cd 100644 --- a/darwin/menu.m +++ b/darwin/menu.m @@ -27,13 +27,21 @@ enum { typeSeparator, }; -@implementation menuManager +static void mapItemReleaser(void *key, void *value) +{ + uiMenuItem *item; + + item = (uiMenuItem *) value; + [item->item release]; +} + +@implementation uiprivMenuManager - (id)init { self = [super init]; if (self) { - self->items = newMap(); + self->items = uiprivNewMap(); self->hasQuit = NO; self->hasPreferences = NO; self->hasAbout = NO; @@ -43,7 +51,10 @@ enum { - (void)dealloc { - mapDestroy(self->items); + uiprivMapWalk(self->items, mapItemReleaser); + uiprivMapReset(self->items); + uiprivMapDestroy(self->items); + uiprivUninitMenus(); [super dealloc]; } @@ -51,16 +62,16 @@ enum { { uiMenuItem *item; - item = (uiMenuItem *) mapGet(self->items, sender); + item = (uiMenuItem *) uiprivMapGet(self->items, sender); if (item->type == typeCheckbox) uiMenuItemSetChecked(item, !uiMenuItemChecked(item)); // use the key window as the source of the menu event; it's the active window - (*(item->onClicked))(item, windowFromNSWindow([realNSApp() keyWindow]), item->onClickedData); + (*(item->onClicked))(item, uiprivWindowFromNSWindow([uiprivNSApp() keyWindow]), item->onClickedData); } - (IBAction)onQuitClicked:(id)sender { - if (shouldQuit()) + if (uiprivShouldQuit()) uiQuit(); } @@ -69,21 +80,21 @@ enum { switch (smi->type) { case typeQuit: if (self->hasQuit) - complain("attempt to add multiple Quit menu items"); + uiprivUserBug("You can't have multiple Quit menu items in one program."); self->hasQuit = YES; break; case typePreferences: if (self->hasPreferences) - complain("attempt to add multiple Preferences menu items"); + uiprivUserBug("You can't have multiple Preferences menu items in one program."); self->hasPreferences = YES; break; case typeAbout: if (self->hasAbout) - complain("attempt to add multiple About menu items"); + uiprivUserBug("You can't have multiple About menu items in one program."); self->hasAbout = YES; break; } - mapSet(self->items, item, smi); + uiprivMapSet(self->items, item, smi); } // on OS X there are two ways to handle menu items being enabled or disabled: automatically and manually @@ -101,7 +112,7 @@ enum { if (item == self.aboutItem && !self->hasAbout) return NO; // then poll the item's enabled/disabled state - smi = (uiMenuItem *) mapGet(self->items, item); + smi = (uiMenuItem *) uiprivMapGet(self->items, item); return !smi->disabled; } @@ -115,15 +126,16 @@ enum { NSString *title; NSMenu *servicesMenu; + // note: no need to call setAppleMenu: on this anymore; see https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/#X10_6Notes appName = [[NSProcessInfo processInfo] processName]; - appMenuItem = [[NSMenuItem alloc] initWithTitle:appName action:NULL keyEquivalent:@""]; - appMenu = [[NSMenu alloc] initWithTitle:appName]; + appMenuItem = [[[NSMenuItem alloc] initWithTitle:appName action:NULL keyEquivalent:@""] autorelease]; + appMenu = [[[NSMenu alloc] initWithTitle:appName] autorelease]; [appMenuItem setSubmenu:appMenu]; [menubar addItem:appMenuItem]; // first is About title = [@"About " stringByAppendingString:appName]; - item = [[NSMenuItem alloc] initWithTitle:title action:@selector(onClicked:) keyEquivalent:@""]; + item = [[[NSMenuItem alloc] initWithTitle:title action:@selector(onClicked:) keyEquivalent:@""] autorelease]; [item setTarget:self]; [appMenu addItem:item]; self.aboutItem = item; @@ -131,7 +143,7 @@ enum { [appMenu addItem:[NSMenuItem separatorItem]]; // next is Preferences - item = [[NSMenuItem alloc] initWithTitle:@"Preferences…" action:@selector(onClicked:) keyEquivalent:@","]; + item = [[[NSMenuItem alloc] initWithTitle:@"Preferences…" action:@selector(onClicked:) keyEquivalent:@","] autorelease]; [item setTarget:self]; [appMenu addItem:item]; self.preferencesItem = item; @@ -139,24 +151,24 @@ enum { [appMenu addItem:[NSMenuItem separatorItem]]; // next is Services - item = [[NSMenuItem alloc] initWithTitle:@"Services" action:NULL keyEquivalent:@""]; - servicesMenu = [[NSMenu alloc] initWithTitle:@"Services"]; + item = [[[NSMenuItem alloc] initWithTitle:@"Services" action:NULL keyEquivalent:@""] autorelease]; + servicesMenu = [[[NSMenu alloc] initWithTitle:@"Services"] autorelease]; [item setSubmenu:servicesMenu]; - [realNSApp() setServicesMenu:servicesMenu]; + [uiprivNSApp() setServicesMenu:servicesMenu]; [appMenu addItem:item]; [appMenu addItem:[NSMenuItem separatorItem]]; // next are the three hiding options title = [@"Hide " stringByAppendingString:appName]; - item = [[NSMenuItem alloc] initWithTitle:title action:@selector(hide:) keyEquivalent:@"h"]; + item = [[[NSMenuItem alloc] initWithTitle:title action:@selector(hide:) keyEquivalent:@"h"] autorelease]; // the .xib file says they go to -1 ("First Responder", which sounds wrong...) // to do that, we simply leave the target as nil [appMenu addItem:item]; - item = [[NSMenuItem alloc] initWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; + item = [[[NSMenuItem alloc] initWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"] autorelease]; [item setKeyEquivalentModifierMask:(NSAlternateKeyMask | NSCommandKeyMask)]; [appMenu addItem:item]; - item = [[NSMenuItem alloc] initWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; + item = [[[NSMenuItem alloc] initWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""] autorelease]; [appMenu addItem:item]; [appMenu addItem:[NSMenuItem separatorItem]]; @@ -164,7 +176,7 @@ enum { // and finally Quit // DON'T use @selector(terminate:) as the action; we handle termination ourselves title = [@"Quit " stringByAppendingString:appName]; - item = [[NSMenuItem alloc] initWithTitle:title action:@selector(onQuitClicked:) keyEquivalent:@"q"]; + item = [[[NSMenuItem alloc] initWithTitle:title action:@selector(onQuitClicked:) keyEquivalent:@"q"] autorelease]; [item setTarget:self]; [appMenu addItem:item]; self.quitItem = item; @@ -174,7 +186,7 @@ enum { { NSMenu *menubar; - menubar = [[NSMenu alloc] initWithTitle:@""]; + menubar = [[[NSMenu alloc] initWithTitle:@""] autorelease]; [self buildApplicationMenu:menubar]; return menubar; } @@ -200,7 +212,7 @@ void uiMenuItemDisable(uiMenuItem *item) void uiMenuItemOnClicked(uiMenuItem *item, void (*f)(uiMenuItem *, uiWindow *, void *), void *data) { if (item->type == typeQuit) - complain("attempt to call uiMenuItemOnClicked() on a Quit item; use uiOnShouldQuit() instead"); + uiprivUserBug("You can't call uiMenuItemOnClicked() on a Quit item; use uiOnShouldQuit() instead."); item->onClicked = f; item->onClickedData = data; } @@ -222,41 +234,45 @@ void uiMenuItemSetChecked(uiMenuItem *item, int checked) static uiMenuItem *newItem(uiMenu *m, int type, const char *name) { + @autoreleasepool { + uiMenuItem *item; if (menusFinalized) - complain("attempt to create a new menu item after menus have been finalized"); + uiprivUserBug("You can't create a new menu item after menus have been finalized."); - item = uiNew(uiMenuItem); + item = uiprivNew(uiMenuItem); item->type = type; switch (item->type) { case typeQuit: - item->item = appDelegate().menuManager.quitItem; + item->item = [uiprivAppDelegate().menuManager.quitItem retain]; break; case typePreferences: - item->item = appDelegate().menuManager.preferencesItem; + item->item = [uiprivAppDelegate().menuManager.preferencesItem retain]; break; case typeAbout: - item->item = appDelegate().menuManager.aboutItem; + item->item = [uiprivAppDelegate().menuManager.aboutItem retain]; break; case typeSeparator: - item->item = [NSMenuItem separatorItem]; + item->item = [[NSMenuItem separatorItem] retain]; [m->menu addItem:item->item]; break; default: - item->item = [[NSMenuItem alloc] initWithTitle:toNSString(name) action:@selector(onClicked:) keyEquivalent:@""]; - [item->item setTarget:appDelegate().menuManager]; + item->item = [[NSMenuItem alloc] initWithTitle:uiprivToNSString(name) action:@selector(onClicked:) keyEquivalent:@""]; + [item->item setTarget:uiprivAppDelegate().menuManager]; [m->menu addItem:item->item]; break; } - [appDelegate().menuManager register:item->item to:item]; + [uiprivAppDelegate().menuManager register:item->item to:item]; item->onClicked = defaultOnClicked; [m->items addObject:[NSValue valueWithPointer:item]]; return item; + + } // @autoreleasepool } uiMenuItem *uiMenuAppendItem(uiMenu *m, const char *name) @@ -294,40 +310,43 @@ void uiMenuAppendSeparator(uiMenu *m) uiMenu *uiNewMenu(const char *name) { + @autoreleasepool { + uiMenu *m; if (menusFinalized) - complain("attempt to create a new menu after menus have been finalized"); + uiprivUserBug("You can't create a new menu after menus have been finalized."); if (menus == nil) menus = [NSMutableArray new]; - m = uiNew(uiMenu); + m = uiprivNew(uiMenu); - m->menu = [[NSMenu alloc] initWithTitle:toNSString(name)]; + m->menu = [[NSMenu alloc] initWithTitle:uiprivToNSString(name)]; // use automatic menu item enabling for all menus for consistency's sake - m->item = [[NSMenuItem alloc] initWithTitle:toNSString(name) action:NULL keyEquivalent:@""]; + m->item = [[NSMenuItem alloc] initWithTitle:uiprivToNSString(name) action:NULL keyEquivalent:@""]; [m->item setSubmenu:m->menu]; m->items = [NSMutableArray new]; - [[realNSApp() mainMenu] addItem:m->item]; + [[uiprivNSApp() mainMenu] addItem:m->item]; [menus addObject:[NSValue valueWithPointer:m]]; return m; + + } // @autoreleasepool } -void finalizeMenus(void) +void uiprivFinalizeMenus(void) { menusFinalized = YES; } -void uninitMenus(void) +void uiprivUninitMenus(void) { if (menus == NULL) return; - // don't worry about the actual NSMenus and NSMenuItems; they'll be freed when we clean up the NSApplication [menus enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop) { NSValue *v; uiMenu *m; @@ -340,10 +359,10 @@ void uninitMenus(void) v = (NSValue *) obj; mi = (uiMenuItem *) [v pointerValue]; - uiFree(mi); + uiprivFree(mi); }]; [m->items release]; - uiFree(m); + uiprivFree(m); }]; [menus release]; } diff --git a/darwin/multilineentry.m b/darwin/multilineentry.m index f4ee69b8..d57284a0 100644 --- a/darwin/multilineentry.m +++ b/darwin/multilineentry.m @@ -4,11 +4,32 @@ // NSTextView has no intrinsic content size by default, which wreaks havoc on a pure-Auto Layout system // we'll have to take over to get it to work // see also http://stackoverflow.com/questions/24210153/nstextview-not-properly-resizing-with-auto-layout and http://stackoverflow.com/questions/11237622/using-autolayout-with-expanding-nstextviews -@interface intrinsicSizeTextView : NSTextView +@interface intrinsicSizeTextView : NSTextView { + uiMultilineEntry *libui_e; +} +- (id)initWithFrame:(NSRect)r e:(uiMultilineEntry *)e; @end +struct uiMultilineEntry { + uiDarwinControl c; + NSScrollView *sv; + intrinsicSizeTextView *tv; + uiprivScrollViewData *d; + void (*onChanged)(uiMultilineEntry *, void *); + void *onChangedData; + BOOL changing; +}; + @implementation intrinsicSizeTextView +- (id)initWithFrame:(NSRect)r e:(uiMultilineEntry *)e +{ + self = [super initWithFrame:r]; + if (self) + self->libui_e = e; + return self; +} + - (NSSize)intrinsicContentSize { NSTextContainer *textContainer; @@ -26,31 +47,23 @@ { [super didChangeText]; [self invalidateIntrinsicContentSize]; -} - -// TODO this doesn't call the above? -// TODO this also isn't perfect; play around with cpp-multithread -- (void)setString:(NSString *)str -{ - [super setString:str]; - [self didChangeText]; + if (!self->libui_e->changing) + (*(self->libui_e->onChanged))(self->libui_e, self->libui_e->onChangedData); } @end -struct uiMultilineEntry { - uiDarwinControl c; - NSScrollView *sv; - intrinsicSizeTextView *tv; - void (*onChanged)(uiMultilineEntry *, void *); - void *onChangedData; - struct scrollViewConstraints constraints; -}; +uiDarwinControlAllDefaultsExceptDestroy(uiMultilineEntry, sv) -// TODO events +static void uiMultilineEntryDestroy(uiControl *c) +{ + uiMultilineEntry *e = uiMultilineEntry(c); -// TODO destroy -uiDarwinControlAllDefaults(uiMultilineEntry, sv) + uiprivScrollViewFreeData(e->sv, e->d); + [e->tv release]; + [e->sv release]; + uiFreeControl(uiControl(e)); +} static void defaultOnChanged(uiMultilineEntry *e, void *data) { @@ -64,20 +77,22 @@ char *uiMultilineEntryText(uiMultilineEntry *e) void uiMultilineEntrySetText(uiMultilineEntry *e, const char *text) { - // TODO does this send a changed signal? - [e->tv setString:toNSString(text)]; + [[e->tv textStorage] replaceCharactersInRange:NSMakeRange(0, [[e->tv string] length]) + withString:uiprivToNSString(text)]; + // must be called explicitly according to the documentation of shouldChangeTextInRange:replacementString: + e->changing = YES; + [e->tv didChangeText]; + e->changing = NO; } // TODO scroll to end? void uiMultilineEntryAppend(uiMultilineEntry *e, const char *text) { - // TODO better way? - NSString *str; - - // TODO does this send a changed signal? - str = [e->tv string]; - str = [str stringByAppendingString:toNSString(text)]; - [e->tv setString:str]; + [[e->tv textStorage] replaceCharactersInRange:NSMakeRange([[e->tv string] length], 0) + withString:uiprivToNSString(text)]; + e->changing = YES; + [e->tv didChangeText]; + e->changing = NO; } void uiMultilineEntryOnChanged(uiMultilineEntry *e, void (*f)(uiMultilineEntry *e, void *data), void *data) @@ -101,54 +116,88 @@ void uiMultilineEntrySetReadOnly(uiMultilineEntry *e, int readonly) [e->tv setEditable:editable]; } -uiMultilineEntry *uiNewMultilineEntry(void) +static uiMultilineEntry *finishMultilineEntry(BOOL hscroll) { uiMultilineEntry *e; NSFont *font; + uiprivScrollViewCreateParams p; uiDarwinNewControl(uiMultilineEntry, e); - e->sv = [[NSScrollView alloc] initWithFrame:NSZeroRect]; - // TODO verify against Interface Builder - [e->sv setHasHorizontalScroller:NO]; - [e->sv setHasVerticalScroller:YES]; - [e->sv setAutohidesScrollers:YES]; - [e->sv setBorderType:NSBezelBorder]; + e->tv = [[intrinsicSizeTextView alloc] initWithFrame:NSZeroRect e:e]; - e->tv = [[intrinsicSizeTextView alloc] initWithFrame:NSZeroRect]; - // verified against Interface Builder, except for rich text options - [e->tv setAllowsDocumentBackgroundColorChange:NO]; - [e->tv setBackgroundColor:[NSColor textBackgroundColor]]; - [e->tv setTextColor:[NSColor textColor]]; - [e->tv setAllowsUndo:YES]; + // verified against Interface Builder for a sufficiently customized text view + + // NSText properties: + // this is what Interface Builder sets the background color to + [e->tv setBackgroundColor:[NSColor colorWithCalibratedWhite:1.0 alpha:1.0]]; + [e->tv setDrawsBackground:YES]; [e->tv setEditable:YES]; [e->tv setSelectable:YES]; + [e->tv setFieldEditor:NO]; [e->tv setRichText:NO]; [e->tv setImportsGraphics:NO]; + [e->tv setUsesFontPanel:NO]; + [e->tv setRulerVisible:NO]; + // we'll handle font last + // while setAlignment: has been around since 10.0, the named constant "NSTextAlignmentNatural" seems to have only been introduced in 10.11 +#define ourNSTextAlignmentNatural 4 + [e->tv setAlignment:ourNSTextAlignmentNatural]; + // textColor is set to nil, just keep the dfault [e->tv setBaseWritingDirection:NSWritingDirectionNatural]; - // TODO default paragraph format + [e->tv setHorizontallyResizable:NO]; + [e->tv setVerticallyResizable:YES]; + + // NSTextView properties: + [e->tv setAllowsDocumentBackgroundColorChange:NO]; + [e->tv setAllowsUndo:YES]; + // default paragraph style is nil; keep default [e->tv setAllowsImageEditing:NO]; [e->tv setAutomaticQuoteSubstitutionEnabled:NO]; [e->tv setAutomaticLinkDetectionEnabled:NO]; + [e->tv setDisplaysLinkToolTips:YES]; [e->tv setUsesRuler:NO]; - [e->tv setRulerVisible:NO]; [e->tv setUsesInspectorBar:NO]; [e->tv setSelectionGranularity:NSSelectByCharacter]; -//TODO [e->tv setInsertionPointColor:[NSColor insertionColor]]; + // there is a dedicated named insertion point color but oh well + [e->tv setInsertionPointColor:[NSColor controlTextColor]]; + // typing attributes is nil; keep default (we change it below for fonts though) + [e->tv setSmartInsertDeleteEnabled:NO]; [e->tv setContinuousSpellCheckingEnabled:NO]; [e->tv setGrammarCheckingEnabled:NO]; - [e->tv setUsesFontPanel:NO]; + [e->tv setUsesFindPanel:YES]; [e->tv setEnabledTextCheckingTypes:0]; [e->tv setAutomaticDashSubstitutionEnabled:NO]; + [e->tv setAutomaticDataDetectionEnabled:NO]; [e->tv setAutomaticSpellingCorrectionEnabled:NO]; [e->tv setAutomaticTextReplacementEnabled:NO]; - [e->tv setSmartInsertDeleteEnabled:NO]; - [e->tv setLayoutOrientation:NSTextLayoutOrientationHorizontal]; - // TODO default find panel behavior - // now just to be safe; this will do some of the above but whatever - disableAutocorrect(e->tv); - // this option is complex; just set it to the Interface Builder default + [e->tv setUsesFindBar:NO]; + [e->tv setIncrementalSearchingEnabled:NO]; + + // NSTextContainer properties: + [[e->tv textContainer] setWidthTracksTextView:YES]; + [[e->tv textContainer] setHeightTracksTextView:NO]; + + // NSLayoutManager properties: [[e->tv layoutManager] setAllowsNonContiguousLayout:YES]; + + // now just to be safe; this will do some of the above but whatever + uiprivDisableAutocorrect(e->tv); + + // see https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/TextUILayer/Tasks/TextInScrollView.html + // notice we don't use the Auto Layout code; see scrollview.m for more details + [e->tv setMaxSize:NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX)]; + [e->tv setVerticallyResizable:YES]; + [e->tv setHorizontallyResizable:hscroll]; + if (hscroll) { + [e->tv setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)]; + [[e->tv textContainer] setWidthTracksTextView:NO]; + } else { + [e->tv setAutoresizingMask:NSViewWidthSizable]; + [[e->tv textContainer] setWidthTracksTextView:YES]; + } + [[e->tv textContainer] setContainerSize:NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX)]; + // don't use uiDarwinSetControlFont() directly; we have to do a little extra work to set the font font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]]; [e->tv setTypingAttributes:[NSDictionary @@ -158,167 +207,27 @@ uiMultilineEntry *uiNewMultilineEntry(void) // let's just set it to the standard control font anyway, just to be safe [e->tv setFont:font]; - [e->sv setDocumentView:e->tv]; - [e->tv setTranslatesAutoresizingMaskIntoConstraints:NO]; - scrollViewConstraintsEstablish(&(e->constraints), e->sv, @"uiMultilineEntry"); - // needed to allow horizontal shrinking - // TODO what about vertical text? - [e->tv setContentCompressionResistancePriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal]; - -//TODO:void printinfo(NSScrollView *sv, NSTextView *tv); -//printinfo(e->sv, e->tv); + memset(&p, 0, sizeof (uiprivScrollViewCreateParams)); + p.DocumentView = e->tv; + // this is what Interface Builder sets it to + p.BackgroundColor = [NSColor colorWithCalibratedWhite:1.0 alpha:1.0]; + p.DrawsBackground = YES; + p.Bordered = YES; + p.HScroll = hscroll; + p.VScroll = YES; + e->sv = uiprivMkScrollView(&p, &(e->d)); uiMultilineEntryOnChanged(e, defaultOnChanged, NULL); return e; } -NSMutableString *s; -static void add(const char *fmt, ...) +uiMultilineEntry *uiNewMultilineEntry(void) { - va_list ap; - NSString *fmts; - NSString *a; - - va_start(ap, fmt); - fmts = [NSString stringWithUTF8String:fmt]; - a = [[NSString alloc] initWithFormat:fmts arguments:ap]; - [s appendString:a]; - [s appendString:@"\n"]; - va_end(ap); + return finishMultilineEntry(NO); } -static NSString *edgeInsetsStr(NSEdgeInsets i) +uiMultilineEntry *uiNewNonWrappingMultilineEntry(void) { - return [NSString - stringWithFormat:@"left:%g top:%g right:%g bottom:%g", - i.left, i.top, i.right, i.bottom]; -} - -void printinfo(NSScrollView *sv, NSTextView *tv) -{ - s = [NSMutableString new]; - -#define self _s -struct { -NSScrollView *sv; -NSTextView *tv; -} _s; -_s.sv = sv; -_s.tv = tv; - - add("NSScrollView"); - add(" backgroundColor %@", [self.sv backgroundColor]); - add(" drawsBackground %d", [self.sv drawsBackground]); - add(" borderType %d", [self.sv borderType]); - add(" documentCursor %@", [self.sv documentCursor]); - add(" hasHorizontalScroller %d", [self.sv hasHorizontalScroller]); - add(" hasVerticalScroller %d", [self.sv hasVerticalScroller]); - add(" autohidesScrollers %d", [self.sv autohidesScrollers]); - add(" hasHorizontalRuler %d", [self.sv hasHorizontalRuler]); - add(" hasVerticalRuler %d", [self.sv hasVerticalRuler]); - add(" rulersVisible %d", [self.sv rulersVisible]); - add(" automaticallyAdjustsContentInsets %d", - [self.sv automaticallyAdjustsContentInsets]); - add(" contentInsets %@", - edgeInsetsStr([self.sv contentInsets])); - add(" scrollerInsets %@", - edgeInsetsStr([self.sv scrollerInsets])); - add(" scrollerKnobStyle %d", [self.sv scrollerKnobStyle]); - add(" scrollerStyle %d", [self.sv scrollerStyle]); - add(" lineScroll %g", [self.sv lineScroll]); - add(" horizontalLineScroll %g", [self.sv horizontalLineScroll]); - add(" verticalLineScroll %g", [self.sv verticalLineScroll]); - add(" pageScroll %g", [self.sv pageScroll]); - add(" horizontalPageScroll %g", [self.sv horizontalPageScroll]); - add(" verticalPageScroll %g", [self.sv verticalPageScroll]); - add(" scrollsDynamically %d", [self.sv scrollsDynamically]); - add(" findBarPosition %d", [self.sv findBarPosition]); - add(" usesPredominantAxisScrolling %d", - [self.sv usesPredominantAxisScrolling]); - add(" horizontalScrollElasticity %d", - [self.sv horizontalScrollElasticity]); - add(" verticalScrollElasticity %d", - [self.sv verticalScrollElasticity]); - add(" allowsMagnification %d", [self.sv allowsMagnification]); - add(" magnification %g", [self.sv magnification]); - add(" maxMagnification %g", [self.sv maxMagnification]); - add(" minMagnification %g", [self.sv minMagnification]); - - add(""); - - add("NSTextView"); - add(" textContainerInset %@", - NSStringFromSize([self.tv textContainerInset])); - add(" textContainerOrigin %@", - NSStringFromPoint([self.tv textContainerOrigin])); - add(" backgroundColor %@", [self.tv backgroundColor]); - add(" drawsBackground %d", [self.tv drawsBackground]); - add(" allowsDocumentBackgroundColorChange %d", - [self.tv allowsDocumentBackgroundColorChange]); - add(" allowedInputSourceLocales %@", - [self.tv allowedInputSourceLocales]); - add(" allowsUndo %d", [self.tv allowsUndo]); - add(" isEditable %d", [self.tv isEditable]); - add(" isSelectable %d", [self.tv isSelectable]); - add(" isFieldEditor %d", [self.tv isFieldEditor]); - add(" isRichText %d", [self.tv isRichText]); - add(" importsGraphics %d", [self.tv importsGraphics]); - add(" defaultParagraphStyle %@", - [self.tv defaultParagraphStyle]); - add(" allowsImageEditing %d", [self.tv allowsImageEditing]); - add(" isAutomaticQuoteSubstitutionEnabled %d", - [self.tv isAutomaticQuoteSubstitutionEnabled]); - add(" isAutomaticLinkDetectionEnabled %d", - [self.tv isAutomaticLinkDetectionEnabled]); - add(" displaysLinkToolTips %d", [self.tv displaysLinkToolTips]); - add(" usesRuler %d", [self.tv usesRuler]); - add(" isRulerVisible %d", [self.tv isRulerVisible]); - add(" usesInspectorBar %d", [self.tv usesInspectorBar]); - add(" selectionAffinity %d", [self.tv selectionAffinity]); - add(" selectionGranularity %d", [self.tv selectionGranularity]); - add(" insertionPointColor %@", [self.tv insertionPointColor]); - add(" selectedTextAttributes %@", - [self.tv selectedTextAttributes]); - add(" markedTextAttributes %@", [self.tv markedTextAttributes]); - add(" linkTextAttributes %@", [self.tv linkTextAttributes]); - add(" typingAttributes %@", [self.tv typingAttributes]); - add(" smartInsertDeleteEnabled %d", - [self.tv smartInsertDeleteEnabled]); - add(" isContinuousSpellCheckingEnabled %d", - [self.tv isContinuousSpellCheckingEnabled]); - add(" isGrammarCheckingEnabled %d", - [self.tv isGrammarCheckingEnabled]); - add(" acceptsGlyphInfo %d", [self.tv acceptsGlyphInfo]); - add(" usesFontPanel %d", [self.tv usesFontPanel]); - add(" usesFindPanel %d", [self.tv usesFindPanel]); - add(" enabledTextCheckingTypes %d", - [self.tv enabledTextCheckingTypes]); - add(" isAutomaticDashSubstitutionEnabled %d", - [self.tv isAutomaticDashSubstitutionEnabled]); - add(" isAutomaticDataDetectionEnabled %d", - [self.tv isAutomaticDataDetectionEnabled]); - add(" isAutomaticSpellingCorrectionEnabled %d", - [self.tv isAutomaticSpellingCorrectionEnabled]); - add(" isAutomaticTextReplacementEnabled %d", - [self.tv isAutomaticTextReplacementEnabled]); - add(" usesFindBar %d", [self.tv usesFindBar]); - add(" isIncrementalSearchingEnabled %d", - [self.tv isIncrementalSearchingEnabled]); - add(" NSText:"); - add(" font %@", [self.tv font]); - add(" textColor %@", [self.tv textColor]); - add(" baseWritingDirection %d", [self.tv baseWritingDirection]); - add(" maxSize %@", - NSStringFromSize([self.tv maxSize])); - add(" minSize %@", - NSStringFromSize([self.tv minSize])); - add(" isVerticallyResizable %d", - [self.tv isVerticallyResizable]); - add(" isHorizontallyResizable %d", - [self.tv isHorizontallyResizable]); - -#undef self - - fprintf(stdout, "%s", [s UTF8String]); + return finishMultilineEntry(YES); } diff --git a/darwin/opentype.m b/darwin/opentype.m new file mode 100644 index 00000000..be12917f --- /dev/null +++ b/darwin/opentype.m @@ -0,0 +1,113 @@ +// 11 may 2017 +#import "uipriv_darwin.h" +#import "attrstr.h" + +struct addCTFeatureEntryParams { + CFMutableArrayRef array; + const void *tagKey; + BOOL tagIsNumber; + CFNumberType tagType; + const void *tagValue; + const void *valueKey; + CFNumberType valueType; + const void *valueValue; +}; + +static void addCTFeatureEntry(struct addCTFeatureEntryParams *p) +{ + CFDictionaryRef featureDict; + CFNumberRef tagNum, valueNum; + const void *keys[2], *values[2]; + + keys[0] = p->tagKey; + tagNum = NULL; + values[0] = p->tagValue; + if (p->tagIsNumber) { + tagNum = CFNumberCreate(NULL, p->tagType, p->tagValue); + values[0] = tagNum; + } + + keys[1] = p->valueKey; + valueNum = CFNumberCreate(NULL, p->valueType, p->valueValue); + values[1] = valueNum; + + featureDict = CFDictionaryCreate(NULL, + keys, values, 2, + // TODO are these correct? + &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + if (featureDict == NULL) { + // TODO + } + CFArrayAppendValue(p->array, featureDict); + + CFRelease(featureDict); + CFRelease(valueNum); + if (p->tagIsNumber) + CFRelease(tagNum); +} + +static uiForEach otfArrayForEachAAT(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value, void *data) +{ + __block struct addCTFeatureEntryParams p; + + p.array = (CFMutableArrayRef) data; + p.tagIsNumber = YES; + uiprivOpenTypeToAAT(a, b, c, d, value, ^(uint16_t type, uint16_t selector) { + p.tagKey = kCTFontFeatureTypeIdentifierKey; + p.tagType = kCFNumberSInt16Type; + p.tagValue = (const SInt16 *) (&type); + p.valueKey = kCTFontFeatureSelectorIdentifierKey; + p.valueType = kCFNumberSInt16Type; + p.valueValue = (const SInt16 *) (&selector); + addCTFeatureEntry(&p); + }); + return uiForEachContinue; +} + +// TODO find out which fonts differ in AAT small caps and test them with this +static uiForEach otfArrayForEachOT(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value, void *data) +{ + struct addCTFeatureEntryParams p; + char tagcstr[5]; + CFStringRef tagstr; + + p.array = (CFMutableArrayRef) data; + + p.tagKey = *uiprivFUTURE_kCTFontOpenTypeFeatureTag; + p.tagIsNumber = NO; + tagcstr[0] = a; + tagcstr[1] = b; + tagcstr[2] = c; + tagcstr[3] = d; + tagcstr[4] = '\0'; + tagstr = CFStringCreateWithCString(NULL, tagcstr, kCFStringEncodingUTF8); + if (tagstr == NULL) { + // TODO + } + p.tagValue = tagstr; + + p.valueKey = *uiprivFUTURE_kCTFontOpenTypeFeatureValue; + p.valueType = kCFNumberSInt32Type; + p.valueValue = (const SInt32 *) (&value); + addCTFeatureEntry(&p); + + CFRelease(tagstr); + return uiForEachContinue; +} + +CFArrayRef uiprivOpenTypeFeaturesToCTFeatures(const uiOpenTypeFeatures *otf) +{ + CFMutableArrayRef array; + uiOpenTypeFeaturesForEachFunc f; + + array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + if (array == NULL) { + // TODO + } + f = otfArrayForEachAAT; + if (uiprivFUTURE_kCTFontOpenTypeFeatureTag != NULL && uiprivFUTURE_kCTFontOpenTypeFeatureValue != NULL) + f = otfArrayForEachOT; + uiOpenTypeFeaturesForEach(otf, f, array); + return array; +} diff --git a/darwin/progressbar.m b/darwin/progressbar.m index bb64ab2e..1f5390ff 100644 --- a/darwin/progressbar.m +++ b/darwin/progressbar.m @@ -1,6 +1,25 @@ // 14 august 2015 #import "uipriv_darwin.h" +// NSProgressIndicator has no intrinsic width by default; use the default width in Interface Builder +#define progressIndicatorWidth 100 + +@interface intrinsicWidthNSProgressIndicator : NSProgressIndicator +@end + +@implementation intrinsicWidthNSProgressIndicator + +- (NSSize)intrinsicContentSize +{ + NSSize s; + + s = [super intrinsicContentSize]; + s.width = progressIndicatorWidth; + return s; +} + +@end + struct uiProgressBar { uiDarwinControl c; NSProgressIndicator *pi; @@ -8,10 +27,29 @@ struct uiProgressBar { uiDarwinControlAllDefaults(uiProgressBar, pi) +int uiProgressBarValue(uiProgressBar *p) +{ + if ([p->pi isIndeterminate]) + return -1; + return [p->pi doubleValue]; +} + void uiProgressBarSetValue(uiProgressBar *p, int value) { + if (value == -1) { + [p->pi setIndeterminate:YES]; + [p->pi startAnimation:p->pi]; + return; + } + + if ([p->pi isIndeterminate]) { + [p->pi setIndeterminate:NO]; + [p->pi stopAnimation:p->pi]; + } + if (value < 0 || value > 100) - complain("value %d out of range in progressbarSetValue()", value); + uiprivUserBug("Value %d out of range for a uiProgressBar.", value); + // on 10.8 there's an animation when the progress bar increases, just like with Aero if (value == 100) { [p->pi setMaxValue:101]; @@ -30,7 +68,7 @@ uiProgressBar *uiNewProgressBar(void) uiDarwinNewControl(uiProgressBar, p); - p->pi = [[NSProgressIndicator alloc] initWithFrame:NSZeroRect]; + p->pi = [[intrinsicWidthNSProgressIndicator alloc] initWithFrame:NSZeroRect]; [p->pi setControlSize:NSRegularControlSize]; [p->pi setBezeled:YES]; [p->pi setStyle:NSProgressIndicatorBarStyle]; diff --git a/darwin/radiobuttons.m b/darwin/radiobuttons.m index 633c1494..c7b03717 100644 --- a/darwin/radiobuttons.m +++ b/darwin/radiobuttons.m @@ -1,70 +1,207 @@ // 14 august 2015 #import "uipriv_darwin.h" -// TODO the selection should NOT be lost when starting a new drag +// TODO resizing the controlgallery vertically causes the third button to still resize :| + +// In the old days you would use a NSMatrix for this; as of OS X 10.8 this was deprecated and now you need just a bunch of NSButtons with the same superview AND same action method. +// This is documented on the NSMatrix page, but the rest of the OS X documentation says to still use NSMatrix. +// NSMatrix has weird quirks anyway... + +// LONGTERM 6 units of spacing between buttons, as suggested by Interface Builder? + +@interface radioButtonsDelegate : NSObject { + uiRadioButtons *libui_r; +} +- (id)initWithR:(uiRadioButtons *)r; +- (IBAction)onClicked:(id)sender; +@end struct uiRadioButtons { uiDarwinControl c; - NSMatrix *matrix; + NSView *view; + NSMutableArray *buttons; + NSMutableArray *constraints; + NSLayoutConstraint *lastv; + radioButtonsDelegate *delegate; + void (*onSelected)(uiRadioButtons *, void *); + void *onSelectedData; }; -uiDarwinControlAllDefaults(uiRadioButtons, matrix) +@implementation radioButtonsDelegate -static NSButtonCell *cellAt(uiRadioButtons *r, uintmax_t n) +- (id)initWithR:(uiRadioButtons *)r { - return (NSButtonCell *) [r->matrix cellAtRow:n column:0]; + self = [super init]; + if (self) + self->libui_r = r; + return self; +} + +- (IBAction)onClicked:(id)sender +{ + uiRadioButtons *r = self->libui_r; + + (*(r->onSelected))(r, r->onSelectedData); +} + +@end + +uiDarwinControlAllDefaultsExceptDestroy(uiRadioButtons, view) + +static void defaultOnSelected(uiRadioButtons *r, void *data) +{ + // do nothing +} + +static void uiRadioButtonsDestroy(uiControl *c) +{ + uiRadioButtons *r = uiRadioButtons(c); + NSButton *b; + + // drop the constraints + [r->view removeConstraints:r->constraints]; + [r->constraints release]; + if (r->lastv != nil) + [r->lastv release]; + // destroy the buttons + for (b in r->buttons) { + [b setTarget:nil]; + [b removeFromSuperview]; + } + [r->buttons release]; + // destroy the delegate + [r->delegate release]; + // and destroy ourselves + [r->view release]; + uiFreeControl(uiControl(r)); +} + +static NSButton *buttonAt(uiRadioButtons *r, int n) +{ + return (NSButton *) [r->buttons objectAtIndex:n]; } void uiRadioButtonsAppend(uiRadioButtons *r, const char *text) { - intmax_t prevSelection; + NSButton *b, *b2; + NSLayoutConstraint *constraint; - // renewRows:columns: will reset the selection - prevSelection = [r->matrix selectedRow]; + b = [[NSButton alloc] initWithFrame:NSZeroRect]; + [b setTitle:uiprivToNSString(text)]; + [b setButtonType:NSRadioButton]; + // doesn't seem to have an associated bezel style + [b setBordered:NO]; + [b setTransparent:NO]; + uiDarwinSetControlFont(b, NSRegularControlSize); + [b setTranslatesAutoresizingMaskIntoConstraints:NO]; - [r->matrix renewRows:([r->matrix numberOfRows] + 1) columns:1]; - [cellAt(r, [r->matrix numberOfRows] - 1) setTitle:toNSString(text)]; + [b setTarget:r->delegate]; + [b setAction:@selector(onClicked:)]; - // this will definitely cause a resize in at least the vertical direction, even if not in the horizontal - // DO NOT CALL sizeToCells! this will glitch out; see http://stackoverflow.com/questions/32162562/dynamically-adding-cells-to-a-nsmatrix-laid-out-with-auto-layout-has-weird-effec + [r->buttons addObject:b]; + [r->view addSubview:b]; - // and renew the previous selection - // we need to turn on allowing empty selection for this to work properly on the initial state - // TODO this doesn't actually work - [r->matrix setAllowsEmptySelection:YES]; - [r->matrix selectCellAtRow:prevSelection column:0]; - [r->matrix setAllowsEmptySelection:NO]; + // pin horizontally to the edges of the superview + constraint = uiprivMkConstraint(b, NSLayoutAttributeLeading, + NSLayoutRelationEqual, + r->view, NSLayoutAttributeLeading, + 1, 0, + @"uiRadioButtons button leading constraint"); + [r->view addConstraint:constraint]; + [r->constraints addObject:constraint]; + constraint = uiprivMkConstraint(b, NSLayoutAttributeTrailing, + NSLayoutRelationEqual, + r->view, NSLayoutAttributeTrailing, + 1, 0, + @"uiRadioButtons button trailing constraint"); + [r->view addConstraint:constraint]; + [r->constraints addObject:constraint]; - uiDarwinControlTriggerRelayout(uiDarwinControl(r)); + // if this is the first view, pin it to the top + // otherwise pin to the bottom of the last + if ([r->buttons count] == 1) + constraint = uiprivMkConstraint(b, NSLayoutAttributeTop, + NSLayoutRelationEqual, + r->view, NSLayoutAttributeTop, + 1, 0, + @"uiRadioButtons first button top constraint"); + else { + b2 = buttonAt(r, [r->buttons count] - 2); + constraint = uiprivMkConstraint(b, NSLayoutAttributeTop, + NSLayoutRelationEqual, + b2, NSLayoutAttributeBottom, + 1, 0, + @"uiRadioButtons non-first button top constraint"); + } + [r->view addConstraint:constraint]; + [r->constraints addObject:constraint]; + + // if there is a previous bottom constraint, remove it + if (r->lastv != nil) { + [r->view removeConstraint:r->lastv]; + [r->constraints removeObject:r->lastv]; + [r->lastv release]; + } + + // and make the new bottom constraint + r->lastv = uiprivMkConstraint(b, NSLayoutAttributeBottom, + NSLayoutRelationEqual, + r->view, NSLayoutAttributeBottom, + 1, 0, + @"uiRadioButtons last button bottom constraint"); + [r->view addConstraint:r->lastv]; + [r->constraints addObject:r->lastv]; + [r->lastv retain]; +} + +int uiRadioButtonsSelected(uiRadioButtons *r) +{ + NSButton *b; + NSUInteger i; + + for (i = 0; i < [r->buttons count]; i++) { + b = (NSButton *) [r->buttons objectAtIndex:i]; + if ([b state] == NSOnState) + return i; + } + return -1; +} + +void uiRadioButtonsSetSelected(uiRadioButtons *r, int n) +{ + NSButton *b; + NSInteger state; + + state = NSOnState; + if (n == -1) { + n = uiRadioButtonsSelected(r); + if (n == -1) // from nothing to nothing; do nothing + return; + state = NSOffState; + } + b = (NSButton *) [r->buttons objectAtIndex:n]; + [b setState:state]; +} + +void uiRadioButtonsOnSelected(uiRadioButtons *r, void (*f)(uiRadioButtons *, void *), void *data) +{ + r->onSelected = f; + r->onSelectedData = data; } uiRadioButtons *uiNewRadioButtons(void) { uiRadioButtons *r; - NSButtonCell *cell; uiDarwinNewControl(uiRadioButtons, r); - // we have to set up the NSMatrix this way (prototype first) - // otherwise we won't be able to change its properties (such as the button type) - cell = [NSButtonCell new]; - [cell setButtonType:NSRadioButton]; - // works on NSCells too (same selector) - uiDarwinSetControlFont((NSControl *) cell, NSRegularControlSize); + r->view = [[NSView alloc] initWithFrame:NSZeroRect]; + r->buttons = [NSMutableArray new]; + r->constraints = [NSMutableArray new]; - r->matrix = [[NSMatrix alloc] initWithFrame:NSZeroRect - mode:NSRadioModeMatrix - prototype:cell - numberOfRows:0 - numberOfColumns:0]; - // we manually twiddle this property to allow programmatic non-selection state - [r->matrix setAllowsEmptySelection:NO]; - [r->matrix setSelectionByRect:YES]; - [r->matrix setIntercellSpacing:NSMakeSize(4, 2)]; - [r->matrix setAutorecalculatesCellSize:YES]; - [r->matrix setDrawsBackground:NO]; - [r->matrix setDrawsCellBackground:NO]; - [r->matrix setAutosizesCells:YES]; + r->delegate = [[radioButtonsDelegate alloc] initWithR:r]; + + uiRadioButtonsOnSelected(r, defaultOnSelected, NULL); return r; } diff --git a/darwin/scrollview.m b/darwin/scrollview.m new file mode 100644 index 00000000..1b5cc8d9 --- /dev/null +++ b/darwin/scrollview.m @@ -0,0 +1,61 @@ +// 27 may 2016 +#include "uipriv_darwin.h" + +// see http://stackoverflow.com/questions/37979445/how-do-i-properly-set-up-a-scrolling-nstableview-using-auto-layout-what-ive-tr for why we don't use auto layout +// TODO do the same with uiGroup and uiTab? + +struct uiprivScrollViewData { + BOOL hscroll; + BOOL vscroll; +}; + +NSScrollView *uiprivMkScrollView(uiprivScrollViewCreateParams *p, uiprivScrollViewData **dout) +{ + NSScrollView *sv; + NSBorderType border; + uiprivScrollViewData *d; + + sv = [[NSScrollView alloc] initWithFrame:NSZeroRect]; + if (p->BackgroundColor != nil) + [sv setBackgroundColor:p->BackgroundColor]; + [sv setDrawsBackground:p->DrawsBackground]; + border = NSNoBorder; + if (p->Bordered) + border = NSBezelBorder; + // document view seems to set the cursor properly + [sv setBorderType:border]; + [sv setAutohidesScrollers:YES]; + [sv setHasHorizontalRuler:NO]; + [sv setHasVerticalRuler:NO]; + [sv setRulersVisible:NO]; + [sv setScrollerKnobStyle:NSScrollerKnobStyleDefault]; + // the scroller style is documented as being set by default for us + // LONGTERM verify line and page for programmatically created NSTableView + [sv setScrollsDynamically:YES]; + [sv setFindBarPosition:NSScrollViewFindBarPositionAboveContent]; + [sv setUsesPredominantAxisScrolling:NO]; + [sv setHorizontalScrollElasticity:NSScrollElasticityAutomatic]; + [sv setVerticalScrollElasticity:NSScrollElasticityAutomatic]; + [sv setAllowsMagnification:NO]; + + [sv setDocumentView:p->DocumentView]; + d = uiprivNew(uiprivScrollViewData); + uiprivScrollViewSetScrolling(sv, d, p->HScroll, p->VScroll); + + *dout = d; + return sv; +} + +// based on http://blog.bjhomer.com/2014/08/nsscrollview-and-autolayout.html because (as pointed out there) Apple's official guide is really only for iOS +void uiprivScrollViewSetScrolling(NSScrollView *sv, uiprivScrollViewData *d, BOOL hscroll, BOOL vscroll) +{ + d->hscroll = hscroll; + [sv setHasHorizontalScroller:d->hscroll]; + d->vscroll = vscroll; + [sv setHasVerticalScroller:d->vscroll]; +} + +void uiprivScrollViewFreeData(NSScrollView *sv, uiprivScrollViewData *d) +{ + uiprivFree(d); +} diff --git a/darwin/separator.m b/darwin/separator.m index 4a5b3431..a37a376e 100644 --- a/darwin/separator.m +++ b/darwin/separator.m @@ -1,10 +1,9 @@ // 14 august 2015 #import "uipriv_darwin.h" -// A separator NSBox is horizontal if width >= height. -// Use Interface Builder's initial size as our initial size, to be safe. -#define separatorFrameWidth 96 /* alignment rect 96 */ -#define separatorFrameHeight 5 /* alignment rect 1 */ +// TODO make this intrinsic +#define separatorWidth 96 +#define separatorHeight 96 struct uiSeparator { uiDarwinControl c; @@ -19,7 +18,24 @@ uiSeparator *uiNewHorizontalSeparator(void) uiDarwinNewControl(uiSeparator, s); - s->box = [[NSBox alloc] initWithFrame:NSMakeRect(0, 0, separatorFrameWidth, separatorFrameHeight)]; + // make the initial width >= initial height to force horizontal + s->box = [[NSBox alloc] initWithFrame:NSMakeRect(0, 0, 100, 1)]; + [s->box setBoxType:NSBoxSeparator]; + [s->box setBorderType:NSGrooveBorder]; + [s->box setTransparent:NO]; + [s->box setTitlePosition:NSNoTitle]; + + return s; +} + +uiSeparator *uiNewVerticalSeparator(void) +{ + uiSeparator *s; + + uiDarwinNewControl(uiSeparator, s); + + // make the initial height >= initial width to force vertical + s->box = [[NSBox alloc] initWithFrame:NSMakeRect(0, 0, 1, 100)]; [s->box setBoxType:NSBoxSeparator]; [s->box setBorderType:NSGrooveBorder]; [s->box setTransparent:NO]; diff --git a/darwin/sierra.h b/darwin/sierra.h new file mode 100644 index 00000000..1a76cb76 --- /dev/null +++ b/darwin/sierra.h @@ -0,0 +1,79 @@ +// 2 june 2017 + +// The OS X 10.12 SDK introduces a number of new names for +// existing constants to align the naming conventions of +// Objective-C and Swift (particularly in AppKit). +// +// Unfortunately, in a baffling move, instead of using the existing +// AvailabilityMacros.h method of marking things deprecated, they +// rewrote the relevant constants in ways that make +// source-compatible building much more annoying: +// +// - The replacement names are now the only names in the enum +// or define sets they used to be in. +// - The old names are provided as static const variables, which +// means any code that used the old names in a switch case now +// spit out a compiler warning in strict C99 mode (TODO and in C++ mode?). +// - The old names are marked with new deprecated-symbol +// macros that are *not* compatible with the AvailabilityMacros.h +// macros, meaning their deprecation warnings still come +// through. (It should be noted that AvailabilityMacros.h was still +// updated for 10.12 regardless, hence our #ifdef below.) +// +// As far as I can gather, these facts are not documented *at all*, so +// in the meantime, other open-source projects just use their own +// #defines to maintain source compatibility, either by making the +// new names available everywhere or the old ones un-deprecated. +// We choose the latter. +// TODO file a radar on the issue (after determining C++ compatibility) so this can be pinned down once and for all +// TODO after that, link my stackoverflow question here too +// TODO make sure this #ifdef does actually work on older systems + +#ifdef MAC_OS_X_VERSION_10_12 + +#define NSControlKeyMask NSEventModifierFlagControl +#define NSAlternateKeyMask NSEventModifierFlagOption +#define NSShiftKeyMask NSEventModifierFlagShift +#define NSCommandKeyMask NSEventModifierFlagCommand + +#define NSLeftMouseDown NSEventTypeLeftMouseDown +#define NSRightMouseDown NSEventTypeRightMouseDown +#define NSOtherMouseDown NSEventTypeOtherMouseDown +#define NSLeftMouseUp NSEventTypeLeftMouseUp +#define NSRightMouseUp NSEventTypeRightMouseUp +#define NSOtherMouseUp NSEventTypeOtherMouseUp +#define NSLeftMouseDragged NSEventTypeLeftMouseDragged +#define NSRightMouseDragged NSEventTypeRightMouseDragged +#define NSOtherMouseDragged NSEventTypeOtherMouseDragged +#define NSKeyDown NSEventTypeKeyDown +#define NSKeyUp NSEventTypeKeyUp +#define NSFlagsChanged NSEventTypeFlagsChanged +#define NSApplicationDefined NSEventTypeApplicationDefined +#define NSPeriodic NSEventTypePeriodic +#define NSMouseMoved NSEventTypeMouseMoved + +#define NSRegularControlSize NSControlSizeRegular +#define NSSmallControlSize NSControlSizeSmall + +#define NSAnyEventMask NSEventMaskAny +#define NSLeftMouseDraggedMask NSEventMaskLeftMouseDragged +#define NSLeftMouseUpMask NSEventMaskLeftMouseUp + +#define NSTickMarkAbove NSTickMarkPositionAbove + +#define NSLinearSlider NSSliderTypeLinear + +#define NSInformationalAlertStyle NSAlertStyleInformational +#define NSCriticalAlertStyle NSAlertStyleCritical + +#define NSBorderlessWindowMask NSWindowStyleMaskBorderless +#define NSTitledWindowMask NSWindowStyleMaskTitled +#define NSClosableWindowMask NSWindowStyleMaskClosable +#define NSMiniaturizableWindowMask NSWindowStyleMaskMiniaturizable +#define NSResizableWindowMask NSWindowStyleMaskResizable + +#endif + +// TODO /Users/pietro/src/github.com/andlabs/libui/darwin/stddialogs.m:83:15: warning: 'beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo:' is deprecated: first deprecated in macOS 10.10 - Use -beginSheetModalForWindow:completionHandler: instead [-Wdeprecated-declarations] + +// TODO https://developer.apple.com/library/content/releasenotes/Miscellaneous/RN-Foundation-OSX10.12/ diff --git a/darwin/slider.m b/darwin/slider.m index d280ba2e..6f5c5a76 100644 --- a/darwin/slider.m +++ b/darwin/slider.m @@ -29,7 +29,7 @@ struct uiSlider { }; @interface sliderDelegateClass : NSObject { - struct mapTable *sliders; + uiprivMap *sliders; } - (IBAction)onChanged:(id)sender; - (void)registerSlider:(uiSlider *)b; @@ -42,13 +42,13 @@ struct uiSlider { { self = [super init]; if (self) - self->sliders = newMap(); + self->sliders = uiprivNewMap(); return self; } - (void)dealloc { - mapDestroy(self->sliders); + uiprivMapDestroy(self->sliders); [super dealloc]; } @@ -56,13 +56,13 @@ struct uiSlider { { uiSlider *s; - s = (uiSlider *) mapGet(self->sliders, sender); + s = (uiSlider *) uiprivMapGet(self->sliders, sender); (*(s->onChanged))(s, s->onChangedData); } - (void)registerSlider:(uiSlider *)s { - mapSet(self->sliders, s->slider, s); + uiprivMapSet(self->sliders, s->slider, s); [s->slider setTarget:self]; [s->slider setAction:@selector(onChanged:)]; } @@ -70,7 +70,7 @@ struct uiSlider { - (void)unregisterSlider:(uiSlider *)s { [s->slider setTarget:nil]; - mapDelete(self->sliders, s->slider); + uiprivMapDelete(self->sliders, s->slider); } @end @@ -88,13 +88,12 @@ static void uiSliderDestroy(uiControl *c) uiFreeControl(uiControl(s)); } -intmax_t uiSliderValue(uiSlider *s) +int uiSliderValue(uiSlider *s) { - // NSInteger is the most similar to intmax_t return [s->slider integerValue]; } -void uiSliderSetValue(uiSlider *s, intmax_t value) +void uiSliderSetValue(uiSlider *s, int value) { [s->slider setIntegerValue:value]; } @@ -110,10 +109,17 @@ static void defaultOnChanged(uiSlider *s, void *data) // do nothing } -uiSlider *uiNewSlider(intmax_t min, intmax_t max) +uiSlider *uiNewSlider(int min, int max) { uiSlider *s; NSSliderCell *cell; + int temp; + + if (min >= max) { + temp = min; + min = max; + max = temp; + } uiDarwinNewControl(uiSlider, s); @@ -131,8 +137,8 @@ uiSlider *uiNewSlider(intmax_t min, intmax_t max) [cell setSliderType:NSLinearSlider]; if (sliderDelegate == nil) { - sliderDelegate = [sliderDelegateClass new]; - [delegates addObject:sliderDelegate]; + sliderDelegate = [[sliderDelegateClass new] autorelease]; + [uiprivDelegates addObject:sliderDelegate]; } [sliderDelegate registerSlider:s]; uiSliderOnChanged(s, defaultOnChanged, NULL); diff --git a/darwin/spinbox.m b/darwin/spinbox.m index 409bd5e0..a22ecf13 100644 --- a/darwin/spinbox.m +++ b/darwin/spinbox.m @@ -29,13 +29,24 @@ struct uiSpinbox { void *onChangedData; }; +// yes folks, this varies by operating system! woo! +// 10.10 started drawing the NSStepper one point too low, so we have to fix it up conditionally +// TODO test this; we'll probably have to substitute 10_9 +static CGFloat stepperYDelta(void) +{ + // via https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKit/ + if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_9) + return 0; + return -1; +} + @implementation libui_spinbox - (id)initWithFrame:(NSRect)r spinbox:(uiSpinbox *)sb { self = [super initWithFrame:r]; if (self) { - self->tf = newEditableTextField(); + self->tf = uiprivNewEditableTextField(); [self->tf setTranslatesAutoresizingMaskIntoConstraints:NO]; self->formatter = [NSNumberFormatter new]; @@ -59,40 +70,40 @@ struct uiSpinbox { [self addSubview:self->tf]; [self addSubview:self->stepper]; - [self addConstraint:mkConstraint(self->tf, NSLayoutAttributeLeading, + [self addConstraint:uiprivMkConstraint(self->tf, NSLayoutAttributeLeading, NSLayoutRelationEqual, self, NSLayoutAttributeLeading, 1, 0, @"uiSpinbox left edge")]; - [self addConstraint:mkConstraint(self->stepper, NSLayoutAttributeTrailing, + [self addConstraint:uiprivMkConstraint(self->stepper, NSLayoutAttributeTrailing, NSLayoutRelationEqual, self, NSLayoutAttributeTrailing, 1, 0, @"uiSpinbox right edge")]; - [self addConstraint:mkConstraint(self->tf, NSLayoutAttributeTop, + [self addConstraint:uiprivMkConstraint(self->tf, NSLayoutAttributeTop, NSLayoutRelationEqual, self, NSLayoutAttributeTop, 1, 0, @"uiSpinbox top edge text field")]; - [self addConstraint:mkConstraint(self->tf, NSLayoutAttributeBottom, + [self addConstraint:uiprivMkConstraint(self->tf, NSLayoutAttributeBottom, NSLayoutRelationEqual, self, NSLayoutAttributeBottom, 1, 0, @"uiSpinbox bottom edge text field")]; - [self addConstraint:mkConstraint(self->stepper, NSLayoutAttributeTop, + [self addConstraint:uiprivMkConstraint(self->stepper, NSLayoutAttributeTop, NSLayoutRelationEqual, self, NSLayoutAttributeTop, - 1, -1, // TODO make sure this is right + 1, stepperYDelta(), @"uiSpinbox top edge stepper")]; - [self addConstraint:mkConstraint(self->stepper, NSLayoutAttributeBottom, + [self addConstraint:uiprivMkConstraint(self->stepper, NSLayoutAttributeBottom, NSLayoutRelationEqual, self, NSLayoutAttributeBottom, - 1, -1, // TODO make sure this is right + 1, stepperYDelta(), @"uiSpinbox bottom edge stepper")]; - [self addConstraint:mkConstraint(self->tf, NSLayoutAttributeTrailing, + [self addConstraint:uiprivMkConstraint(self->tf, NSLayoutAttributeTrailing, NSLayoutRelationEqual, self->stepper, NSLayoutAttributeLeading, - 1, -3, // TODO + 1, -3, // arbitrary amount; good enough visually (and it seems to match NSDatePicker too, at least on 10.11, which is even better) @"uiSpinbox space between text field and stepper")]; self->spinbox = sb; @@ -158,12 +169,12 @@ struct uiSpinbox { uiDarwinControlAllDefaults(uiSpinbox, spinbox) -intmax_t uiSpinboxValue(uiSpinbox *s) +int uiSpinboxValue(uiSpinbox *s) { return [s->spinbox libui_value]; } -void uiSpinboxSetValue(uiSpinbox *s, intmax_t value) +void uiSpinboxSetValue(uiSpinbox *s, int value) { [s->spinbox libui_setValue:value]; } @@ -179,12 +190,16 @@ static void defaultOnChanged(uiSpinbox *s, void *data) // do nothing } -uiSpinbox *uiNewSpinbox(intmax_t min, intmax_t max) +uiSpinbox *uiNewSpinbox(int min, int max) { uiSpinbox *s; + int temp; - if (min >= max) - complain("error: min >= max in uiNewSpinbox()"); + if (min >= max) { + temp = min; + min = max; + max = temp; + } uiDarwinNewControl(uiSpinbox, s); diff --git a/darwin/stddialogs.m b/darwin/stddialogs.m index c9639a5e..814456ab 100644 --- a/darwin/stddialogs.m +++ b/darwin/stddialogs.m @@ -1,7 +1,9 @@ // 26 june 2015 #import "uipriv_darwin.h" -// TODO while a dialog is running no other window receives events +// LONGTERM restructure this whole file +// LONGTERM explicitly document this works as we want +// LONGTERM note that font and color buttons also do this #define windowWindow(w) ((NSWindow *) uiControlHandle(uiControl(w))) @@ -22,9 +24,9 @@ static char *runSavePanel(NSWindow *parent, NSSavePanel *s) char *filename; [s beginSheetModalForWindow:parent completionHandler:^(NSInteger result) { - [realNSApp() stopModalWithCode:result]; + [uiprivNSApp() stopModalWithCode:result]; }]; - if ([realNSApp() runModalForWindow:s] != NSFileHandlingPanelOKButton) + if ([uiprivNSApp() runModalForWindow:s] != NSFileHandlingPanelOKButton) return NULL; filename = uiDarwinNSStringToText([[s URL] path]); return filename; @@ -82,12 +84,12 @@ char *uiSaveFile(uiWindow *parent) modalDelegate:self didEndSelector:@selector(panelEnded:result:data:) contextInfo:NULL]; - return [realNSApp() runModalForWindow:[self->panel window]]; + return [uiprivNSApp() runModalForWindow:[self->panel window]]; } - (void)panelEnded:(NSAlert *)panel result:(NSInteger)result data:(void *)data { - [realNSApp() stopModalWithCode:result]; + [uiprivNSApp() stopModalWithCode:result]; } @end @@ -101,8 +103,8 @@ static void msgbox(NSWindow *parent, const char *title, const char *description, [a setAlertStyle:style]; [a setShowsHelp:NO]; [a setShowsSuppressionButton:NO]; - [a setMessageText:toNSString(title)]; - [a setInformativeText:toNSString(description)]; + [a setMessageText:uiprivToNSString(title)]; + [a setInformativeText:uiprivToNSString(description)]; [a addButtonWithTitle:@"OK"]; cm = [[libuiCodeModalAlertPanel alloc] initWithPanel:a parent:parent]; [cm run]; diff --git a/darwin/tab.m b/darwin/tab.m index 0e7a0d19..28c38318 100644 --- a/darwin/tab.m +++ b/darwin/tab.m @@ -1,11 +1,10 @@ // 15 august 2015 #import "uipriv_darwin.h" -// TODOs -// - page 2 tab doesn't lay out properly at first; need to jiggle on page change too :S +// TODO need to jiggle on tab change too (second page disabled tab label initially ambiguous) @interface tabPage : NSObject { - struct singleChildConstraints constraints; + uiprivSingleChildConstraints constraints; int margined; NSView *view; // the NSTabViewItem view itself NSObject *pageID; @@ -35,8 +34,8 @@ struct uiTab { { self = [super init]; if (self != nil) { - self->view = v; - self->pageID = o; + self->view = [v retain]; + self->pageID = [o retain]; } return self; } @@ -59,7 +58,7 @@ struct uiTab { [self removeChildConstraints]; if (self.c == NULL) return; - singleChildConstraintsEstablish(&(self->constraints), + uiprivSingleChildConstraintsEstablish(&(self->constraints), self->view, [self childView], uiDarwinControlHugsTrailingEdge(uiDarwinControl(self.c)), uiDarwinControlHugsBottom(uiDarwinControl(self.c)), @@ -69,7 +68,7 @@ struct uiTab { - (void)removeChildConstraints { - singleChildConstraintsRemove(&(self->constraints), self->view); + uiprivSingleChildConstraintsRemove(&(self->constraints), self->view); } - (int)isMargined @@ -80,8 +79,7 @@ struct uiTab { - (void)setMargined:(int)m { self->margined = m; - singleChildConstraintsSetMargined(&(self->constraints), self->margined); - // TODO issue a relayout command? + uiprivSingleChildConstraintsSetMargined(&(self->constraints), self->margined); } @end @@ -118,8 +116,16 @@ uiDarwinControlDefaultEnabled(uiTab, tabview) uiDarwinControlDefaultEnable(uiTab, tabview) uiDarwinControlDefaultDisable(uiTab, tabview) -// TODO container update -uiDarwinControlDefaultSyncEnableState(uiTab, tabview) +static void uiTabSyncEnableState(uiDarwinControl *c, int enabled) +{ + uiTab *t = uiTab(c); + tabPage *page; + + if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(t), enabled)) + return; + for (page in t->pages) + uiDarwinControlSyncEnableState(uiDarwinControl(page.c), enabled); +} uiDarwinControlDefaultSetSuperview(uiTab, tabview) @@ -130,14 +136,13 @@ static void tabRelayout(uiTab *t) for (page in t->pages) [page establishChildConstraints]; // and this gets rid of some weird issues with regards to box alignment - jiggleViewLayout(t->tabview); + uiprivJiggleViewLayout(t->tabview); } BOOL uiTabHugsTrailingEdge(uiDarwinControl *c) { uiTab *t = uiTab(c); - // TODO make a function? return t->horzHuggingPri < NSLayoutPriorityWindowSizeStayPut; } @@ -175,12 +180,19 @@ static void uiTabSetHuggingPriority(uiDarwinControl *c, NSLayoutPriority priorit uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(t)); } +static void uiTabChildVisibilityChanged(uiDarwinControl *c) +{ + uiTab *t = uiTab(c); + + tabRelayout(t); +} + void uiTabAppend(uiTab *t, const char *name, uiControl *child) { uiTabInsertAt(t, name, [t->pages count], child); } -void uiTabInsertAt(uiTab *t, const char *name, uintmax_t n, uiControl *child) +void uiTabInsertAt(uiTab *t, const char *name, int n, uiControl *child) { tabPage *page; NSView *view; @@ -189,14 +201,14 @@ void uiTabInsertAt(uiTab *t, const char *name, uintmax_t n, uiControl *child) uiControlSetParent(child, uiControl(t)); - view = [[NSView alloc] initWithFrame:NSZeroRect]; - // TODO if we turn off the autoresizing mask, nothing shows up; didn't this get documented somewhere? + view = [[[NSView alloc] initWithFrame:NSZeroRect] autorelease]; + // note: if we turn off the autoresizing mask, nothing shows up uiDarwinControlSetSuperview(uiDarwinControl(child), view); uiDarwinControlSyncEnableState(uiDarwinControl(child), uiControlEnabledToUser(uiControl(t))); // the documentation says these can be nil but the headers say these must not be; let's be safe and make them non-nil anyway pageID = [NSObject new]; - page = [[tabPage alloc] initWithView:view pageID:pageID]; + page = [[[tabPage alloc] initWithView:view pageID:pageID] autorelease]; page.c = child; // don't hug, just in case we're a stretchy tab @@ -206,21 +218,16 @@ void uiTabInsertAt(uiTab *t, const char *name, uintmax_t n, uiControl *child) uiDarwinControlSetHuggingPriority(uiDarwinControl(page.c), NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationVertical); [t->pages insertObject:page atIndex:n]; - [page release]; // no need for initial reference - i = [[NSTabViewItem alloc] initWithIdentifier:pageID]; - [i setLabel:toNSString(name)]; + i = [[[NSTabViewItem alloc] initWithIdentifier:pageID] autorelease]; + [i setLabel:uiprivToNSString(name)]; [i setView:view]; [t->tabview insertTabViewItem:i atIndex:n]; - // TODO release i? - - [pageID release]; // no need for initial reference - [view release]; tabRelayout(t); } -void uiTabDelete(uiTab *t, uintmax_t n) +void uiTabDelete(uiTab *t, int n) { tabPage *page; uiControl *child; @@ -244,12 +251,12 @@ void uiTabDelete(uiTab *t, uintmax_t n) tabRelayout(t); } -uintmax_t uiTabNumPages(uiTab *t) +int uiTabNumPages(uiTab *t) { return [t->pages count]; } -int uiTabMargined(uiTab *t, uintmax_t n) +int uiTabMargined(uiTab *t, int n) { tabPage *page; @@ -257,7 +264,7 @@ int uiTabMargined(uiTab *t, uintmax_t n) return [page isMargined]; } -void uiTabSetMargined(uiTab *t, uintmax_t n, int margined) +void uiTabSetMargined(uiTab *t, int n, int margined) { tabPage *page; diff --git a/darwin/table.h b/darwin/table.h new file mode 100644 index 00000000..4146ab71 --- /dev/null +++ b/darwin/table.h @@ -0,0 +1,27 @@ +// 3 june 2018 +#import "../common/table.h" + +// table.m +// TODO get rid of forward declaration +@class uiprivTableModel; +struct uiTableModel { + uiTableModelHandler *mh; + uiprivTableModel *m; + NSMutableArray *tables; +}; +struct uiTable { + uiDarwinControl c; + NSScrollView *sv; + NSTableView *tv; + uiprivScrollViewData *d; + int backgroundColumn; + uiTableModel *m; +}; + +// tablecolumn.m +@interface uiprivTableCellView : NSTableCellView +- (void)uiprivUpdate:(NSInteger)row; +@end +@interface uiprivTableColumn : NSTableColumn +- (uiprivTableCellView *)uiprivMakeCellView; +@end diff --git a/darwin/table.m b/darwin/table.m new file mode 100644 index 00000000..1bdc7f8d --- /dev/null +++ b/darwin/table.m @@ -0,0 +1,218 @@ +// 3 june 2018 +#import "uipriv_darwin.h" +#import "table.h" + +// TODO is the initial scroll position still wrong? + +@interface uiprivTableModel : NSObject { + uiTableModel *m; +} +- (id)initWithModel:(uiTableModel *)model; +@end + +// TODO we really need to clean up the sharing of the table and model variables... +@interface uiprivTableView : NSTableView { + uiTable *uiprivT; + uiTableModel *uiprivM; +} +- (id)initWithFrame:(NSRect)r uiprivT:(uiTable *)t uiprivM:(uiTableModel *)m; +@end + +@implementation uiprivTableView + +- (id)initWithFrame:(NSRect)r uiprivT:(uiTable *)t uiprivM:(uiTableModel *)m +{ + self = [super initWithFrame:r]; + if (self) { + self->uiprivT = t; + self->uiprivM = m; + } + return self; +} + +// TODO is this correct for overflow scrolling? +static void setBackgroundColor(uiprivTableView *t, NSTableRowView *rv, NSInteger row) +{ + NSColor *color; + double r, g, b, a; + + if (t->uiprivT->backgroundColumn == -1) + return; // let Cocoa do its default thing + if (uiprivTableModelColorIfProvided(t->uiprivM, row, t->uiprivT->backgroundColumn, &r, &g, &b, &a)) + color = [NSColor colorWithSRGBRed:r green:g blue:b alpha:a]; + else { + NSArray *colors; + NSInteger index; + + // this usage is primarily a guess; hopefully it is correct for the non-two color case... (TODO) + // it does seem to be correct for the two-color case, judging from comparing against the value of backgroundColor before changing it (and no, nil does not work; it just sets to white) + colors = [NSColor controlAlternatingRowBackgroundColors]; + index = row % [colors count]; + color = (NSColor *) [colors objectAtIndex:index]; + } + [rv setBackgroundColor:color]; + // color is autoreleased in all cases +} + +@end + +@implementation uiprivTableModel + +- (id)initWithModel:(uiTableModel *)model +{ + self = [super init]; + if (self) + self->m = model; + return self; +} + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tv +{ + return uiprivTableModelNumRows(self->m); +} + + - (NSView *)tableView:(NSTableView *)tv viewForTableColumn:(NSTableColumn *)cc row:(NSInteger)row +{ + uiprivTableColumn *c = (uiprivTableColumn *) cc; + uiprivTableCellView *cv; + + cv = (uiprivTableCellView *) [tv makeViewWithIdentifier:[c identifier] owner:self]; + if (cv == nil) + cv = [c uiprivMakeCellView]; + [cv uiprivUpdate:row]; + return cv; +} + +- (void)tableView:(NSTableView *)tv didAddRowView:(NSTableRowView *)rv forRow:(NSInteger)row +{ + setBackgroundColor((uiprivTableView *) tv, rv, row); +} + +@end + +uiTableModel *uiNewTableModel(uiTableModelHandler *mh) +{ + uiTableModel *m; + + m = uiprivNew(uiTableModel); + m->mh = mh; + m->m = [[uiprivTableModel alloc] initWithModel:m]; + m->tables = [NSMutableArray new]; + return m; +} + +void uiFreeTableModel(uiTableModel *m) +{ + if ([m->tables count] != 0) + uiprivUserBug("You cannot free a uiTableModel while uiTables are using it."); + [m->tables release]; + [m->m release]; + uiprivFree(m); +} + +void uiTableModelRowInserted(uiTableModel *m, int newIndex) +{ + NSTableView *tv; + NSIndexSet *set; + + set = [NSIndexSet indexSetWithIndex:newIndex]; + for (tv in m->tables) + [tv insertRowsAtIndexes:set withAnimation:NSTableViewAnimationEffectNone]; + // set is autoreleased +} + +void uiTableModelRowChanged(uiTableModel *m, int index) +{ + uiprivTableView *tv; + NSTableRowView *rv; + NSUInteger i, n; + uiprivTableCellView *cv; + + for (tv in m->tables) { + rv = [tv rowViewAtRow:index makeIfNecessary:NO]; + if (rv != nil) + setBackgroundColor(tv, rv, index); + n = [[tv tableColumns] count]; + for (i = 0; i < n; i++) { + cv = (uiprivTableCellView *) [tv viewAtColumn:i row:index makeIfNecessary:NO]; + if (cv != nil) + [cv uiprivUpdate:index]; + } + } +} + +void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) +{ + NSTableView *tv; + NSIndexSet *set; + + set = [NSIndexSet indexSetWithIndex:oldIndex]; + for (tv in m->tables) + [tv removeRowsAtIndexes:set withAnimation:NSTableViewAnimationEffectNone]; + // set is autoreleased +} + +uiTableModelHandler *uiprivTableModelHandler(uiTableModel *m) +{ + return m->mh; +} + +uiDarwinControlAllDefaultsExceptDestroy(uiTable, sv) + +static void uiTableDestroy(uiControl *c) +{ + uiTable *t = uiTable(c); + + [t->m->tables removeObject:t->tv]; + uiprivScrollViewFreeData(t->sv, t->d); + [t->tv release]; + [t->sv release]; + uiFreeControl(uiControl(t)); +} + +uiTable *uiNewTable(uiTableParams *p) +{ + uiTable *t; + uiprivScrollViewCreateParams sp; + + uiDarwinNewControl(uiTable, t); + t->m = p->Model; + t->backgroundColumn = p->RowBackgroundColorModelColumn; + + t->tv = [[uiprivTableView alloc] initWithFrame:NSZeroRect uiprivT:t uiprivM:t->m]; + + [t->tv setDataSource:t->m->m]; + [t->tv setDelegate:t->m->m]; + [t->tv reloadData]; + [t->m->tables addObject:t->tv]; + + // TODO is this sufficient? + [t->tv setAllowsColumnReordering:NO]; + [t->tv setAllowsColumnResizing:YES]; + [t->tv setAllowsMultipleSelection:NO]; + [t->tv setAllowsEmptySelection:YES]; + [t->tv setAllowsColumnSelection:NO]; + [t->tv setUsesAlternatingRowBackgroundColors:YES]; + [t->tv setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleRegular]; + [t->tv setGridStyleMask:NSTableViewGridNone]; + [t->tv setAllowsTypeSelect:YES]; + // TODO floatsGroupRows — do we even allow group rows? + + memset(&sp, 0, sizeof (uiprivScrollViewCreateParams)); + sp.DocumentView = t->tv; + // this is what Interface Builder sets it to + // TODO verify + sp.BackgroundColor = [NSColor colorWithCalibratedWhite:1.0 alpha:1.0]; + sp.DrawsBackground = YES; + sp.Bordered = YES; + sp.HScroll = YES; + sp.VScroll = YES; + t->sv = uiprivMkScrollView(&sp, &(t->d)); + + // TODO WHY DOES THIS REMOVE ALL GRAPHICAL GLITCHES? + // I got the idea from http://jwilling.com/blog/optimized-nstableview-scrolling/ but that was on an unrelated problem I didn't seem to have (although I have small-ish tables to start with) + // I don't get layer-backing... am I supposed to layer-back EVERYTHING manually? I need to check Interface Builder again... + [t->sv setWantsLayer:YES]; + + return t; +} diff --git a/darwin/tablecolumn.m b/darwin/tablecolumn.m new file mode 100644 index 00000000..5038cc6b --- /dev/null +++ b/darwin/tablecolumn.m @@ -0,0 +1,720 @@ +// 3 june 2018 +#import "uipriv_darwin.h" +#import "table.h" + +// values from interface builder +#define textColumnLeading 2 +#define textColumnTrailing 2 +#define imageColumnLeading 3 +#define imageTextColumnLeading 7 +#define checkboxTextColumnLeading 0 +// these aren't provided by IB; let's just choose one +#define checkboxColumnLeading imageColumnLeading +#define progressBarColumnLeading imageColumnLeading +#define progressBarColumnTrailing progressBarColumnLeading +#define buttonColumnLeading imageColumnLeading +#define buttonColumnTrailing buttonColumnLeading + +@implementation uiprivTableCellView + +- (void)uiprivUpdate:(NSInteger)row +{ + [self doesNotRecognizeSelector:_cmd]; +} + +@end + +@implementation uiprivTableColumn + +- (uiprivTableCellView *)uiprivMakeCellView +{ + [self doesNotRecognizeSelector:_cmd]; + return nil; // appease compiler +} + +@end + +struct textColumnCreateParams { + uiTable *t; + uiTableModel *m; + + BOOL makeTextField; + int textModelColumn; + int textEditableModelColumn; + uiTableTextColumnOptionalParams textParams; + + BOOL makeImageView; + int imageModelColumn; + + BOOL makeCheckbox; + int checkboxModelColumn; + int checkboxEditableModelColumn; +}; + +@interface uiprivTextImageCheckboxTableCellView : uiprivTableCellView { + uiTable *t; + uiTableModel *m; + + NSTextField *tf; + int textModelColumn; + int textEditableModelColumn; + uiTableTextColumnOptionalParams textParams; + + NSImageView *iv; + int imageModelColumn; + + NSButton *cb; + int checkboxModelColumn; + int checkboxEditableModelColumn; +} +- (id)initWithFrame:(NSRect)r params:(struct textColumnCreateParams *)p; +- (IBAction)uiprivOnTextFieldAction:(id)sender; +- (IBAction)uiprivOnCheckboxAction:(id)sender; +@end + +@implementation uiprivTextImageCheckboxTableCellView + +- (id)initWithFrame:(NSRect)r params:(struct textColumnCreateParams *)p +{ + self = [super initWithFrame:r]; + if (self) { + NSMutableArray *constraints; + + self->t = p->t; + self->m = p->m; + constraints = [NSMutableArray new]; + + self->tf = nil; + if (p->makeTextField) { + self->textModelColumn = p->textModelColumn; + self->textEditableModelColumn = p->textEditableModelColumn; + self->textParams = p->textParams; + + self->tf = uiprivNewLabel(@""); + // TODO set wrap and ellipsize modes? + [self->tf setTarget:self]; + [self->tf setAction:@selector(uiprivOnTextFieldAction:)]; + [self->tf setTranslatesAutoresizingMaskIntoConstraints:NO]; + [self addSubview:self->tf]; + + // TODO for all three controls: set hugging and compression resistance properly + [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeLeading, + NSLayoutRelationEqual, + self->tf, NSLayoutAttributeLeading, + 1, -textColumnLeading, + @"uiTable cell text leading constraint")]; + [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeTop, + NSLayoutRelationEqual, + self->tf, NSLayoutAttributeTop, + 1, 0, + @"uiTable cell text top constraint")]; + [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeTrailing, + NSLayoutRelationEqual, + self->tf, NSLayoutAttributeTrailing, + 1, textColumnTrailing, + @"uiTable cell text trailing constraint")]; + [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeBottom, + NSLayoutRelationEqual, + self->tf, NSLayoutAttributeBottom, + 1, 0, + @"uiTable cell text bottom constraint")]; + } + + self->iv = nil; + if (p->makeImageView) { + self->imageModelColumn = p->imageModelColumn; + + self->iv = [[NSImageView alloc] initWithFrame:NSZeroRect]; + [self->iv setImageFrameStyle:NSImageFrameNone]; + [self->iv setImageAlignment:NSImageAlignCenter]; + [self->iv setImageScaling:NSImageScaleProportionallyDown]; + [self->iv setAnimates:NO]; + [self->iv setEditable:NO]; + [self->iv setTranslatesAutoresizingMaskIntoConstraints:NO]; + [self addSubview:self->iv]; + + [constraints addObject:uiprivMkConstraint(self->iv, NSLayoutAttributeWidth, + NSLayoutRelationEqual, + self->iv, NSLayoutAttributeHeight, + 1, 0, + @"uiTable image squareness constraint")]; + if (self->tf != nil) { + [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeLeading, + NSLayoutRelationEqual, + self->iv, NSLayoutAttributeLeading, + 1, -imageColumnLeading, + @"uiTable cell image leading constraint")]; + [constraints replaceObjectAtIndex:0 + withObject:uiprivMkConstraint(self->iv, NSLayoutAttributeTrailing, + NSLayoutRelationEqual, + self->tf, NSLayoutAttributeLeading, + 1, -imageTextColumnLeading, + @"uiTable cell image-text spacing constraint")]; + } else + [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeCenterX, + NSLayoutRelationEqual, + self->iv, NSLayoutAttributeCenterX, + 1, 0, + @"uiTable cell image centering constraint")]; + [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeTop, + NSLayoutRelationEqual, + self->iv, NSLayoutAttributeTop, + 1, 0, + @"uiTable cell image top constraint")]; + [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeBottom, + NSLayoutRelationEqual, + self->iv, NSLayoutAttributeBottom, + 1, 0, + @"uiTable cell image bottom constraint")]; + } + + self->cb = nil; + if (p->makeCheckbox) { + self->checkboxModelColumn = p->checkboxModelColumn; + self->checkboxEditableModelColumn = p->checkboxEditableModelColumn; + + self->cb = [[NSButton alloc] initWithFrame:NSZeroRect]; + [self->cb setTitle:@""]; + [self->cb setButtonType:NSSwitchButton]; + // doesn't seem to have an associated bezel style + [self->cb setBordered:NO]; + [self->cb setTransparent:NO]; + uiDarwinSetControlFont(self->cb, NSRegularControlSize); + [self->cb setTranslatesAutoresizingMaskIntoConstraints:NO]; + [self addSubview:self->cb]; + + if (self->tf != nil) { + [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeLeading, + NSLayoutRelationEqual, + self->cb, NSLayoutAttributeLeading, + 1, -imageColumnLeading, + @"uiTable cell checkbox leading constraint")]; + [constraints replaceObjectAtIndex:0 + withObject:uiprivMkConstraint(self->cb, NSLayoutAttributeTrailing, + NSLayoutRelationEqual, + self->tf, NSLayoutAttributeLeading, + 1, -imageTextColumnLeading, + @"uiTable cell checkbox-text spacing constraint")]; + } else + [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeCenterX, + NSLayoutRelationEqual, + self->cb, NSLayoutAttributeCenterX, + 1, 0, + @"uiTable cell checkbox centering constraint")]; + [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeTop, + NSLayoutRelationEqual, + self->cb, NSLayoutAttributeTop, + 1, 0, + @"uiTable cell checkbox top constraint")]; + [constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeBottom, + NSLayoutRelationEqual, + self->cb, NSLayoutAttributeBottom, + 1, 0, + @"uiTable cell checkbox bottom constraint")]; + } + + [self addConstraints:constraints]; + + // take advantage of NSTableCellView-provided accessibility features + if (self->tf != nil) + [self setTextField:self->tf]; + if (self->iv != nil) + [self setImageView:self->iv]; + } + return self; +} + +- (void)dealloc +{ + if (self->cb != nil) { + [self->cb release]; + self->cb = nil; + } + if (self->iv != nil) { + [self->iv release]; + self->iv = nil; + } + if (self->tf != nil) { + [self->tf release]; + self->tf = nil; + } + [super dealloc]; +} + +- (void)uiprivUpdate:(NSInteger)row +{ + uiTableValue *value; + + if (self->tf != nil) { + NSString *str; + NSColor *color; + double r, g, b, a; + + value = uiprivTableModelCellValue(self->m, row, self->textModelColumn); + str = uiprivToNSString(uiTableValueString(value)); + uiFreeTableValue(value); + [self->tf setStringValue:str]; + + [self->tf setEditable:uiprivTableModelCellEditable(self->m, row, self->textEditableModelColumn)]; + + color = [NSColor controlTextColor]; + if (uiprivTableModelColorIfProvided(self->m, row, self->textParams.ColorModelColumn, &r, &g, &b, &a)) + color = [NSColor colorWithSRGBRed:r green:g blue:b alpha:a]; + [self->tf setTextColor:color]; + // we don't own color in ether case; don't release + } + if (self->iv != nil) { + uiImage *img; + + value = uiprivTableModelCellValue(self->m, row, self->imageModelColumn); + img = uiTableValueImage(value); + uiFreeTableValue(value); + [self->iv setImage:uiprivImageNSImage(img)]; + } + if (self->cb != nil) { + value = uiprivTableModelCellValue(self->m, row, self->checkboxModelColumn); + if (uiTableValueInt(value) != 0) + [self->cb setState:NSOnState]; + else + [self->cb setState:NSOffState]; + uiFreeTableValue(value); + + [self->cb setEnabled:uiprivTableModelCellEditable(self->m, row, self->checkboxEditableModelColumn)]; + } +} + +- (IBAction)uiprivOnTextFieldAction:(id)sender +{ + NSInteger row; + uiTableValue *value; + + row = [self->t->tv rowForView:self->tf]; + value = uiNewTableValueString([[self->tf stringValue] UTF8String]); + uiprivTableModelSetCellValue(self->m, row, self->textModelColumn, value); + uiFreeTableValue(value); + // always refresh the value in case the model rejected it + // TODO document that we do this, but not for the whole row (or decide to do both, or do neither...) + [self uiprivUpdate:row]; +} + +- (IBAction)uiprivOnCheckboxAction:(id)sender +{ + NSInteger row; + uiTableValue *value; + + row = [self->t->tv rowForView:self->cb]; + value = uiNewTableValueInt([self->cb state] != NSOffState); + uiprivTableModelSetCellValue(self->m, row, self->checkboxModelColumn, value); + uiFreeTableValue(value); + // always refresh the value in case the model rejected it + [self uiprivUpdate:row]; +} + +@end + +@interface uiprivTextImageCheckboxTableColumn : uiprivTableColumn { + struct textColumnCreateParams params; +} +- (id)initWithIdentifier:(NSString *)ident params:(struct textColumnCreateParams *)p; +@end + +@implementation uiprivTextImageCheckboxTableColumn + +- (id)initWithIdentifier:(NSString *)ident params:(struct textColumnCreateParams *)p +{ + self = [super initWithIdentifier:ident]; + if (self) + self->params = *p; + return self; +} + +- (uiprivTableCellView *)uiprivMakeCellView +{ + uiprivTableCellView *cv; + + cv = [[uiprivTextImageCheckboxTableCellView alloc] initWithFrame:NSZeroRect params:&(self->params)]; + [cv setIdentifier:[self identifier]]; + return cv; +} + +@end + +@interface uiprivProgressBarTableCellView : uiprivTableCellView { + uiTable *t; + uiTableModel *m; + NSProgressIndicator *p; + int modelColumn; +} +- (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc; +@end + +@implementation uiprivProgressBarTableCellView + +- (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc +{ + self = [super initWithFrame:r]; + if (self) { + self->t = table; + self->m = model; + self->modelColumn = mc; + + self->p = [[NSProgressIndicator alloc] initWithFrame:NSZeroRect]; + [self->p setControlSize:NSRegularControlSize]; + [self->p setBezeled:YES]; + [self->p setStyle:NSProgressIndicatorBarStyle]; + [self->p setTranslatesAutoresizingMaskIntoConstraints:NO]; + [self addSubview:self->p]; + + // TODO set hugging and compression resistance properly + [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeLeading, + NSLayoutRelationEqual, + self->p, NSLayoutAttributeLeading, + 1, -progressBarColumnLeading, + @"uiTable cell progressbar leading constraint")]; + [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeTop, + NSLayoutRelationEqual, + self->p, NSLayoutAttributeTop, + 1, 0, + @"uiTable cell progressbar top constraint")]; + [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeTrailing, + NSLayoutRelationEqual, + self->p, NSLayoutAttributeTrailing, + 1, progressBarColumnTrailing, + @"uiTable cell progressbar trailing constraint")]; + [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeBottom, + NSLayoutRelationEqual, + self->p, NSLayoutAttributeBottom, + 1, 0, + @"uiTable cell progressbar bottom constraint")]; + } + return self; +} + +- (void)dealloc +{ + [self->p release]; + self->p = nil; + [super dealloc]; +} + +- (void)uiprivUpdate:(NSInteger)row +{ + uiTableValue *value; + int progress; + + value = uiprivTableModelCellValue(self->m, row, self->modelColumn); + progress = uiTableValueInt(value); + uiFreeTableValue(value); + if (progress == -1) { + [self->p setIndeterminate:YES]; + [self->p startAnimation:self->p]; + } else if (progress == 100) { + [self->p setIndeterminate:NO]; + [self->p setMaxValue:101]; + [self->p setDoubleValue:101]; + [self->p setDoubleValue:100]; + [self->p setMaxValue:100]; + } else { + [self->p setIndeterminate:NO]; + [self->p setDoubleValue:(progress + 1)]; + [self->p setDoubleValue:progress]; + } +} + +@end + +@interface uiprivProgressBarTableColumn : uiprivTableColumn { + uiTable *t; + // TODO remove the need for this given t (or make t not require m, one of the two) + uiTableModel *m; + int modelColumn; +} +- (id)initWithIdentifier:(NSString *)ident table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc; +@end + +@implementation uiprivProgressBarTableColumn + +- (id)initWithIdentifier:(NSString *)ident table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc +{ + self = [super initWithIdentifier:ident]; + if (self) { + self->t = table; + self->m = model; + self->modelColumn = mc; + } + return self; +} + +- (uiprivTableCellView *)uiprivMakeCellView +{ + uiprivTableCellView *cv; + + cv = [[uiprivProgressBarTableCellView alloc] initWithFrame:NSZeroRect table:self->t model:self->m modelColumn:self->modelColumn]; + [cv setIdentifier:[self identifier]]; + return cv; +} + +@end + +@interface uiprivButtonTableCellView : uiprivTableCellView { + uiTable *t; + uiTableModel *m; + NSButton *b; + int modelColumn; + int editableColumn; +} +- (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc editableColumn:(int)ec; +- (IBAction)uiprivOnClicked:(id)sender; +@end + +@implementation uiprivButtonTableCellView + +- (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc editableColumn:(int)ec +{ + self = [super initWithFrame:r]; + if (self) { + self->t = table; + self->m = model; + self->modelColumn = mc; + self->editableColumn = ec; + + self->b = [[NSButton alloc] initWithFrame:NSZeroRect]; + [self->b setButtonType:NSMomentaryPushInButton]; + [self->b setBordered:YES]; + [self->b setBezelStyle:NSRoundRectBezelStyle]; + uiDarwinSetControlFont(self->b, NSRegularControlSize); + [self->b setTarget:self]; + [self->b setAction:@selector(uiprivOnClicked:)]; + [self->b setTranslatesAutoresizingMaskIntoConstraints:NO]; + [self addSubview:self->b]; + + // TODO set hugging and compression resistance properly + [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeLeading, + NSLayoutRelationEqual, + self->b, NSLayoutAttributeLeading, + 1, -buttonColumnLeading, + @"uiTable cell button leading constraint")]; + [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeTop, + NSLayoutRelationEqual, + self->b, NSLayoutAttributeTop, + 1, 0, + @"uiTable cell button top constraint")]; + [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeTrailing, + NSLayoutRelationEqual, + self->b, NSLayoutAttributeTrailing, + 1, buttonColumnTrailing, + @"uiTable cell button trailing constraint")]; + [self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeBottom, + NSLayoutRelationEqual, + self->b, NSLayoutAttributeBottom, + 1, 0, + @"uiTable cell button bottom constraint")]; + } + return self; +} + +- (void)dealloc +{ + [self->b release]; + self->b = nil; + [super dealloc]; +} + +- (void)uiprivUpdate:(NSInteger)row +{ + uiTableValue *value; + NSString *str; + + value = uiprivTableModelCellValue(self->m, row, self->modelColumn); + str = uiprivToNSString(uiTableValueString(value)); + uiFreeTableValue(value); + [self->b setTitle:str]; + + [self->b setEnabled:uiprivTableModelCellEditable(self->m, row, self->editableColumn)]; +} + +- (IBAction)uiprivOnClicked:(id)sender +{ + NSInteger row; + + row = [self->t->tv rowForView:self->b]; + uiprivTableModelSetCellValue(self->m, row, self->modelColumn, NULL); + // TODO document we DON'T update the cell after doing this + // TODO or decide what to do instead +} + +@end + +@interface uiprivButtonTableColumn : uiprivTableColumn { + uiTable *t; + uiTableModel *m; + int modelColumn; + int editableColumn; +} +- (id)initWithIdentifier:(NSString *)ident table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc editableColumn:(int)ec; +@end + +@implementation uiprivButtonTableColumn + +- (id)initWithIdentifier:(NSString *)ident table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc editableColumn:(int)ec +{ + self = [super initWithIdentifier:ident]; + if (self) { + self->t = table; + self->m = model; + self->modelColumn = mc; + self->editableColumn = ec; + } + return self; +} + +- (uiprivTableCellView *)uiprivMakeCellView +{ + uiprivTableCellView *cv; + + cv = [[uiprivButtonTableCellView alloc] initWithFrame:NSZeroRect table:self->t model:self->m modelColumn:self->modelColumn editableColumn:self->editableColumn]; + [cv setIdentifier:[self identifier]]; + return cv; +} + +@end + +void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) +{ + struct textColumnCreateParams p; + uiprivTableColumn *col; + NSString *str; + + memset(&p, 0, sizeof (struct textColumnCreateParams)); + p.t = t; + p.m = t->m; + + p.makeTextField = YES; + p.textModelColumn = textModelColumn; + p.textEditableModelColumn = textEditableModelColumn; + if (textParams != NULL) + p.textParams = *textParams; + else + p.textParams = uiprivDefaultTextColumnOptionalParams; + + str = [NSString stringWithUTF8String:name]; + col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p]; + [col setTitle:str]; + [t->tv addTableColumn:col]; +} + +void uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn) +{ + struct textColumnCreateParams p; + uiprivTableColumn *col; + NSString *str; + + memset(&p, 0, sizeof (struct textColumnCreateParams)); + p.t = t; + p.m = t->m; + + p.makeImageView = YES; + p.imageModelColumn = imageModelColumn; + + str = [NSString stringWithUTF8String:name]; + col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p]; + [col setTitle:str]; + [t->tv addTableColumn:col]; +} + +void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) +{ + struct textColumnCreateParams p; + uiprivTableColumn *col; + NSString *str; + + memset(&p, 0, sizeof (struct textColumnCreateParams)); + p.t = t; + p.m = t->m; + + p.makeTextField = YES; + p.textModelColumn = textModelColumn; + p.textEditableModelColumn = textEditableModelColumn; + if (textParams != NULL) + p.textParams = *textParams; + else + p.textParams = uiprivDefaultTextColumnOptionalParams; + + p.makeImageView = YES; + p.imageModelColumn = imageModelColumn; + + str = [NSString stringWithUTF8String:name]; + col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p]; + [col setTitle:str]; + [t->tv addTableColumn:col]; +} + +void uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn) +{ + struct textColumnCreateParams p; + uiprivTableColumn *col; + NSString *str; + + memset(&p, 0, sizeof (struct textColumnCreateParams)); + p.t = t; + p.m = t->m; + + p.makeCheckbox = YES; + p.checkboxModelColumn = checkboxModelColumn; + p.checkboxEditableModelColumn = checkboxEditableModelColumn; + + str = [NSString stringWithUTF8String:name]; + col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p]; + [col setTitle:str]; + [t->tv addTableColumn:col]; +} + +void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) +{ + struct textColumnCreateParams p; + uiprivTableColumn *col; + NSString *str; + + memset(&p, 0, sizeof (struct textColumnCreateParams)); + p.t = t; + p.m = t->m; + + p.makeTextField = YES; + p.textModelColumn = textModelColumn; + p.textEditableModelColumn = textEditableModelColumn; + if (textParams != NULL) + p.textParams = *textParams; + else + p.textParams = uiprivDefaultTextColumnOptionalParams; + + p.makeCheckbox = YES; + p.checkboxModelColumn = checkboxModelColumn; + p.checkboxEditableModelColumn = checkboxEditableModelColumn; + + str = [NSString stringWithUTF8String:name]; + col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p]; + [col setTitle:str]; + [t->tv addTableColumn:col]; +} + +void uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressModelColumn) +{ + uiprivTableColumn *col; + NSString *str; + + str = [NSString stringWithUTF8String:name]; + col = [[uiprivProgressBarTableColumn alloc] initWithIdentifier:str table:t model:t->m modelColumn:progressModelColumn]; + [col setTitle:str]; + [t->tv addTableColumn:col]; +} + +void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonModelColumn, int buttonClickableModelColumn) +{ + uiprivTableColumn *col; + NSString *str; + + str = [NSString stringWithUTF8String:name]; + col = [[uiprivButtonTableColumn alloc] initWithIdentifier:str table:t model:t->m modelColumn:buttonModelColumn editableColumn:buttonClickableModelColumn]; + [col setTitle:str]; + [t->tv addTableColumn:col]; +} diff --git a/darwin/text.m b/darwin/text.m index f0d3dab6..8efd36fe 100644 --- a/darwin/text.m +++ b/darwin/text.m @@ -17,3 +17,8 @@ void uiFreeText(char *s) { free(s); } + +int uiprivStricmp(const char *a, const char *b) +{ + return strcasecmp(a, b); +} diff --git a/darwin/uipriv_darwin.h b/darwin/uipriv_darwin.h index 028ae8a2..5d50f623 100644 --- a/darwin/uipriv_darwin.h +++ b/darwin/uipriv_darwin.h @@ -1,21 +1,41 @@ // 6 january 2015 -#define MAC_OS_X_VERSION_MIN_REQUIRED MAC_OS_X_VERSION_10_7 -#define MAC_OS_X_VERSION_MAX_ALLOWED MAC_OS_X_VERSION_10_7 +// note: as of OS X Sierra, the -mmacosx-version-min compiler options governs deprecation warnings; keep these around anyway just in case +#define MAC_OS_X_VERSION_MIN_REQUIRED MAC_OS_X_VERSION_10_8 +#define MAC_OS_X_VERSION_MAX_ALLOWED MAC_OS_X_VERSION_10_8 #import +#import // see future.m #import "../ui.h" #import "../ui_darwin.h" #import "../common/uipriv.h" +// TODO should we rename the uiprivMk things or not +// TODO what about renaming class wrapper functions that return the underlying class (like uiprivNewLabel()) + #if __has_feature(objc_arc) #error Sorry, libui cannot be compiled with ARC. #endif -#define toNSString(str) [NSString stringWithUTF8String:(str)] -#define fromNSString(str) [(str) UTF8String] +#define uiprivToNSString(str) [NSString stringWithUTF8String:(str)] +#define uiprivFromNSString(str) [(str) UTF8String] + +// TODO find a better place for this +#ifndef NSAppKitVersionNumber10_9 +#define NSAppKitVersionNumber10_9 1265 +#endif + +// map.m +typedef struct uiprivMap uiprivMap; +extern uiprivMap *uiprivNewMap(void); +extern void uiprivMapDestroy(uiprivMap *m); +extern void *uiprivMapGet(uiprivMap *m, void *key); +extern void uiprivMapSet(uiprivMap *m, void *key, void *value); +extern void uiprivMapDelete(uiprivMap *m, void *key); +extern void uiprivMapWalk(uiprivMap *m, void (*f)(void *key, void *value)); +extern void uiprivMapReset(uiprivMap *m); // menu.m -@interface menuManager : NSObject { - struct mapTable *items; +@interface uiprivMenuManager : NSObject { + uiprivMap *items; BOOL hasQuit; BOOL hasPreferences; BOOL hasAbout; @@ -27,38 +47,52 @@ - (BOOL)validateMenuItem:(NSMenuItem *)item; - (NSMenu *)makeMenubar; @end -extern void finalizeMenus(void); -extern void uninitMenus(void); +extern void uiprivFinalizeMenus(void); +extern void uiprivUninitMenus(void); -// init.m -@interface applicationClass : NSApplication +// main.m +@interface uiprivApplicationClass : NSApplication @end // this is needed because NSApp is of type id, confusing clang -#define realNSApp() ((applicationClass *) NSApp) -@interface appDelegate : NSObject -@property (strong) menuManager *menuManager; +#define uiprivNSApp() ((uiprivApplicationClass *) NSApp) +@interface uiprivAppDelegate : NSObject +@property (strong) uiprivMenuManager *menuManager; @end -#define appDelegate() ((appDelegate *) [realNSApp() delegate]) +#define uiprivAppDelegate() ((uiprivAppDelegate *) [uiprivNSApp() delegate]) +typedef struct uiprivNextEventArgs uiprivNextEventArgs; +struct uiprivNextEventArgs { + NSEventMask mask; + NSDate *duration; + // LONGTERM no NSRunLoopMode? + NSString *mode; + BOOL dequeue; +}; +extern int uiprivMainStep(uiprivNextEventArgs *nea, BOOL (^interceptEvent)(NSEvent *)); // util.m -extern void disableAutocorrect(NSTextView *); +extern void uiprivDisableAutocorrect(NSTextView *); // entry.m -extern void finishNewTextField(NSTextField *, BOOL); -extern NSTextField *newEditableTextField(void); +extern void uiprivFinishNewTextField(NSTextField *, BOOL); +extern NSTextField *uiprivNewEditableTextField(void); // window.m -extern uiWindow *windowFromNSWindow(NSWindow *); +@interface uiprivNSWindow : NSWindow +- (void)uiprivDoMove:(NSEvent *)initialEvent; +- (void)uiprivDoResize:(NSEvent *)initialEvent on:(uiWindowResizeEdge)edge; +@end +extern uiWindow *uiprivWindowFromNSWindow(NSWindow *); // alloc.m -extern NSMutableArray *delegates; -extern void initAlloc(void); -extern void uninitAlloc(void); +extern NSMutableArray *uiprivDelegates; +extern void uiprivInitAlloc(void); +extern void uiprivUninitAlloc(void); // autolayout.m -extern NSLayoutConstraint *mkConstraint(id view1, NSLayoutAttribute attr1, NSLayoutRelation relation, id view2, NSLayoutAttribute attr2, CGFloat multiplier, CGFloat c, NSString *desc); -extern void jiggleViewLayout(NSView *view); -struct singleChildConstraints { +extern NSLayoutConstraint *uiprivMkConstraint(id view1, NSLayoutAttribute attr1, NSLayoutRelation relation, id view2, NSLayoutAttribute attr2, CGFloat multiplier, CGFloat c, NSString *desc); +extern void uiprivJiggleViewLayout(NSView *view); +typedef struct uiprivSingleChildConstraints uiprivSingleChildConstraints; +struct uiprivSingleChildConstraints { NSLayoutConstraint *leadingConstraint; NSLayoutConstraint *topConstraint; NSLayoutConstraint *trailingConstraintGreater; @@ -66,46 +100,64 @@ struct singleChildConstraints { NSLayoutConstraint *bottomConstraintGreater; NSLayoutConstraint *bottomConstraintEqual; }; -extern void singleChildConstraintsEstablish(struct singleChildConstraints *c, NSView *contentView, NSView *childView, BOOL hugsTrailing, BOOL hugsBottom, int margined, NSString *desc); -extern void singleChildConstraintsRemove(struct singleChildConstraints *c, NSView *cv); -extern void singleChildConstraintsSetMargined(struct singleChildConstraints *c, int margined); -struct scrollViewConstraints { - NSLayoutConstraint *documentLeading; - NSLayoutConstraint *documentTop; - NSLayoutConstraint *documentTrailing; - NSLayoutConstraint *documentBottom; - NSLayoutConstraint *documentWidth; - NSLayoutConstraint *documentHeight; -}; -extern void scrollViewConstraintsEstablish(struct scrollViewConstraints *c, NSScrollView *sv, NSString *desc); -extern void scrollViewConstraintsRemove(struct scrollViewConstraints *c, NSScrollView *sv); - -// map.m -extern struct mapTable *newMap(void); -extern void mapDestroy(struct mapTable *m); -extern void *mapGet(struct mapTable *m, void *key); -extern void mapSet(struct mapTable *m, void *key, void *value); -extern void mapDelete(struct mapTable *m, void *key); +extern void uiprivSingleChildConstraintsEstablish(uiprivSingleChildConstraints *c, NSView *contentView, NSView *childView, BOOL hugsTrailing, BOOL hugsBottom, int margined, NSString *desc); +extern void uiprivSingleChildConstraintsRemove(uiprivSingleChildConstraints *c, NSView *cv); +extern void uiprivSingleChildConstraintsSetMargined(uiprivSingleChildConstraints *c, int margined); // area.m -extern int sendAreaEvents(NSEvent *); +extern int uiprivSendAreaEvents(NSEvent *); // areaevents.m -extern BOOL fromKeycode(unsigned short keycode, uiAreaKeyEvent *ke); -extern BOOL keycodeModifier(unsigned short keycode, uiModifiers *mod); +extern BOOL uiprivFromKeycode(unsigned short keycode, uiAreaKeyEvent *ke); +extern BOOL uiprivKeycodeModifier(unsigned short keycode, uiModifiers *mod); // draw.m -extern uiDrawContext *newContext(CGContextRef, CGFloat); -extern void freeContext(uiDrawContext *); - -// drawtext.m -extern uiDrawTextFont *mkTextFont(CTFontRef f, BOOL retain); -extern uiDrawTextFont *mkTextFontFromNSFont(NSFont *f); -extern void doDrawText(CGContextRef c, CGFloat cheight, double x, double y, uiDrawTextLayout *layout); +extern uiDrawContext *uiprivDrawNewContext(CGContextRef, CGFloat); +extern void uiprivDrawFreeContext(uiDrawContext *); // fontbutton.m -extern BOOL fontButtonInhibitSendAction(SEL sel, id from, id to); -extern BOOL fontButtonOverrideTargetForAction(SEL sel, id from, id to, id *override); +extern BOOL uiprivFontButtonInhibitSendAction(SEL sel, id from, id to); +extern BOOL uiprivFontButtonOverrideTargetForAction(SEL sel, id from, id to, id *override); +extern void uiprivSetupFontPanel(void); -// MASSIVE TODO -#define uiDarwinControlTriggerRelayout(...) +// colorbutton.m +extern BOOL uiprivColorButtonInhibitSendAction(SEL sel, id from, id to); + +// scrollview.m +typedef struct uiprivScrollViewCreateParams uiprivScrollViewCreateParams; +struct uiprivScrollViewCreateParams { + // TODO MAYBE fix these identifiers + NSView *DocumentView; + NSColor *BackgroundColor; + BOOL DrawsBackground; + BOOL Bordered; + BOOL HScroll; + BOOL VScroll; +}; +typedef struct uiprivScrollViewData uiprivScrollViewData; +extern NSScrollView *uiprivMkScrollView(uiprivScrollViewCreateParams *p, uiprivScrollViewData **dout); +extern void uiprivScrollViewSetScrolling(NSScrollView *sv, uiprivScrollViewData *d, BOOL hscroll, BOOL vscroll); +extern void uiprivScrollViewFreeData(NSScrollView *sv, uiprivScrollViewData *d); + +// label.m +extern NSTextField *uiprivNewLabel(NSString *str); + +// image.m +extern NSImage *uiprivImageNSImage(uiImage *); + +// winmoveresize.m +extern void uiprivDoManualMove(NSWindow *w, NSEvent *initialEvent); +extern void uiprivDoManualResize(NSWindow *w, NSEvent *initialEvent, uiWindowResizeEdge edge); + +// future.m +extern CFStringRef *uiprivFUTURE_kCTFontOpenTypeFeatureTag; +extern CFStringRef *uiprivFUTURE_kCTFontOpenTypeFeatureValue; +extern CFStringRef *uiprivFUTURE_kCTBackgroundColorAttributeName; +extern void uiprivLoadFutures(void); +extern void uiprivFUTURE_NSLayoutConstraint_setIdentifier(NSLayoutConstraint *constraint, NSString *identifier); +extern BOOL uiprivFUTURE_NSWindow_performWindowDragWithEvent(NSWindow *w, NSEvent *initialEvent); + +// undocumented.m +extern CFStringRef uiprivUNDOC_kCTFontPreferredSubFamilyNameKey; +extern CFStringRef uiprivUNDOC_kCTFontPreferredFamilyNameKey; +extern void uiprivLoadUndocumented(void); diff --git a/darwin/undocumented.m b/darwin/undocumented.m new file mode 100644 index 00000000..a3984aa8 --- /dev/null +++ b/darwin/undocumented.m @@ -0,0 +1,31 @@ +// 3 november 2017 +#import "uipriv_darwin.h" + +// functions and constants FROM THE DEPTHS BELOW! +// note: for constants, dlsym() returns the address of the constant itself, as if we had done &constantName +// we also provide default values just in case + +// these values come from 10.12.6 +CFStringRef uiprivUNDOC_kCTFontPreferredSubFamilyNameKey = CFSTR("CTFontPreferredSubFamilyName"); +CFStringRef uiprivUNDOC_kCTFontPreferredFamilyNameKey = CFSTR("CTFontPreferredFamilyName"); + +// note that we treat any error as "the symbols aren't there" (and don't care if dlclose() failed) +void uiprivLoadUndocumented(void) +{ + void *handle; + CFStringRef *str; + + // dlsym() walks the dependency chain, so opening the current process should be sufficient + handle = dlopen(NULL, RTLD_LAZY); + if (handle == NULL) + return; +#define GET(var, fn) *((void **) (&var)) = dlsym(handle, #fn) + GET(str, kCTFontPreferredSubFamilyNameKey); +NSLog(@"get %p", str); + if (str != NULL) + uiprivUNDOC_kCTFontPreferredSubFamilyNameKey = *str; + GET(str, kCTFontPreferredFamilyNameKey); + if (str != NULL) + uiprivUNDOC_kCTFontPreferredFamilyNameKey = *str; + dlclose(handle); +} diff --git a/darwin/util.m b/darwin/util.m index fa6f5994..418d958e 100644 --- a/darwin/util.m +++ b/darwin/util.m @@ -1,8 +1,9 @@ // 7 april 2015 #import "uipriv_darwin.h" -// TODO do we really want to do this? make it an option? -void disableAutocorrect(NSTextView *tv) +// LONGTERM do we really want to do this? make it an option? +// TODO figure out why we removed this from window.m +void uiprivDisableAutocorrect(NSTextView *tv) { [tv setEnabledTextCheckingTypes:0]; [tv setAutomaticDashSubstitutionEnabled:NO]; @@ -13,15 +14,3 @@ void disableAutocorrect(NSTextView *tv) [tv setAutomaticLinkDetectionEnabled:NO]; [tv setSmartInsertDeleteEnabled:NO]; } - -void complain(const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - fprintf(stderr, "[libui] "); - vfprintf(stderr, fmt, ap); - fprintf(stderr, "\n"); - va_end(ap); - abort(); -} diff --git a/darwin/window.m b/darwin/window.m index a36cff83..1a048207 100644 --- a/darwin/window.m +++ b/darwin/window.m @@ -1,6 +1,8 @@ // 15 august 2015 #import "uipriv_darwin.h" +#define defaultStyleMask (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask) + struct uiWindow { uiDarwinControl c; NSWindow *window; @@ -8,13 +10,35 @@ struct uiWindow { int margined; int (*onClosing)(uiWindow *, void *); void *onClosingData; - struct singleChildConstraints constraints; + uiprivSingleChildConstraints constraints; + void (*onContentSizeChanged)(uiWindow *, void *); + void *onContentSizeChangedData; + BOOL suppressSizeChanged; + int fullscreen; + int borderless; }; +@implementation uiprivNSWindow + +- (void)uiprivDoMove:(NSEvent *)initialEvent +{ + uiprivDoManualMove(self, initialEvent); +} + +- (void)uiprivDoResize:(NSEvent *)initialEvent on:(uiWindowResizeEdge)edge +{ + uiprivDoManualResize(self, initialEvent, edge); +} + +@end + @interface windowDelegateClass : NSObject { - struct mapTable *windows; + uiprivMap *windows; } - (BOOL)windowShouldClose:(id)sender; +- (void)windowDidResize:(NSNotification *)note; +- (void)windowDidEnterFullScreen:(NSNotification *)note; +- (void)windowDidExitFullScreen:(NSNotification *)note; - (void)registerWindow:(uiWindow *)w; - (void)unregisterWindow:(uiWindow *)w; - (uiWindow *)lookupWindow:(NSWindow *)w; @@ -26,13 +50,13 @@ struct uiWindow { { self = [super init]; if (self) - self->windows = newMap(); + self->windows = uiprivNewMap(); return self; } - (void)dealloc { - mapDestroy(self->windows); + uiprivMapDestroy(self->windows); [super dealloc]; } @@ -47,23 +71,50 @@ struct uiWindow { return NO; } +- (void)windowDidResize:(NSNotification *)note +{ + uiWindow *w; + + w = [self lookupWindow:((NSWindow *) [note object])]; + if (!w->suppressSizeChanged) + (*(w->onContentSizeChanged))(w, w->onContentSizeChangedData); +} + +- (void)windowDidEnterFullScreen:(NSNotification *)note +{ + uiWindow *w; + + w = [self lookupWindow:((NSWindow *) [note object])]; + if (!w->suppressSizeChanged) + w->fullscreen = 1; +} + +- (void)windowDidExitFullScreen:(NSNotification *)note +{ + uiWindow *w; + + w = [self lookupWindow:((NSWindow *) [note object])]; + if (!w->suppressSizeChanged) + w->fullscreen = 0; +} + - (void)registerWindow:(uiWindow *)w { - mapSet(self->windows, w->window, w); + uiprivMapSet(self->windows, w->window, w); [w->window setDelegate:self]; } - (void)unregisterWindow:(uiWindow *)w { [w->window setDelegate:nil]; - mapDelete(self->windows, w->window); + uiprivMapDelete(self->windows, w->window); } - (uiWindow *)lookupWindow:(NSWindow *)w { uiWindow *v; - v = uiWindow(mapGet(self->windows, w)); + v = uiWindow(uiprivMapGet(self->windows, w)); // this CAN (and IS ALLOWED TO) return NULL, just in case we're called with some OS X-provided window as the key window return v; } @@ -77,7 +128,7 @@ static void removeConstraints(uiWindow *w) NSView *cv; cv = [w->window contentView]; - singleChildConstraintsRemove(&(w->constraints), cv); + uiprivSingleChildConstraintsRemove(&(w->constraints), cv); } static void uiWindowDestroy(uiControl *c) @@ -93,16 +144,21 @@ static void uiWindowDestroy(uiControl *c) uiControlDestroy(w->child); } [windowDelegate unregisterWindow:w]; - // TODO make sure this next line is right [w->window release]; uiFreeControl(uiControl(w)); } uiDarwinControlDefaultHandle(uiWindow, window) -// TODO? -uiDarwinControlDefaultParent(uiWindow, window) -uiDarwinControlDefaultSetParent(uiWindow, window) -// end TODO + +uiControl *uiWindowParent(uiControl *c) +{ + return NULL; +} + +void uiWindowSetParent(uiControl *c, uiControl *parent) +{ + uiUserBugCannotSetParentOnToplevel("uiWindow"); +} static int uiWindowToplevel(uiControl *c) { @@ -159,7 +215,7 @@ static void windowRelayout(uiWindow *w) return; childView = (NSView *) uiControlHandle(w->child); contentView = [w->window contentView]; - singleChildConstraintsEstablish(&(w->constraints), + uiprivSingleChildConstraintsEstablish(&(w->constraints), contentView, childView, uiDarwinControlHugsTrailingEdge(uiDarwinControl(w->child)), uiDarwinControlHugsBottom(uiDarwinControl(w->child)), @@ -182,6 +238,13 @@ uiDarwinControlDefaultHuggingPriority(uiWindow, window) uiDarwinControlDefaultSetHuggingPriority(uiWindow, window) // end TODO +static void uiWindowChildVisibilityChanged(uiDarwinControl *c) +{ + uiWindow *w = uiWindow(c); + + windowRelayout(w); +} + char *uiWindowTitle(uiWindow *w) { return uiDarwinNSStringToText([w->window title]); @@ -189,7 +252,50 @@ char *uiWindowTitle(uiWindow *w) void uiWindowSetTitle(uiWindow *w, const char *title) { - [w->window setTitle:toNSString(title)]; + [w->window setTitle:uiprivToNSString(title)]; +} + +void uiWindowContentSize(uiWindow *w, int *width, int *height) +{ + NSRect r; + + r = [w->window contentRectForFrameRect:[w->window frame]]; + *width = r.size.width; + *height = r.size.height; +} + +void uiWindowSetContentSize(uiWindow *w, int width, int height) +{ + w->suppressSizeChanged = YES; + [w->window setContentSize:NSMakeSize(width, height)]; + w->suppressSizeChanged = NO; +} + +int uiWindowFullscreen(uiWindow *w) +{ + return w->fullscreen; +} + +void uiWindowSetFullscreen(uiWindow *w, int fullscreen) +{ + if (w->fullscreen && fullscreen) + return; + if (!w->fullscreen && !fullscreen) + return; + w->fullscreen = fullscreen; + if (w->fullscreen && w->borderless) // borderless doesn't play nice with fullscreen; don't toggle while borderless + return; + w->suppressSizeChanged = YES; + [w->window toggleFullScreen:w->window]; + w->suppressSizeChanged = NO; + if (!w->fullscreen && w->borderless) // borderless doesn't play nice with fullscreen; restore borderless after removing + [w->window setStyleMask:NSBorderlessWindowMask]; +} + +void uiWindowOnContentSizeChanged(uiWindow *w, void (*f)(uiWindow *, void *), void *data) +{ + w->onContentSizeChanged = f; + w->onContentSizeChangedData = data; } void uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *, void *), void *data) @@ -198,6 +304,29 @@ void uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *, void *), void *data) w->onClosingData = data; } +int uiWindowBorderless(uiWindow *w) +{ + return w->borderless; +} + +void uiWindowSetBorderless(uiWindow *w, int borderless) +{ + w->borderless = borderless; + if (w->borderless) { + // borderless doesn't play nice with fullscreen; wait for later + if (!w->fullscreen) + [w->window setStyleMask:NSBorderlessWindowMask]; + } else { + [w->window setStyleMask:defaultStyleMask]; + // borderless doesn't play nice with fullscreen; restore state + if (w->fullscreen) { + w->suppressSizeChanged = YES; + [w->window toggleFullScreen:w->window]; + w->suppressSizeChanged = NO; + } + } +} + void uiWindowSetChild(uiWindow *w, uiControl *child) { NSView *childView; @@ -225,8 +354,7 @@ int uiWindowMargined(uiWindow *w) void uiWindowSetMargined(uiWindow *w, int margined) { w->margined = margined; - singleChildConstraintsSetMargined(&(w->constraints), w->margined); - // TODO issue a relayout command? + uiprivSingleChildConstraintsSetMargined(&(w->constraints), w->margined); } static int defaultOnClosing(uiWindow *w, void *data) @@ -234,36 +362,42 @@ static int defaultOnClosing(uiWindow *w, void *data) return 0; } +static void defaultOnPositionContentSizeChanged(uiWindow *w, void *data) +{ + // do nothing +} + uiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar) { uiWindow *w; - finalizeMenus(); + uiprivFinalizeMenus(); uiDarwinNewControl(uiWindow, w); - w->window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, (CGFloat) width, (CGFloat) height) - styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask) + w->window = [[uiprivNSWindow alloc] initWithContentRect:NSMakeRect(0, 0, (CGFloat) width, (CGFloat) height) + styleMask:defaultStyleMask backing:NSBackingStoreBuffered defer:YES]; - [w->window setTitle:toNSString(title)]; + [w->window setTitle:uiprivToNSString(title)]; - // explicitly release when closed - // the only thing that closes the window is us anyway - [w->window setReleasedWhenClosed:YES]; + // do NOT release when closed + // we manually do this in uiWindowDestroy() above + [w->window setReleasedWhenClosed:NO]; if (windowDelegate == nil) { - windowDelegate = [windowDelegateClass new]; - [delegates addObject:windowDelegate]; + windowDelegate = [[windowDelegateClass new] autorelease]; + [uiprivDelegates addObject:windowDelegate]; } [windowDelegate registerWindow:w]; uiWindowOnClosing(w, defaultOnClosing, NULL); + uiWindowOnContentSizeChanged(w, defaultOnPositionContentSizeChanged, NULL); return w; } // utility function for menus -uiWindow *windowFromNSWindow(NSWindow *w) +uiWindow *uiprivWindowFromNSWindow(NSWindow *w) { if (w == nil) return NULL; diff --git a/darwin/winmoveresize.m b/darwin/winmoveresize.m new file mode 100644 index 00000000..efb61eae --- /dev/null +++ b/darwin/winmoveresize.m @@ -0,0 +1,253 @@ +// 1 november 2016 +#import "uipriv_darwin.h" + +// TODO option while resizing resizes both opposing sides at once (thanks swillits in irc.freenode.net/#macdev for showing this to me); figure out how far back that behavior goes when we do implement it + +// because we are changing the window frame each time the mouse moves, the successive -[NSEvent locationInWindow]s cannot be meaningfully used together +// make sure they are all following some sort of standard to avoid this problem; the screen is the most obvious possibility since it requires only one conversion (the only one that a NSWindow provides) +static NSPoint makeIndependent(NSPoint p, NSWindow *w) +{ + NSRect r; + + r.origin = p; + // mikeash in irc.freenode.net/#macdev confirms both that any size will do and that we can safely ignore the resultant size + r.size = NSZeroSize; + return [w convertRectToScreen:r].origin; +} + +struct onMoveDragParams { + NSWindow *w; + // using the previous point causes weird issues like the mouse seeming to fall behind the window edge... so do this instead + // TODO will this make things like the menubar and dock easier too? + NSRect initialFrame; + NSPoint initialPoint; +}; + +void onMoveDrag(struct onMoveDragParams *p, NSEvent *e) +{ + NSPoint new; + NSRect frame; + CGFloat offx, offy; + + new = makeIndependent([e locationInWindow], p->w); + frame = p->initialFrame; + + offx = new.x - p->initialPoint.x; + offy = new.y - p->initialPoint.y; + frame.origin.x += offx; + frame.origin.y += offy; + + // TODO handle the menubar + // TODO wait the system does this for us already?! + + [p->w setFrameOrigin:frame.origin]; +} + +void uiprivDoManualMove(NSWindow *w, NSEvent *initialEvent) +{ + __block struct onMoveDragParams mdp; + uiprivNextEventArgs nea; + BOOL (^handleEvent)(NSEvent *e); + __block BOOL done; + + // 10.11 gives us a method to handle this for us + // use it if available; this lets us use the real OS dragging code, which means we can take advantage of OS features like Spaces + if (uiprivFUTURE_NSWindow_performWindowDragWithEvent(w, initialEvent)) + return; + + mdp.w = w; + mdp.initialFrame = [mdp.w frame]; + mdp.initialPoint = makeIndependent([initialEvent locationInWindow], mdp.w); + + nea.mask = NSLeftMouseDraggedMask | NSLeftMouseUpMask; + nea.duration = [NSDate distantFuture]; + nea.mode = NSEventTrackingRunLoopMode; // nextEventMatchingMask: docs suggest using this for manual mouse tracking + nea.dequeue = YES; + handleEvent = ^(NSEvent *e) { + if ([e type] == NSLeftMouseUp) { + done = YES; + return YES; // do not send + } + onMoveDrag(&mdp, e); + return YES; // do not send + }; + done = NO; + while (uiprivMainStep(&nea, handleEvent)) + if (done) + break; +} + +// see http://stackoverflow.com/a/40352996/3408572 +static void minMaxAutoLayoutSizes(NSWindow *w, NSSize *min, NSSize *max) +{ + NSLayoutConstraint *cw, *ch; + NSView *contentView; + NSRect prevFrame; + + // if adding these constraints causes the window to change size somehow, don't show it to the user and change it back afterwards + NSDisableScreenUpdates(); + prevFrame = [w frame]; + + // minimum: encourage the window to be as small as possible + contentView = [w contentView]; + cw = uiprivMkConstraint(contentView, NSLayoutAttributeWidth, + NSLayoutRelationEqual, + nil, NSLayoutAttributeNotAnAttribute, + 0, 0, + @"window minimum width finding constraint"); + [cw setPriority:NSLayoutPriorityDragThatCanResizeWindow]; + [contentView addConstraint:cw]; + ch = uiprivMkConstraint(contentView, NSLayoutAttributeHeight, + NSLayoutRelationEqual, + nil, NSLayoutAttributeNotAnAttribute, + 0, 0, + @"window minimum height finding constraint"); + [ch setPriority:NSLayoutPriorityDragThatCanResizeWindow]; + [contentView addConstraint:ch]; + *min = [contentView fittingSize]; + [contentView removeConstraint:cw]; + [contentView removeConstraint:ch]; + + // maximum: encourage the window to be as large as possible + contentView = [w contentView]; + cw = uiprivMkConstraint(contentView, NSLayoutAttributeWidth, + NSLayoutRelationEqual, + nil, NSLayoutAttributeNotAnAttribute, + 0, CGFLOAT_MAX, + @"window maximum width finding constraint"); + [cw setPriority:NSLayoutPriorityDragThatCanResizeWindow]; + [contentView addConstraint:cw]; + ch = uiprivMkConstraint(contentView, NSLayoutAttributeHeight, + NSLayoutRelationEqual, + nil, NSLayoutAttributeNotAnAttribute, + 0, CGFLOAT_MAX, + @"window maximum height finding constraint"); + [ch setPriority:NSLayoutPriorityDragThatCanResizeWindow]; + [contentView addConstraint:ch]; + *max = [contentView fittingSize]; + [contentView removeConstraint:cw]; + [contentView removeConstraint:ch]; + + [w setFrame:prevFrame display:YES]; // TODO really YES? + NSEnableScreenUpdates(); +} + +static void handleResizeLeft(NSRect *frame, NSPoint old, NSPoint new) +{ + frame->origin.x += new.x - old.x; + frame->size.width -= new.x - old.x; +} + +// TODO properly handle the menubar +// TODO wait, OS X does it for us?! +static void handleResizeTop(NSRect *frame, NSPoint old, NSPoint new) +{ + frame->size.height += new.y - old.y; +} + +static void handleResizeRight(NSRect *frame, NSPoint old, NSPoint new) +{ + frame->size.width += new.x - old.x; +} + + +// TODO properly handle the menubar +static void handleResizeBottom(NSRect *frame, NSPoint old, NSPoint new) +{ + frame->origin.y += new.y - old.y; + frame->size.height -= new.y - old.y; +} + +struct onResizeDragParams { + NSWindow *w; + // using the previous point causes weird issues like the mouse seeming to fall behind the window edge... so do this instead + // TODO will this make things like the menubar and dock easier too? + NSRect initialFrame; + NSPoint initialPoint; + uiWindowResizeEdge edge; + NSSize min; + NSSize max; +}; + +static void onResizeDrag(struct onResizeDragParams *p, NSEvent *e) +{ + NSPoint new; + NSRect frame; + + new = makeIndependent([e locationInWindow], p->w); + frame = p->initialFrame; + + // horizontal + switch (p->edge) { + case uiWindowResizeEdgeLeft: + case uiWindowResizeEdgeTopLeft: + case uiWindowResizeEdgeBottomLeft: + handleResizeLeft(&frame, p->initialPoint, new); + break; + case uiWindowResizeEdgeRight: + case uiWindowResizeEdgeTopRight: + case uiWindowResizeEdgeBottomRight: + handleResizeRight(&frame, p->initialPoint, new); + break; + } + // vertical + switch (p->edge) { + case uiWindowResizeEdgeTop: + case uiWindowResizeEdgeTopLeft: + case uiWindowResizeEdgeTopRight: + handleResizeTop(&frame, p->initialPoint, new); + break; + case uiWindowResizeEdgeBottom: + case uiWindowResizeEdgeBottomLeft: + case uiWindowResizeEdgeBottomRight: + handleResizeBottom(&frame, p->initialPoint, new); + break; + } + + // constrain + // TODO should we constrain against anything else as well? minMaxAutoLayoutSizes() already gives us nonnegative sizes, but... + if (frame.size.width < p->min.width) + frame.size.width = p->min.width; + if (frame.size.height < p->min.height) + frame.size.height = p->min.height; + // TODO > or >= ? + if (frame.size.width > p->max.width) + frame.size.width = p->max.width; + if (frame.size.height > p->max.height) + frame.size.height = p->max.height; + + [p->w setFrame:frame display:YES]; // and do reflect the new frame immediately +} + +// TODO do our events get fired with this? *should* they? +void uiprivDoManualResize(NSWindow *w, NSEvent *initialEvent, uiWindowResizeEdge edge) +{ + __block struct onResizeDragParams rdp; + uiprivNextEventArgs nea; + BOOL (^handleEvent)(NSEvent *e); + __block BOOL done; + + rdp.w = w; + rdp.initialFrame = [rdp.w frame]; + rdp.initialPoint = makeIndependent([initialEvent locationInWindow], rdp.w); + rdp.edge = edge; + // TODO what happens if these change during the loop? + minMaxAutoLayoutSizes(rdp.w, &(rdp.min), &(rdp.max)); + + nea.mask = NSLeftMouseDraggedMask | NSLeftMouseUpMask; + nea.duration = [NSDate distantFuture]; + nea.mode = NSEventTrackingRunLoopMode; // nextEventMatchingMask: docs suggest using this for manual mouse tracking + nea.dequeue = YES; + handleEvent = ^(NSEvent *e) { + if ([e type] == NSLeftMouseUp) { + done = YES; + return YES; // do not send + } + onResizeDrag(&rdp, e); + return YES; // do not send + }; + done = NO; + while (uiprivMainStep(&nea, handleEvent)) + if (done) + break; +} diff --git a/doc/areahandler b/doc/areahandler new file mode 100644 index 00000000..4c559db5 --- /dev/null +++ b/doc/areahandler @@ -0,0 +1 @@ +Yes, you keep ownership of the uiAreaHandler. libui only cares about the address you give uiNewArea(); it doesn't copy anything. You can even use the same uiAreaHandler on multiple uiAreas, which is why you get the uiArea as a parameter in each function. diff --git a/doc/drawtext b/doc/drawtext new file mode 100644 index 00000000..9d377135 --- /dev/null +++ b/doc/drawtext @@ -0,0 +1,13 @@ +on some unix systems, alpha blending fonts may not be available; this depends on your installed version of pango and is determined at runtime by libui + +uiDrawTextLayoutExtents: document that the extent width can be greater than the requested width if the requested width is small enough that only one character can fit + + +font matching is closest match but the search method is OS defined + + +weight names in libui do not necessarily line up with their OS names + + +uiDrawFontHandle() may not return a unique handle per instance + diff --git a/doc/export/ctweights b/doc/export/ctweights new file mode 100644 index 00000000..0bdbd0d4 --- /dev/null +++ b/doc/export/ctweights @@ -0,0 +1,750 @@ +// pseudo-go + +func (f *CTFont) IsRegistered() bool { + n := f.Attribute(kCTFontRegistrationScopeAttribute) + if n == nil { + return false + } + return n.(*CFNumber).Uint32Value() == kCTFontManagerScopeNone +} + +// this type is in libFontRegistry.dylib; functions like x_list.Prepend() are called things like x_list_prepend() there +type x_list struct { + Data interface{} + Next *x_list +} + +func (x *x_list) Prepend(data interface{}) *x_list { + y := malloc(sizeof (x_list)) + if y != nil { + y.data = data + y.next = x + return y + } + return x +} + +func (x *x_list) Reverse() *x_list { + if x == nil { + return nil + } + + var old, next *x_list + + next = nil + for { + old = x + x = old.next + old.next = next + next = old + if x == nil { + break + } + } + return old +} + +func (x *x_list) Concat(y *x_list) *x_list { + if x == nil { + return y + } + start := x + z := x + for { + x = z + z = z.next + if z == nil { + break + } + } + x.next = y + return start +} + +// based on CoreGraphics dylib's _CGFontCopyName +// note that this is different from the public API function CGFontCopyPostScriptName() (which is font type-independent) +// also note that in reality these keys are strings but the implementation of the function turns them into ints and only uses them as such +const ( + kCGFontNameKeyPostScriptName = 0x6 + kCGFontNameKeyPreferredSubfamily = 0x11 + kCGFontNameKeyFontSubfamily = 0x2 + kCGFontNameKeyFullName = 0x4 + kCGFontNameKeyPreferredFamily = 0x10 + kCGFontNameKeyFontFamily = 0x1 +) +func (f *CGFont) CopyName(key int) (string, bool) { + table := f.TableForTag('name') + b := table.Bytes() + n := table.Len() + + // this code looks weird, but we're imitating the assembly, or the effective effects thereof + offCount := uint16(0) + offStringOffset := uint16(2) + if n > 1 { + offCount = 2 + offStringOffset = 4 + } + + count := uint16(0) + if int(offCount) <= n { + count = uint16be(b[offCount:offCount + 2]) + } + + offNameRecord := offStringOffset + 2 + stringOffset := uint16(0) + if int(offNameRecord) <= n { + stringOffset = uint16be(b[offStringOffset:offStringOffset + 2]) + } + + type NameRecord struct { + PlatformID uint16 + PlatformSpecificID uint16 + LanguageID uint16 + NameID uint16 + Length uint16 + Offset uint16 + } + + var nameList *x_list + + addrStrings := offNameRecords + (12 * count) + if addrStrings != stringOffset { + goto hasLanguageTags + } + pos := offNameRecords + if count == 0 { + // TODO note assembly logic here + } else { + for { + var nr NameRecord + + nr.PlatformID = 0 + next := pos + 2 + if int(next) <= n { + nr.PlatformID = uint16be(b[pos:pos + 2]) + pos = next + } + + nr.PlatformSpecificID = 0 + next = pos + 2 + if int(next) <= n { + nr.PlatformSpecificID = uint16be(b[pos:pos + 2]) + pos = next + } + + nr.LanguageID = 0 + next = pos + 2 + if int(next) <= n { + nr.LanguageID = uint16be(b[pos:pos + 2]) + pos = next + } + + nr.NameID = 0 + next = pos + 2 + if int(next) <= n { + nr.NameID = uint16be(b[pos:pos + 2]) + pos = next + } + + nr.Length = 0 + next = pos + 2 + if int(next) <= n { + nr.Length = uint16be(b[pos:pos + 2]) + pos = next + } + + nr.Offset = 0 + next = pos + 2 + if int(next) <= n { + nr.Offset = uint16be(b[pos:pos + 2]) + pos = next + } + + strpos := stringOffset + nr.Offset + if strpos >= n { + // TODO put comment about imitating the assembly comparisons here + } else { + realLen := nr.Length + strend = strpos + nr.Length + if strend > n { + realLen = nr.Length - strpos + strend = strpos + realLen + } + b := malloc(12 + realLen + 1) + if b != nil { + name := (*sfnt_name_t)(b) + name.PlatformID = nr.PlatformID + name.PlatformSpecificID = nr.PlatformSpecificID + name.LanguageID = nr.LanguageID + name.NameID = nr.NameID + name.Length = realLen + memcpy(&(name.Name), b[strpos:strend], realLen) + name.Name[realLen] = 0 + nameList = nameList.Prepend(name) + } + } + count-- + if count == 0 { + break + } + } + } + nameList = nameList.Reverse() + +hasLanguageTags: + add_localized_names := func(platformID uint16, platformSpecificID uint16, to *x_list) *x_list { + out := (*x_list)(nil) + if nameList == nil { + xx TODO logic verbatim etc. + } else { + x := nameList + for { + name := (*sfnt_name_t)(x.data) + if name.PlatformID != platformID { + xx TODO + } else { + if platformSpecificID == 0xFFFF || name.PlatformSpecificID == platformSpecificID { + out = out.Prepend(name) + } + } + x = x.next + if x == nil { + break + } + } + } + out = out.Reverse() + return to.Concat(out) + } + localized := (*x_list)(nil) + localized = add_localized_names(0x1, 0xFFFF, localized) + localized = add_localized_names(0, 0xFFFF, localized) + localized = add_localized_names(0x3, 0xFFFF, localized) + localized = add_localized_names(0x1, 0, localized) + localized = add_localized_names(0x3, 0x9, localized) + localized = add_localized_names(0x3, 0x409, localized) + + sysLocale := CFLocaleGetSystem() + +} + +// based on libFontRegistry.dylib's __ZNK8OS2Table15DetermineWeightERf — OS2Table::DetermineWeight(float&) const +func RegistryDetermineOS2Weight(table *CFData) (float32, bool) { + if table == nil { + return 0, false + } + if table.Len() < 78 { + return 0, false + } + + b := table.Bytes() + usWeightClass := uint16be(b[4:6]) + if usWeightClass >= 10 { + // do nothing; we are preserving the original asm comparisons + } else { + usWeightClass *= 100 + } + /* TODO: +000000000000b37e mov dx, word [rax+4] +000000000000b382 mov cx, dx +000000000000b385 rol cx, 0x8 +000000000000b389 movzx esi, cx +000000000000b38c imul ecx, ecx, 100 +000000000000b38f cmp esi, 10 +000000000000b392 cmovae cx, si +000000000000b396 test dx, dx +000000000000b399 cmove cx, si + what's the function of the last two instructions? */ + + // note that this is an unsigned comparison, so underflow will result in a number > 998 + // the effect is the same as (usWeightClass == 0) || (usWeightClass >= 1000) + if (usWeightClass - 1) > 998 { + // note the - 2 here; the switch cases below reflect that! + // also note that b[0x22] and panose will be unsigned, so underflow will result in a number > 9 + panose := b[0x22] - 2 + if panose > 9 { + return 0, false + } + switch panose { + case 0: + return float32as(-0.500000, 0xbf000000), true + case 1: + return float32as(-0.400000, 0xbecccccd), true + case 2: + // yes, this returns false; I don't know why + return float32as(-0.300000, 0xbe99999a), false + case 3: + return float32as(-0.230000, 0xbe6b851f), true + case 4: + return float32as(0.230000, 0x3e6b851f), true + case 5: + return float32as(0.250000, 0x3e800000), true + case 6: + return float32as(0.400000, 0x3ecccccd), true + case 7: + return float32as(0.560000, 0x3f0f5c29), true + case 8: + return float32as(0.620000, 0x3f1eb852), true + case 9: + return float32as(0.800000, 0x3f4ccccd), true + } + // should not reach here + } + + // let's mimic the assembly here too + // the gotos avoid the massive if nesting + // also note I'm using Go idioms and not saying "else return", imagine those if you must + if usWeightClass > 100 { + if usWeightClass > 200 { + goto do201AndUp + } + return float32as(-0.500000, 0xbf000000), true + } + return float32as(-0.800000, 0xbf4ccccd), true + +do201AndUp: + if usWeightClass > 300 { + if usWeightClass > 400 { + goto do401AndUp + } + return float32as(0.000000, 0x0), true + } + return float32as(-0.400000, 0xbecccccd), true + +do401AndUp: + if usWeightClass > 500 { + if usWeightClass > 600 { + goto do601AndUp + } + return float32as(0.250000, 0x3e800000), true + } + return float32as(0.230000, 0x3e6b851f), true + +do601AndUp: + if usWeightClass > 700 { + if usWeightClass > 800 { + goto do801AndUp + } + return float32as(0.500000, 0x3f000000), true + } + return float32as(0.400000, 0x3ecccccd), true + +do801AndUp: + if usWeightClass > 900 { + if usWeightClass > 950 { + return float32(0.800000, 0x3f4ccccd), true + } + return float32(0.750000, 0x3f400000), true + } + return float32as(0.620000, 0x3f1eb852), true +} + +// based on libFontRegistry.dylib's __ZN11TFontTraitsC2EP6CGFontRK13TFontMetadata — TFontTraits::TFontTraits(CGFont*, TFontMetadata const&) +func (f *Font) WeightFromFontRegistry32() float32 { + var weight float32 + var hasWeight bool = false + + cgfont := f.CGFont() + if f.RegistryHasMetadata() { + wv := f.RegistryMetadataValueForKey("MTD_Typeface_Weight_VisualDescriptor") + if wv != nil { + if wn, ok := wv.(string); ok { + // note: uses CFStringCompare(0) + switch wn { + case "reg": + weight = float32as(0.000000, 0x0) + hasWeight = true + case "semi": + weight = float32as(0.300000, 0x3e99999a) + hasWeight = true + case "bold": + weight = float32as(0.400000, 0x3ecccccd) + hasWeight = true + case "light": + weight = float32as(-0.400000, 0xbecccccd) + hasWeight = true + case "med": + weight = float32as(0.230000, 0x3e6b851f) + hasWeight = true + case "heavy": + weight = float32as(0.560000, 0x3f0f5c29) + hasWeight = true + case "black": + weight = float32as(0.620000, 0x3f1eb852) + hasWeight = true + case "thin": + weight = float32as(-0.600000, 0xbf19999a) + hasWeight = true + case "ulight": + weight = float32as(-0.800000, 0xbf4ccccd) + hasWeight = true + } + } + } + } + + cgpsname, ok := cgfont.CopyName(kCGFontNameKeyPostScriptName) + if ok { + // note: uses CFStringCompare(0) + switch cgpsname { + case "LucidaGrande", + ".LucidaGrandeUI", + ".Keyboard": + weight = float32as(0.000000, 0x0) + hasWeight = true + case "STHeiti": + weight = float32as(0.240000, 0x3e75c28f) + hasWeight = true + case "STXihei": + weight = float32as(-0.100000, 0xbdcccccd) + hasWeight = true + case "TimesNewRomanPSMT": + weight = float32as(0.000000, 0x0) + hasWeight = true + } + } + + styleGlossaryStrings := []int{ + kCGFontNameKeyPreferredSubfamily, + kCGFontNameKeyFontSubfamily, + kCGFontNameKeyFullName, + kCGFontNameKeyPreferredFamily, + kCGFontNameKeyFontFamily, + } + weightNameMap := []struct { + key string + val float32 + }{ + { "Ultra Light", float32as(-0.800000f, 0xbf4ccccd) }, + { "Ultra Black", float32as(0.750000f, 0x3f400000) }, + { "Extra Light", float32as(-0.500000f, 0xbf000000) }, + { "UltraBlack", float32as(0.750000f, 0x3f400000) }, + { "ExtraBlack", float32as(0.800000f, 0x3f4ccccd) }, + { "UltraLight", float32as(-0.800000f, 0xbf4ccccd) }, + { "ExtraLight", float32as(-0.500000f, 0xbf000000) }, + { "Ultra Thin", float32as(-0.800000f, 0xbf4ccccd) }, + { "Extra Thin", float32as(-0.800000f, 0xbf4ccccd) }, + { "Heavy Face", float32as(0.560000f, 0x3f0f5c29) }, + { "Semi Light", float32as(-0.200000f, 0xbe4ccccd) }, + { "Extra Bold", float32as(0.500000f, 0x3f000000) }, + { "Ultra Bold", float32as(0.700000f, 0x3f333333) }, + { "HeavyFace", float32as(0.560000f, 0x3f0f5c29) }, + { "ExtraBold", float32as(0.500000f, 0x3f000000) }, + { "UltraBold", float32as(0.700000f, 0x3f333333) }, + { "Ext Black", float32as(0.800000f, 0x3f4ccccd) }, + { "SemiLight", float32as(-0.200000f, 0xbe4ccccd) }, + { "Demi Bold", float32as(0.250000f, 0x3e800000) }, + { "Semi Bold", float32as(0.300000f, 0x3e99999a) }, + { "Ext Light", float32as(-0.500000f, 0xbf000000) }, + { "Ext Bold", float32as(0.500000f, 0x3f000000) }, + { "DemiBold", float32as(0.250000f, 0x3e800000) }, + { "SemiBold", float32as(0.300000f, 0x3e99999a) }, + { "HairLine", float32as(-0.800000f, 0xbf4ccccd) }, + { "Ext Thin", float32as(-0.800000f, 0xbf4ccccd) }, + { "Medium", float32as(0.230000f, 0x3e6b851f) }, + { "Poster", float32as(0.800000f, 0x3f4ccccd) }, + { "Light", float32as(-0.400000f, 0xbecccccd) }, + { "Ultra", float32as(0.500000f, 0x3f000000) }, + { "Heavy", float32as(0.560000f, 0x3f0f5c29) }, + { "Extra", float32as(0.500000f, 0x3f000000) }, + { "Black", float32as(0.620000f, 0x3f1eb852) }, + { "Super", float32as(0.620000f, 0x3f1eb852) }, + { "Obese", float32as(0.850000f, 0x3f59999a) }, + { "Lite", float32as(-0.400000f, 0xbecccccd) }, + { "Book", float32as(-0.230000f, 0xbe6b851f) }, + { "Demi", float32as(0.250000f, 0x3e800000) }, + { "Semi", float32as(0.300000f, 0x3e99999a) }, + { "Thin", float32as(-0.500000f, 0xbf000000) }, + { "Bold", float32as(0.400000f, 0x3ecccccd) }, + { "Nord", float32as(0.800000f, 0x3f4ccccd) }, + { "Fat", float32as(0.750000f, 0x3f400000) }, + { "W1", float32as(-0.230000f, 0xbe6b851f) }, + { "W2", float32as(-0.500000f, 0xbf000000) }, + { "W3", float32as(-0.230000f, 0xbe6b851f) }, + { "W4", float32as(0.000000f, 0x0) }, + { "W5", float32as(0.230000f, 0x3e6b851f) }, + { "W6", float32as(0.300000f, 0x3e99999a) }, + { "W7", float32as(0.440000f, 0x3ee147ae) }, + { "W8", float32as(0.540000f, 0x3f0a3d71) }, + { "W9", float32as(0.620000f, 0x3f1eb852) }, + } + for _, key := range styleGlossaryStrings { + if hasWeight { + break + } + str, ok := cgfont.CopyName(key) + if !ok { + continue + } + for _, m := range weightNameMap { + if str.FindWithOptions(m.key, CFRangeMake(0, str.CFStringLength()), kCFCompareCaseInsensitive | kCFCompareBackwards | kCFCompareNonliteral, nil) { + weight = m.val + hasWeight = true + break + } + } + } + + if !hasWeight { + os2table := cgfont.TableForTag('OS/2') + weight, hasWeight = RegistryDetermineOS2Weight(os2table) + } + + if !hasWeight { + headtable := cgfont.TableForTag('head') + if headtable != nil { + if headtable.Len() >= 54 { + b := headtable.Bytes() + if (b[0x2d] & 1) != 0 { + weight = float32as(0.400000, 0x3ecccccd) + hasWeight = true + } + } + } + } + + styleGlossaryAbbreviationKeys := []int{ + kCGFontNameKeyPreferredSubfamily, + kCGFontNameKeyFontSubfamily, + } + abbreviatedWeightNameMap := []struct { + key string + val float32 + }{ + { "EL", float32as(-0.200000, 0xbe4ccccd) }, + { "EB", float32as(0.500000, 0x3f000000) }, + { "SB", float32as(0.300000, 0x3e99999a) }, + { "UH", float32as(0.800000, 0x3f4ccccd) }, + { "U", float32as(0.700000, 0x3f333333) }, + { "L", float32as(-0.400000, 0xbecccccd) }, + { "H", float32as(0.560000, 0x3f0f5c29) }, + { "B", float32as(0.400000, 0x3ecccccd) }, + { "M", float32as(0.230000, 0x3e6b851f) }, + { "R", float32as(0.000000, 0x0) }, + } + if !hasWeight { + for _, key := range styleGlossaryAbbreviationStrings { + str, ok := cgfont.CopyName(key) + if !ok { + continue + } + for _, m := range abbreviatedWeightNameMap { + if str.Compare(m.key, kCFCompareCaseInsensitive) == kCFCompareEqualTo { + weight = m.val + hasWeight = true + break + } + } + if hasWeight { + break + } + } + } + + if !hasWeight { + return float32as(0.000000, 0x0) + } + return weight +} + +// because Core Text gets registry traits as a CFDictionary, convert the float to a double with CFNumber as that is what actually would be done +func (f *Font) WeightFromFontRegistry() float64 { + return CFNumberWithFloat32(f.WeightFromFontRegistry32()).Float64Value() +} + +// based on CoreText dylib's __Z13WeightOfClasst — WeightOfClass(unsigned short) +func CoreText_WeightOfClass(usWeightClass uint16) float64 { + if usWeightClass >= 11 { + // do nothing; we are preserving the original asm comparisons + // and yes, this one is 11, but the one above is 10 + } else { + usWeightClass *= 100 + } + + // figure out what two floats our weight will be between + i := usWeightClass / 100 + j := i + 1 + if j > 10 { + j = 10 + } + b := float64(i * 100) + c := float64(j * 100) + + a := float64(0) + if b != c { + a = float64(usWeightClass) + a -= b + c -= b + a /= c + } + scales := []float32{ + float32as(-1.000000, 0xbf800000), + float32as(-0.700000, 0xbf333333), + float32as(-0.500000, 0xbf000000), + float32as(-0.230000, 0xbe6b851f), + float32as(0.000000, 0x0), + float32as(0.200000, 0x3e4ccccd), + float32as(0.300000, 0x3e99999a), + float32as(0.400000, 0x3ecccccd), + float32as(0.600000, 0x3f19999a), + float32as(0.800000, 0x3f4ccccd), + float32as(1.000000, 0x3f800000), + } + c = float64(scale[i]) + b = float64[scale[j]) + return fma(a, b, c) +} + +// based on CoreText dylib's __ZL33CreateTraitsByStyleGlossaryStringPK10__CFString — CreateTraitsByStyleGlossaryString(__CFString const*) +func CoreText_WeightByStyleGlossaryString(str string) (weight float64, ok bool) { + str.Fold(kCFCompareCaseInsensitive, nil) + weightNameMap := []struct { + key string + val float32 + }{ + { "ultra light", float32as(-0.800000, 0xbf4ccccd) }, + { "ultra black", float32as(0.750000, 0x3f400000) }, + { "extra light", float32as(-0.500000, 0xbf000000) }, + { "ultralight", float32as(-0.800000, 0xbf4ccccd) }, + { "ultrablack", float32as(0.750000, 0x3f400000) }, + { "extrablack", float32as(0.800000, 0x3f4ccccd) }, + { "extralight", float32as(-0.500000, 0xbf000000) } + { "heavy face", float32as(0.560000, 0x3f0f5c29) }, + { "semi light", float32as(-0.200000, 0xbe4ccccd) }, + { "extra bold", float32as(0.500000, 0x3f000000) }, + { "ultra bold", float32as(0.700000, 0x3f333333) }, + { "heavyface", float32as(0.560000, 0x3f0f5c29) }, + { "extrabold", float32as(0.500000, 0x3f000000) }, + { "ultrabold", float32as(0.700000, 0x3f333333) }, + { "semilight", float32as(-0.200000, 0xbe4ccccd) }, + { "demi bold", float32as(0.250000, 0x3e800000) }, + { "semi bold", float32as(0.300000, 0x3e99999a) }, + { "demibold", float32as(0.250000, 0x3e800000) }, + { "semibold", float32as(0.300000, 0x3e99999a) }, + { "hairline", float32as(-0.700000, 0xbf333333) }, + { "medium", float32as(0.230000, 0x3e6b851f) }, + { "poster", float32as(0.800000, 0x3f4ccccd) }, + { "light", float32as(-0.400000, 0xbecccccd) }, + { "heavy", float32as(0.560000, 0x3f0f5c29) }, + { "extra", float32as(0.500000, 0x3f000000) }, + { "black", float32as(0.620000, 0x3f1eb852) }, + { "super", float32as(0.620000, 0x3f1eb852) }, + { "obese", float32as(0.850000, 0x3f59999a) }, + { "lite", float32as(-0.400000, 0xbecccccd) }, + { "book", float32as(-0.230000, 0xbe6b851f) }, + { "demi", float32as(0.250000, 0x3e800000) }, + { "semi", float32as(0.300000, 0x3e99999a) }, + { "thin", float32as(-0.500000, 0xbf000000) }, + { "bold", float32as(0.400000, 0x3ecccccd) }, + { "nord", float32as(0.800000, 0x3f4ccccd) }, + { "fat", float32as(0.750000, 0x3f400000) }, + { "w1", float32as(-0.700000, 0xbf333333) }, + { "w2", float32as(-0.500000, 0xbf000000) }, + { "w3", float32as(-0.230000, 0xbe6b851f) }, + { "w4", float32as(0.000000, 0x0) }, + { "w5", float32as(0.230000, 0x3e6b851f) }, + { "w6", float32as(0.300000, 0x3e99999a) }, + { "w7", float32as(0.440000, 0x3ee147ae) }, + { "w8", float32as(0.540000, 0x3f0a3d71) }, + { "w9", float32as(0.620000, 0x3f1eb852) }, + } + for _, m := range weightNameMap { + if strstr(str, m.key) != nil { + val := CFNumberWithFloat32(m.val) + return val.Float64Value(), true + } + } + return 0, false +} + +// based on CoreText dylib's __ZNK9TBaseFont29CreateTraitsValuesPerFontInfoEP12MetadataFlag — TBaseFont::CreateTraitsValuesPerFontInfo(MetadataFlag*) const +func (f *CTFont) Weight() float64 { + if f.IsRegistered() { + return f.WeightFromFontRegistry() + } + + weight := float64as(2.0, 0x4000000000000000) + ebx := -1 + hasWeight := false + + name := f.Name(kCTFontPostScriptNameKey) + if name != nil { + switch *name { + case "LucidaGrande": + weight = float64as(0.000000, 0x0) + hasWeight = true + case ".LucidaGrandeUI": + weight = float64as(0.000000, 0x0) + hasWeight = true + case "STHeiti": + weight = float64as(0.240000, 0x3fceb851eb851eb8) + hasWeight = true + case "STXihei": + weight = float64as(-0.100000, 0xbfb999999999999a) + hasWeight = true + case "TimesNewRomanPSMT": + weight = float64as(0.000000, 0x0) + hasWeight = true + // there is one more hardcoded case, for "Times-Roman", but that will only set the class style, not the weight + } + } + + os2table := f.Table('OS/2') + if os2table != nil { + if !hasWeight { + var usWeightClass uint16 + + valid := false + if os2table.Len() > 77 { + b := os2table.Bytes() + usWeightClass = uint16be(b[4:6]) + if usWeightClass > 1000 { + weight = 0 + hasWeight = false + } else { + valid = true + } + } else { + usWeightClass = 0 + valid = true + } + if valid { + weight = CoreText_WeightOfClass(usWeightClass) + hasWeight = true + } + } + } + + styleGlossaryNames := []string{ + kCTFontSubFamilyNameKey, + kCTFontFullNameKey, + kCTFontFamilyNameKey, + } + for _, key := range styleGlossaryNames { + name := f.Name(key) + if name == nil { + continue + } + candidate, ok := CoreText_WeightByStyleGlossaryString(*name) + if !ok { + continue + } + if !hasWeight { + weight = candidate + hasWeight = true + } + } + + if hasWeight { + return weight + } + return 0 +} + +func (f *Font) ShouldEnableBoldSymbolicTrait() bool { + if f.IsRegistered() { + return f.ShouldEnableBoldSymbolicTraitFromRegistry() + } + no := f.Weight() <= float64as(0.239000, 0x3fce978d4fdf3b64) + return !no +} diff --git a/doc/export/ctweightsannotated b/doc/export/ctweightsannotated new file mode 100644 index 00000000..8df56f25 --- /dev/null +++ b/doc/export/ctweightsannotated @@ -0,0 +1,149 @@ +-0.100000 0xbdcccccd registered postscript name "STXihei" +-0.100000 0xbfb999999999999a unregistered postscript name "STXihei" +-0.200000 0xbe4ccccd registered subfamily abbr "EL" +-0.200000 0xbe4ccccd "Semi Light" +-0.200000 0xbe4ccccd "SemiLight" +-0.200000 0xbe4ccccd "semi light" +-0.200000 0xbe4ccccd "semilight" +-0.230000 0xbe6b851f "Book" +-0.230000 0xbe6b851f "W1" +-0.230000 0xbe6b851f "W3" +-0.230000 0xbe6b851f "book" +-0.230000 0xbe6b851f "w3" +-0.230000 0xbe6b851f panose 5 +-0.400000 0xbecccccd registered subfamily abbr "L" +-0.400000 0xbecccccd "Light" +-0.400000 0xbecccccd "Lite" +-0.400000 0xbecccccd "light" +-0.400000 0xbecccccd "light" +-0.400000 0xbecccccd "lite" +-0.400000 0xbecccccd registered OS2 weights 3, 201 - 300 +-0.400000 0xbecccccd panose 3 +-0.500000 0xbf000000 "Ext Light" +-0.500000 0xbf000000 "Extra Light" +-0.500000 0xbf000000 "ExtraLight" +-0.500000 0xbf000000 "Thin" +-0.500000 0xbf000000 "W2" +-0.500000 0xbf000000 "extra light" +-0.500000 0xbf000000 "extralight" +-0.500000 0xbf000000 "thin" +-0.500000 0xbf000000 "w2" +-0.500000 0xbf000000 registered OS2 weights 2, 101 - 200 +-0.500000 0xbf000000 panose 2 +-0.600000 0xbf19999a "thin" +-0.700000 0xbf333333 "hairline" +-0.700000 0xbf333333 "w1" +-0.800000 0xbf4ccccd "Ext Thin" +-0.800000 0xbf4ccccd "Extra Thin" +-0.800000 0xbf4ccccd "HairLine" +-0.800000 0xbf4ccccd "Ultra Light" +-0.800000 0xbf4ccccd "Ultra Thin" +-0.800000 0xbf4ccccd "UltraLight" +-0.800000 0xbf4ccccd "ulight" +-0.800000 0xbf4ccccd "ultra light" +-0.800000 0xbf4ccccd "ultralight" +-0.800000 0xbf4ccccd registered OS2 weights 1, 10 - 100 +0.000000 0x0 registered postscript name ".Keyboard" +0.000000 0x0 registered postscript name ".LucidaGrandeUI" +0.000000 0x0 unregistered postscript name ".LucidaGrandeUI" +0.000000 0x0 registered postscript name "LucidaGrande" +0.000000 0x0 unregistered postscript name "LucidaGrande" +0.000000 0x0 registered subfamily abbr "R" +0.000000 0x0 registered postscript name "TimesNewRomanPSMT" +0.000000 0x0 unregistered postscript name "TimesNewRomanPSMT" +0.000000 0x0 "W4" +0.000000 0x0 "reg" +0.000000 0x0 "w4" +0.000000 0x0 registered OS2 weights 4, 301 - 400 +0.000000 0x0 default +0.230000 0x3e6b851f registered OS2 weights 5, 401 - 500 +0.230000 0x3e6b851f registered subfamily abbr "M" +0.230000 0x3e6b851f "Medium" +0.230000 0x3e6b851f "W5" +0.230000 0x3e6b851f "med" +0.230000 0x3e6b851f "medium" +0.230000 0x3e6b851f "w5" +0.230000 0x3e6b851f panose 6 +0.240000 0x3e75c28f registered postscript name "STHeiti" +0.240000 0x3fceb851eb851eb8 unregistered postscript name "STHeiti" +0.250000 0x3e800000 "Demi Bold" +0.250000 0x3e800000 "Demi" +0.250000 0x3e800000 "DemiBold" +0.250000 0x3e800000 "demi bold" +0.250000 0x3e800000 "demi" +0.250000 0x3e800000 "demibold" +0.250000 0x3e800000 registered OS2 weights 6, 501 - 600 +0.250000 0x3e800000 panose 7 +0.300000 0x3e99999a registered subfamily abbr "SB" +0.300000 0x3e99999a "Semi Bold" +0.300000 0x3e99999a "Semi" +0.300000 0x3e99999a "SemiBold" +0.300000 0x3e99999a "W6" +0.300000 0x3e99999a "semi bold" +0.300000 0x3e99999a "semi" +0.300000 0x3e99999a "semi" +0.300000 0x3e99999a "semibold" +0.300000 0x3e99999a "w6" +0.400000 0x3ecccccd registered subfamily abbr "B" +0.400000 0x3ecccccd "Bold" +0.400000 0x3ecccccd "bold" +0.400000 0x3ecccccd "bold" +0.400000 0x3ecccccd 'head'[0x2D] & 1 +0.400000 0x3ecccccd registered OS2 weights 7, 601 - 700 +0.400000 0x3ecccccd panose 8 +0.440000 0x3ee147ae "W7" +0.440000 0x3ee147ae "w7" +0.500000 0x3f000000 registered subfamily abbr "EB" +0.500000 0x3f000000 "Ext Bold" +0.500000 0x3f000000 "Extra Bold" +0.500000 0x3f000000 "Extra" +0.500000 0x3f000000 "ExtraBold" +0.500000 0x3f000000 "Ultra" +0.500000 0x3f000000 "extra bold" +0.500000 0x3f000000 "extra" +0.500000 0x3f000000 "extrabold" +0.500000 0x3f000000 registered OS2 weights 8, 701 - 800 +0.540000 0x3f0a3d71 "W8" +0.540000 0x3f0a3d71 "w8" +0.560000 0x3f0f5c29 registered subfamily abbr "H" +0.560000 0x3f0f5c29 "Heavy Face" +0.560000 0x3f0f5c29 "Heavy" +0.560000 0x3f0f5c29 "HeavyFace" +0.560000 0x3f0f5c29 "heavy face" +0.560000 0x3f0f5c29 "heavy" +0.560000 0x3f0f5c29 "heavy" +0.560000 0x3f0f5c29 "heavyface" +0.560000 0x3f0f5c29 panose 9 +0.620000 0x3f1eb852 "Black" +0.620000 0x3f1eb852 "Super" +0.620000 0x3f1eb852 "W9" +0.620000 0x3f1eb852 "black" +0.620000 0x3f1eb852 "black" +0.620000 0x3f1eb852 "super" +0.620000 0x3f1eb852 "w9" +0.620000 0x3f1eb852 registered OS2 weights 9, 801 - 900 +0.620000 0x3f1eb852 panose 10 +0.700000 0x3f333333 registered subfamily abbr "U" +0.700000 0x3f333333 "Ultra Bold" +0.700000 0x3f333333 "UltraBold" +0.700000 0x3f333333 "ultra bold" +0.700000 0x3f333333 "ultrabold" +0.750000 0x3f400000 "Fat" +0.750000 0x3f400000 "Ultra Black" +0.750000 0x3f400000 "UltraBlack" +0.750000 0x3f400000 "fat" +0.750000 0x3f400000 "ultra black" +0.750000 0x3f400000 "ultrablack" +0.750000 0x3f400000 registered OS2 weights 901 - 950 +0.800000 0x3f4ccccd "Ext Black" +0.800000 0x3f4ccccd "ExtraBlack" +0.800000 0x3f4ccccd "Nord" +0.800000 0x3f4ccccd "Poster" +0.800000 0x3f4ccccd registered subfamily abbr "UH" +0.800000 0x3f4ccccd "extrablack" +0.800000 0x3f4ccccd "nord" +0.800000 0x3f4ccccd "poster" +0.800000 0x3f4ccccd registered OS2 weights 951 - 999 +0.800000 0x3f4ccccd panose 11 +0.850000 0x3f59999a "Obese" +0.850000 0x3f59999a "obese" diff --git a/doc/export/ctweightscombined b/doc/export/ctweightscombined new file mode 100644 index 00000000..d5017938 --- /dev/null +++ b/doc/export/ctweightscombined @@ -0,0 +1,146 @@ +-0.100000 + postscript name "STXihei" (which, according to http://www.typophile.com/node/93813, means "ST Hei Light", and so we can assume it's the Light version of STHeiti, which I think is Medium though is given Regular status below because meh) + +-0.200000 + subfamily abbreviation "EL" (???????????) + style string contains "Semi Light" + style string contains "SemiLight" + +-0.230000 + style string contains "Book" + style string contains "W1" (registered fonts only; probably a typo and -0.7 was expected instead) + style string contains "W3" + panose 5 + +-0.400000 + subfamily abbreviation "L" + style string contains "Light" + style string contains "Lite" + ATSD style "light" + OS2 weights 3, 201 - 300 + panose 3 + +-0.500000 + style string contains "Ext Light" + style string contains "Extra Light" + style string contains "ExtraLight" + style string contains "Thin" + style string contains "W2" + OS2 weights 2, 101 - 200 + panose 2 + +-0.600000 + ATSD style "thin" + +-0.700000 + style string contains "hairline" + style string contains "w1" + +-0.800000 + style string contains "Ext Thin" + style string contains "Extra Thin" + style string contains "HairLine" + style string contains "Ultra Light" + style string contains "Ultra Thin" + style string contains "UltraLight" + ATSD style "ulight" + OS2 weights 1, 10 - 100 + +0.000000 + ostscript name ".Keyboard" + postscript name ".LucidaGrandeUI" + postscript name "LucidaGrande" + subfamily abbreviation "R" + postscript name "TimesNewRomanPSMT" + style string contains "W4" + ATSD style "reg" + OS2 weights 4, 301 - 400 + default + +0.230000 + OS2 weights 5, 401 - 500 + subfamily abbreviation "M" + style string contains "Medium" + style string contains "W5" + ATSD style "med" + panose 6 + +0.240000 + postscript name "STHeiti" + +0.250000 + style string contains "Demi Bold" + style string contains "Demi" + style string contains "DemiBold" + OS2 weights 6, 501 - 600 + panose 7 + +0.300000 + subfamily abbreviation "SB" + style string contains "Semi Bold" + style string contains "Semi" + style string contains "SemiBold" + style string contains "W6" + ATSD style "semi" + +0.400000 + subfamily abbreviation "B" + style string contains "Bold" + ATSD style "bold" + ('head' table byte 0x2D) & 1 != 0 + OS2 weights 7, 601 - 700 + panose 8 + +0.440000 + style string contains "W7" + +0.500000 + subfamily abbreviation "EB" + style string contains "Ext Bold" + style string contains "Extra Bold" + style string contains "Extra" + style string contains "ExtraBold" + style string contains "Ultra" + OS2 weights 8, 701 - 800 + +0.540000 + style string contains "W8" + +0.560000 + subfamily abbreviation "H" + style string contains "Heavy Face" + style string contains "Heavy" + style string contains "HeavyFace" + ATSD style "heavy" + panose 9 + +0.620000 + style string contains "Black" + style string contains "Super" + style string contains "W9" + ATSD style "black" + OS2 weights 9, 801 - 900 + panose 10 + +0.700000 + subfamily abbreviation "U" + style string contains "Ultra Bold" + style string contains "UltraBold" + +0.750000 + style string contains "Fat" + style string contains "Ultra Black" + style string contains "UltraBlack" + OS2 weights 901 - 950 + +0.800000 + style string contains "Ext Black" + style string contains "ExtraBlack" + style string contains "Nord" + style string contains "Poster" + subfamily abbreviation "UH" + OS2 weights 951 - 999 + panose 11 + +0.850000 + style string contains "Obese" diff --git a/doc/export/ctweightsprocessed b/doc/export/ctweightsprocessed new file mode 100644 index 00000000..fe315cc7 --- /dev/null +++ b/doc/export/ctweightsprocessed @@ -0,0 +1,149 @@ +-0.100000 0xbdcccccd "STXihei" +-0.100000 0xbfb999999999999a "STXihei" +-0.200000 0xbe4ccccd "EL" +-0.200000 0xbe4ccccd "Semi Light" +-0.200000 0xbe4ccccd "SemiLight" +-0.200000 0xbe4ccccd "semi light" +-0.200000 0xbe4ccccd "semilight" +-0.230000 0xbe6b851f "Book" +-0.230000 0xbe6b851f "W1" +-0.230000 0xbe6b851f "W3" +-0.230000 0xbe6b851f "book" +-0.230000 0xbe6b851f "w3" +-0.230000 0xbe6b851f panose 5 +-0.400000 0xbecccccd "L" +-0.400000 0xbecccccd "Light" +-0.400000 0xbecccccd "Lite" +-0.400000 0xbecccccd "light" +-0.400000 0xbecccccd "light" +-0.400000 0xbecccccd "lite" +-0.400000 0xbecccccd 3, 201 - 300 +-0.400000 0xbecccccd panose 3 +-0.500000 0xbf000000 "Ext Light" +-0.500000 0xbf000000 "Extra Light" +-0.500000 0xbf000000 "ExtraLight" +-0.500000 0xbf000000 "Thin" +-0.500000 0xbf000000 "W2" +-0.500000 0xbf000000 "extra light" +-0.500000 0xbf000000 "extralight" +-0.500000 0xbf000000 "thin" +-0.500000 0xbf000000 "w2" +-0.500000 0xbf000000 2, 101 - 200 +-0.500000 0xbf000000 panose 2 +-0.600000 0xbf19999a "thin" +-0.700000 0xbf333333 "hairline" +-0.700000 0xbf333333 "w1" +-0.800000 0xbf4ccccd "Ext Thin" +-0.800000 0xbf4ccccd "Extra Thin" +-0.800000 0xbf4ccccd "HairLine" +-0.800000 0xbf4ccccd "Ultra Light" +-0.800000 0xbf4ccccd "Ultra Thin" +-0.800000 0xbf4ccccd "UltraLight" +-0.800000 0xbf4ccccd "ulight" +-0.800000 0xbf4ccccd "ultra light" +-0.800000 0xbf4ccccd "ultralight" +-0.800000 0xbf4ccccd 1, 10 - 100 +0.000000 0x0 ".Keyboard" +0.000000 0x0 ".LucidaGrandeUI" +0.000000 0x0 ".LucidaGrandeUI" +0.000000 0x0 "LucidaGrande" +0.000000 0x0 "LucidaGrande" +0.000000 0x0 "R" +0.000000 0x0 "TimesNewRomanPSMT" +0.000000 0x0 "TimesNewRomanPSMT" +0.000000 0x0 "W4" +0.000000 0x0 "reg" +0.000000 0x0 "w4" +0.000000 0x0 4, 301 - 400 +0.000000 0x0 default +0.230000 0x3e6b851f 5, 401 - 500 +0.230000 0x3e6b851f "M" +0.230000 0x3e6b851f "Medium" +0.230000 0x3e6b851f "W5" +0.230000 0x3e6b851f "med" +0.230000 0x3e6b851f "medium" +0.230000 0x3e6b851f "w5" +0.230000 0x3e6b851f panose 6 +0.240000 0x3e75c28f "STHeiti" +0.240000 0x3fceb851eb851eb8 "STHeiti" +0.250000 0x3e800000 "Demi Bold" +0.250000 0x3e800000 "Demi" +0.250000 0x3e800000 "DemiBold" +0.250000 0x3e800000 "demi bold" +0.250000 0x3e800000 "demi" +0.250000 0x3e800000 "demibold" +0.250000 0x3e800000 6, 501 - 600 +0.250000 0x3e800000 panose 7 +0.300000 0x3e99999a "SB" +0.300000 0x3e99999a "Semi Bold" +0.300000 0x3e99999a "Semi" +0.300000 0x3e99999a "SemiBold" +0.300000 0x3e99999a "W6" +0.300000 0x3e99999a "semi bold" +0.300000 0x3e99999a "semi" +0.300000 0x3e99999a "semi" +0.300000 0x3e99999a "semibold" +0.300000 0x3e99999a "w6" +0.400000 0x3ecccccd "B" +0.400000 0x3ecccccd "Bold" +0.400000 0x3ecccccd "bold" +0.400000 0x3ecccccd "bold" +0.400000 0x3ecccccd 'head'[0x2D] & 1 +0.400000 0x3ecccccd 7, 601 - 700 +0.400000 0x3ecccccd panose 8 +0.440000 0x3ee147ae "W7" +0.440000 0x3ee147ae "w7" +0.500000 0x3f000000 "EB" +0.500000 0x3f000000 "Ext Bold" +0.500000 0x3f000000 "Extra Bold" +0.500000 0x3f000000 "Extra" +0.500000 0x3f000000 "ExtraBold" +0.500000 0x3f000000 "Ultra" +0.500000 0x3f000000 "extra bold" +0.500000 0x3f000000 "extra" +0.500000 0x3f000000 "extrabold" +0.500000 0x3f000000 8, 701 - 800 +0.540000 0x3f0a3d71 "W8" +0.540000 0x3f0a3d71 "w8" +0.560000 0x3f0f5c29 "H" +0.560000 0x3f0f5c29 "Heavy Face" +0.560000 0x3f0f5c29 "Heavy" +0.560000 0x3f0f5c29 "HeavyFace" +0.560000 0x3f0f5c29 "heavy face" +0.560000 0x3f0f5c29 "heavy" +0.560000 0x3f0f5c29 "heavy" +0.560000 0x3f0f5c29 "heavyface" +0.560000 0x3f0f5c29 panose 9 +0.620000 0x3f1eb852 "Black" +0.620000 0x3f1eb852 "Super" +0.620000 0x3f1eb852 "W9" +0.620000 0x3f1eb852 "black" +0.620000 0x3f1eb852 "black" +0.620000 0x3f1eb852 "super" +0.620000 0x3f1eb852 "w9" +0.620000 0x3f1eb852 9, 801 - 900 +0.620000 0x3f1eb852 panose 10 +0.700000 0x3f333333 "U" +0.700000 0x3f333333 "Ultra Bold" +0.700000 0x3f333333 "UltraBold" +0.700000 0x3f333333 "ultra bold" +0.700000 0x3f333333 "ultrabold" +0.750000 0x3f400000 "Fat" +0.750000 0x3f400000 "Ultra Black" +0.750000 0x3f400000 "UltraBlack" +0.750000 0x3f400000 "fat" +0.750000 0x3f400000 "ultra black" +0.750000 0x3f400000 "ultrablack" +0.750000 0x3f400000 901 - 950 +0.800000 0x3f4ccccd "Ext Black" +0.800000 0x3f4ccccd "ExtraBlack" +0.800000 0x3f4ccccd "Nord" +0.800000 0x3f4ccccd "Poster" +0.800000 0x3f4ccccd "UH" +0.800000 0x3f4ccccd "extrablack" +0.800000 0x3f4ccccd "nord" +0.800000 0x3f4ccccd "poster" +0.800000 0x3f4ccccd 951 - 999 +0.800000 0x3f4ccccd panose 11 +0.850000 0x3f59999a "Obese" +0.850000 0x3f59999a "obese" diff --git a/doc/export/ctweightsraw b/doc/export/ctweightsraw new file mode 100644 index 00000000..502d473e --- /dev/null +++ b/doc/export/ctweightsraw @@ -0,0 +1,149 @@ +0.000000 0x0 "reg" +0.300000 0x3e99999a "semi" +0.400000 0x3ecccccd "bold" +-0.400000 0xbecccccd "light" +0.230000 0x3e6b851f "med" +0.560000 0x3f0f5c29 "heavy" +0.620000 0x3f1eb852 "black" +-0.600000 0xbf19999a "thin" +-0.800000 0xbf4ccccd "ulight" +0.000000 0x0 "LucidaGrande" +0.000000 0x0 ".LucidaGrandeUI" +0.000000 0x0 ".Keyboard" +0.240000 0x3e75c28f "STHeiti" +-0.100000 0xbdcccccd "STXihei" +0.000000 0x0 "TimesNewRomanPSMT" +-0.800000 0xbf4ccccd "Ultra Light" +0.750000 0x3f400000 "Ultra Black" +-0.500000 0xbf000000 "Extra Light" +0.750000 0x3f400000 "UltraBlack" +0.800000 0x3f4ccccd "ExtraBlack" +-0.800000 0xbf4ccccd "UltraLight" +-0.500000 0xbf000000 "ExtraLight" +-0.800000 0xbf4ccccd "Ultra Thin" +-0.800000 0xbf4ccccd "Extra Thin" +0.560000 0x3f0f5c29 "Heavy Face" +-0.200000 0xbe4ccccd "Semi Light" +0.500000 0x3f000000 "Extra Bold" +0.700000 0x3f333333 "Ultra Bold" +0.560000 0x3f0f5c29 "HeavyFace" +0.500000 0x3f000000 "ExtraBold" +0.700000 0x3f333333 "UltraBold" +0.800000 0x3f4ccccd "Ext Black" +-0.200000 0xbe4ccccd "SemiLight" +0.250000 0x3e800000 "Demi Bold" +0.300000 0x3e99999a "Semi Bold" +-0.500000 0xbf000000 "Ext Light" +0.500000 0x3f000000 "Ext Bold" +0.250000 0x3e800000 "DemiBold" +0.300000 0x3e99999a "SemiBold" +-0.800000 0xbf4ccccd "HairLine" +-0.800000 0xbf4ccccd "Ext Thin" +0.230000 0x3e6b851f "Medium" +0.800000 0x3f4ccccd "Poster" +-0.400000 0xbecccccd "Light" +0.500000 0x3f000000 "Ultra" +0.560000 0x3f0f5c29 "Heavy" +0.500000 0x3f000000 "Extra" +0.620000 0x3f1eb852 "Black" +0.620000 0x3f1eb852 "Super" +0.850000 0x3f59999a "Obese" +-0.400000 0xbecccccd "Lite" +-0.230000 0xbe6b851f "Book" +0.250000 0x3e800000 "Demi" +0.300000 0x3e99999a "Semi" +-0.500000 0xbf000000 "Thin" +0.400000 0x3ecccccd "Bold" +0.800000 0x3f4ccccd "Nord" +0.750000 0x3f400000 "Fat" +-0.230000 0xbe6b851f "W1" +-0.500000 0xbf000000 "W2" +-0.230000 0xbe6b851f "W3" +0.000000 0x0 "W4" +0.230000 0x3e6b851f "W5" +0.300000 0x3e99999a "W6" +0.440000 0x3ee147ae "W7" +0.540000 0x3f0a3d71 "W8" +0.620000 0x3f1eb852 "W9" +-0.800000 0xbf4ccccd 1, 10 - 100 +-0.500000 0xbf000000 2, 101 - 200 +-0.400000 0xbecccccd 3, 201 - 300 +0.000000 0x0 4, 301 - 400 +0.230000 0x3e6b851f 5, 401 - 500 +0.250000 0x3e800000 6, 501 - 600 +0.400000 0x3ecccccd 7, 601 - 700 +0.500000 0x3f000000 8, 701 - 800 +0.620000 0x3f1eb852 9, 801 - 900 +0.750000 0x3f400000 901 - 950 +0.800000 0x3f4ccccd 951 - 999 +-0.500000 0xbf000000 panose 2 +-0.400000 0xbecccccd panose 3 +-0.230000 0xbe6b851f panose 5 +0.230000 0x3e6b851f panose 6 +0.250000 0x3e800000 panose 7 +0.400000 0x3ecccccd panose 8 +0.560000 0x3f0f5c29 panose 9 +0.620000 0x3f1eb852 panose 10 +0.800000 0x3f4ccccd panose 11 +0.400000 0x3ecccccd 'head'[0x2D] & 1 +-0.200000 0xbe4ccccd "EL" +0.500000 0x3f000000 "EB" +0.300000 0x3e99999a "SB" +0.800000 0x3f4ccccd "UH" +0.700000 0x3f333333 "U" +-0.400000 0xbecccccd "L" +0.560000 0x3f0f5c29 "H" +0.400000 0x3ecccccd "B" +0.230000 0x3e6b851f "M" +0.000000 0x0 "R" +0.000000 0x0 default +0.000000 0x0 "LucidaGrande" +0.000000 0x0 ".LucidaGrandeUI" +0.240000 0x3fceb851eb851eb8 "STHeiti" +-0.100000 0xbfb999999999999a "STXihei" +0.000000 0x0 "TimesNewRomanPSMT" +-0.800000 0xbf4ccccd "ultra light" +0.750000 0x3f400000 "ultra black" +-0.500000 0xbf000000 "extra light" +-0.800000 0xbf4ccccd "ultralight" +0.750000 0x3f400000 "ultrablack" +0.800000 0x3f4ccccd "extrablack" +-0.500000 0xbf000000 "extralight" +0.560000 0x3f0f5c29 "heavy face" +-0.200000 0xbe4ccccd "semi light" +0.500000 0x3f000000 "extra bold" +0.700000 0x3f333333 "ultra bold" +0.560000 0x3f0f5c29 "heavyface" +0.500000 0x3f000000 "extrabold" +0.700000 0x3f333333 "ultrabold" +-0.200000 0xbe4ccccd "semilight" +0.250000 0x3e800000 "demi bold" +0.300000 0x3e99999a "semi bold" +0.250000 0x3e800000 "demibold" +0.300000 0x3e99999a "semibold" +-0.700000 0xbf333333 "hairline" +0.230000 0x3e6b851f "medium" +0.800000 0x3f4ccccd "poster" +-0.400000 0xbecccccd "light" +0.560000 0x3f0f5c29 "heavy" +0.500000 0x3f000000 "extra" +0.620000 0x3f1eb852 "black" +0.620000 0x3f1eb852 "super" +0.850000 0x3f59999a "obese" +-0.400000 0xbecccccd "lite" +-0.230000 0xbe6b851f "book" +0.250000 0x3e800000 "demi" +0.300000 0x3e99999a "semi" +-0.500000 0xbf000000 "thin" +0.400000 0x3ecccccd "bold" +0.800000 0x3f4ccccd "nord" +0.750000 0x3f400000 "fat" +-0.700000 0xbf333333 "w1" +-0.500000 0xbf000000 "w2" +-0.230000 0xbe6b851f "w3" +0.000000 0x0 "w4" +0.230000 0x3e6b851f "w5" +0.300000 0x3e99999a "w6" +0.440000 0x3ee147ae "w7" +0.540000 0x3f0a3d71 "w8" +0.620000 0x3f1eb852 "w9" diff --git a/doc/export/ctweightvalues b/doc/export/ctweightvalues new file mode 100644 index 00000000..acbcb794 --- /dev/null +++ b/doc/export/ctweightvalues @@ -0,0 +1,247 @@ +registered font, preexisting metadata weight +"reg": float32as(0.000000, 0x0) +"semi": float32as(0.300000, 0x3e99999a) +"bold": float32as(0.400000, 0x3ecccccd) +"light": float32as(-0.400000, 0xbecccccd) +"med": float32as(0.230000, 0x3e6b851f) +"heavy": float32as(0.560000, 0x3f0f5c29) +"black": float32as(0.620000, 0x3f1eb852) +"thin": float32as(-0.600000, 0xbf19999a) +"ulight": float32as(-0.800000, 0xbf4ccccd) + +registered font, postscript name (probably only for TrueType and OpenType) special cases, overrides the above +"LucidaGrande": float32as(0.000000, 0x0) +".LucidaGrandeUI": float32as(0.000000, 0x0) +".Keyboard": float32as(0.000000, 0x0) +"STHeiti": float32as(0.240000, 0x3e75c28f) +"STXihei": float32as(-0.100000, 0xbdcccccd) +"TimesNewRomanPSMT": float32as(0.000000, 0x0) + +registered font, style glossary strings, tried in this order (possibly TrueType and OpenType only): preferred subfamily, subfamily, full name, preferred family, family; case-insensitive search, reverse search, "nonliteral" (kCFCompareNonliteral) +"Ultra Light": float32as(-0.800000, 0xbf4ccccd) +"Ultra Black": float32as(0.750000, 0x3f400000) +"Extra Light": float32as(-0.500000, 0xbf000000) +"UltraBlack": float32as(0.750000, 0x3f400000) +"ExtraBlack": float32as(0.800000, 0x3f4ccccd) +"UltraLight": float32as(-0.800000, 0xbf4ccccd) +"ExtraLight": float32as(-0.500000, 0xbf000000) +"Ultra Thin": float32as(-0.800000, 0xbf4ccccd) +"Extra Thin": float32as(-0.800000, 0xbf4ccccd) +"Heavy Face": float32as(0.560000, 0x3f0f5c29) +"Semi Light": float32as(-0.200000, 0xbe4ccccd) +"Extra Bold": float32as(0.500000, 0x3f000000) +"Ultra Bold": float32as(0.700000, 0x3f333333) +"HeavyFace": float32as(0.560000, 0x3f0f5c29) +"ExtraBold": float32as(0.500000, 0x3f000000) +"UltraBold": float32as(0.700000, 0x3f333333) +"Ext Black": float32as(0.800000, 0x3f4ccccd) +"SemiLight": float32as(-0.200000, 0xbe4ccccd) +"Demi Bold": float32as(0.250000, 0x3e800000) +"Semi Bold": float32as(0.300000, 0x3e99999a) +"Ext Light": float32as(-0.500000, 0xbf000000) +"Ext Bold": float32as(0.500000, 0x3f000000) +"DemiBold": float32as(0.250000, 0x3e800000) +"SemiBold": float32as(0.300000, 0x3e99999a) +"HairLine": float32as(-0.800000, 0xbf4ccccd) +"Ext Thin": float32as(-0.800000, 0xbf4ccccd) +"Medium": float32as(0.230000, 0x3e6b851f) +"Poster": float32as(0.800000, 0x3f4ccccd) +"Light": float32as(-0.400000, 0xbecccccd) +"Ultra": float32as(0.500000, 0x3f000000) +"Heavy": float32as(0.560000, 0x3f0f5c29) +"Extra": float32as(0.500000, 0x3f000000) +"Black": float32as(0.620000, 0x3f1eb852) +"Super": float32as(0.620000, 0x3f1eb852) +"Obese": float32as(0.850000, 0x3f59999a) +"Lite": float32as(-0.400000, 0xbecccccd) +"Book": float32as(-0.230000, 0xbe6b851f) +"Demi": float32as(0.250000, 0x3e800000) +"Semi": float32as(0.300000, 0x3e99999a) +"Thin": float32as(-0.500000, 0xbf000000) +"Bold": float32as(0.400000, 0x3ecccccd) +"Nord": float32as(0.800000, 0x3f4ccccd) +"Fat": float32as(0.750000, 0x3f400000) +"W1": float32as(-0.230000, 0xbe6b851f) +"W2": float32as(-0.500000, 0xbf000000) +"W3": float32as(-0.230000, 0xbe6b851f) +"W4": float32as(0.000000, 0x0) +"W5": float32as(0.230000, 0x3e6b851f) +"W6": float32as(0.300000, 0x3e99999a) +"W7": float32as(0.440000, 0x3ee147ae) +"W8": float32as(0.540000, 0x3f0a3d71) +"W9": float32as(0.620000, 0x3f1eb852) + +registered font, OS2 weights; table length >= 78 +1, 10 - 100: float32as(-0.800000, 0xbf4ccccd) +2, 101 - 200: float32as(-0.500000, 0xbf000000) +3, 201 - 300: float32as(-0.400000, 0xbecccccd) +4, 301 - 400: float32as(0.000000, 0x0) +5, 401 - 500: float32as(0.230000, 0x3e6b851f) +6, 501 - 600: float32as(0.250000, 0x3e800000) +7, 601 - 700: float32as(0.400000, 0x3ecccccd) +8, 701 - 800: float32as(0.500000, 0x3f000000) +9, 801 - 900: float32as(0.620000, 0x3f1eb852) +901 - 950: float32as(0.750000, 0x3f400000) +951 - 999: float32as(0.800000, 0x3f4ccccd) +0, 1000: panose +panose 2: float32as(-0.500000, 0xbf000000) +panose 3: float32as(-0.400000, 0xbecccccd) +panose 4: !!!! see note should be float32as(-0.300000, 0xbe99999a) but is treated as invalid instead due to returning false instead of true +panose 5: float32as(-0.230000, 0xbe6b851f) +panose 6: float32as(0.230000, 0x3e6b851f) +panose 7: float32as(0.250000, 0x3e800000) +panose 8: float32as(0.400000, 0x3ecccccd) +panose 9: float32as(0.560000, 0x3f0f5c29) +panose 10: float32as(0.620000, 0x3f1eb852) +panose 11: float32as(0.800000, 0x3f4ccccd) + +registered font, head table, low bit of byte 0x2D +'head'[0x2D] & 1: float32as(0.400000, 0x3ecccccd) + +registered font, abbreviated weight glossary, checks for (possibly in TrueType and OpenType only) in order: preferred subfamily, family; case-insensitive strict comparison +"EL": float32as(-0.200000, 0xbe4ccccd) +"EB": float32as(0.500000, 0x3f000000) +"SB": float32as(0.300000, 0x3e99999a) +"UH": float32as(0.800000, 0x3f4ccccd) +"U": float32as(0.700000, 0x3f333333) +"L": float32as(-0.400000, 0xbecccccd) +"H": float32as(0.560000, 0x3f0f5c29) +"B": float32as(0.400000, 0x3ecccccd) +"M": float32as(0.230000, 0x3e6b851f) +"R": float32as(0.000000, 0x0) + +registered font +default: float32as(0.000000, 0x0) + +// based on CoreText dylib's __Z13WeightOfClasst — WeightOfClass(unsigned short) +func CoreText_WeightOfClass(usWeightClass uint16) float64 { + if usWeightClass >= 11 { + // do nothing; we are preserving the original asm comparisons + // and yes, this one is 11, but the one above is 10 + } else { + usWeightClass *= 100 + } + + // figure out what two floats our weight will be between + i := usWeightClass / 100 + j := i + 1 + if j > 10 { + j = 10 + } + b := float64(i * 100) + c := float64(j * 100) + + a := float64(0) + if b != c { + a = float64(usWeightClass) + a -= b + c -= b + a /= c + } + scales := []float32{ + float32as(-1.000000, 0xbf800000), + float32as(-0.700000, 0xbf333333), + float32as(-0.500000, 0xbf000000), + float32as(-0.230000, 0xbe6b851f), + float32as(0.000000, 0x0), + float32as(0.200000, 0x3e4ccccd), + float32as(0.300000, 0x3e99999a), + float32as(0.400000, 0x3ecccccd), + float32as(0.600000, 0x3f19999a), + float32as(0.800000, 0x3f4ccccd), + float32as(1.000000, 0x3f800000), + } + c = float64(scale[i]) + b = float64[scale[j]) + return fma(a, b, c) +} + +unregistered font: kCTFontPostScriptNameKey defaults +"LucidaGrande": float64as(0.000000, 0x0) +".LucidaGrandeUI": float64as(0.000000, 0x0) +"STHeiti": float64as(0.240000, 0x3fceb851eb851eb8) +"STXihei": float64as(-0.100000, 0xbfb999999999999a) +"TimesNewRomanPSMT": float64as(0.000000, 0x0) + + + os2table := f.Table('OS/2') + if os2table != nil { + if !hasWeight { + var usWeightClass uint16 + + valid := false + if os2table.Len() > 77 { + b := os2table.Bytes() + usWeightClass = uint16be(b[4:6]) + if usWeightClass > 1000 { + weight = 0 + hasWeight = false + } else { + valid = true + } + } else { + usWeightClass = 0 + valid = true + } + if valid { + weight = CoreText_WeightOfClass(usWeightClass) + hasWeight = true + } + } + } + + +unregistered font, style glossary, checks against kCTFontSubFamilyNameKey, kCTFontFullNameKey, kCTFontFamilyNameKey; case-insensitive (folded by Unicode rules) strstr() +"ultra light": float32as(-0.800000, 0xbf4ccccd) +"ultra black": float32as(0.750000, 0x3f400000) +"extra light": float32as(-0.500000, 0xbf000000) +"ultralight": float32as(-0.800000, 0xbf4ccccd) +"ultrablack": float32as(0.750000, 0x3f400000) +"extrablack": float32as(0.800000, 0x3f4ccccd) +"extralight": float32as(-0.500000, 0xbf000000) +"heavy face": float32as(0.560000, 0x3f0f5c29) +"semi light": float32as(-0.200000, 0xbe4ccccd) +"extra bold": float32as(0.500000, 0x3f000000) +"ultra bold": float32as(0.700000, 0x3f333333) +"heavyface": float32as(0.560000, 0x3f0f5c29) +"extrabold": float32as(0.500000, 0x3f000000) +"ultrabold": float32as(0.700000, 0x3f333333) +"semilight": float32as(-0.200000, 0xbe4ccccd) +"demi bold": float32as(0.250000, 0x3e800000) +"semi bold": float32as(0.300000, 0x3e99999a) +"demibold": float32as(0.250000, 0x3e800000) +"semibold": float32as(0.300000, 0x3e99999a) +"hairline": float32as(-0.700000, 0xbf333333) +"medium": float32as(0.230000, 0x3e6b851f) +"poster": float32as(0.800000, 0x3f4ccccd) +"light": float32as(-0.400000, 0xbecccccd) +"heavy": float32as(0.560000, 0x3f0f5c29) +"extra": float32as(0.500000, 0x3f000000) +"black": float32as(0.620000, 0x3f1eb852) +"super": float32as(0.620000, 0x3f1eb852) +"obese": float32as(0.850000, 0x3f59999a) +"lite": float32as(-0.400000, 0xbecccccd) +"book": float32as(-0.230000, 0xbe6b851f) +"demi": float32as(0.250000, 0x3e800000) +"semi": float32as(0.300000, 0x3e99999a) +"thin": float32as(-0.500000, 0xbf000000) +"bold": float32as(0.400000, 0x3ecccccd) +"nord": float32as(0.800000, 0x3f4ccccd) +"fat": float32as(0.750000, 0x3f400000) +"w1": float32as(-0.700000, 0xbf333333) +"w2": float32as(-0.500000, 0xbf000000) +"w3": float32as(-0.230000, 0xbe6b851f) +"w4": float32as(0.000000, 0x0) +"w5": float32as(0.230000, 0x3e6b851f) +"w6": float32as(0.300000, 0x3e99999a) +"w7": float32as(0.440000, 0x3ee147ae) +"w8": float32as(0.540000, 0x3f0a3d71) +"w9": float32as(0.620000, 0x3f1eb852) + +func (f *Font) ShouldEnableBoldSymbolicTrait() bool { + if f.IsRegistered() { + return f.ShouldEnableBoldSymbolicTraitFromRegistry() + } + no := f.Weight() <= float64as(0.239000, 0x3fce978d4fdf3b64) + return !no +} diff --git a/doc/export/ctwidths b/doc/export/ctwidths new file mode 100644 index 00000000..dd6d2ca2 --- /dev/null +++ b/doc/export/ctwidths @@ -0,0 +1,160 @@ +xx pseudo-go + +func (f *CTFont) RegistryWidth32() float32 { + metadata visual descriptors + { "med", float32as(0.000000, 0x0) }, + { "cond", float32as(-0.200000, 0xbe4ccccd) }, + { "ext", float32as(0.200000, 0x3e4ccccd) }, + + style dictionary + { "Extra Compressed", float32as(-0.700000, 0xbf333333) }, + { "Ultra Compressed", float32as(-0.700000, 0xbf333333) }, + { "Ultra Condensed", float32as(-0.700000, 0xbf333333) }, + { "Extra Condensed", float32as(-0.500000, 0xbf000000) }, + { "Extra Extended", float32as(0.400000, 0x3ecccccd) }, + { "Ext Compressed", float32as(-0.700000, 0xbf333333) }, + { "Ultra Expanded", float32as(0.800000, 0x3f4ccccd) }, + { "Ultra Extended", float32as(0.800000, 0x3f4ccccd) }, + { "Extra Expanded", float32as(0.400000, 0x3ecccccd) }, + xx TODO this is weird, but correct... + { "Semi Condensed", float32as(-0.700000, 0xbf333333) }, + { "Semi Condensed", float32as(-0.100000, 0xbdcccccd) }, + xx end TODO + { "Ext Condensed", float32as(-0.500000, 0xbf000000) }, + { "SemiCondensed", float32as(-0.100000, 0xbdcccccd) }, + { "ExtraExpanded", float32as(0.400000, 0x3ecccccd) }, + { "Semi Expanded", float32as(0.100000, 0x3dcccccd) }, + { "Semi Extended", float32as(0.100000, 0x3dcccccd) }, + { "Ext Expanded", float32as(0.400000, 0x3ecccccd) }, + { "Ext Extended", float32as(0.400000, 0x3ecccccd) }, + { "SemiExpanded", float32as(0.100000, 0x3dcccccd) }, + { "Extra Narrow", float32as(-0.500000, 0xbf000000) }, + { "ExtraNarrow", float32as(-0.500000, 0xbf000000) }, + { "Extra Wide", float32as(0.800000, 0x3f4ccccd) }, + { "Ultra Cond", float32as(-0.700000, 0xbf333333) }, + { "Compressed", float32as(-0.500000, 0xbf000000) }, + { "Extra Cond", float32as(-0.500000, 0xbf000000) }, + { "Semi Cond", float32as(-0.100000, 0xbdcccccd) }, + { "Condensed", float32as(-0.200000, 0xbe4ccccd) }, + { "ExtraWide", float32as(0.800000, 0x3f4ccccd) }, + { "Extended", float32as(0.200000, 0x3e4ccccd) }, + { "Expanded", float32as(0.200000, 0x3e4ccccd) }, + { "Ext Cond", float32as(-0.500000, 0xbf000000) }, + { "Narrow", float32as(-0.400000 , 0xbecccccd) }, + { "Compact", float32as(-0.400000, 0xbecccccd) }, + { "Cond", float32as(-0.200000, 0xbe4ccccd) }, + { "Wide", float32as(0.600000, 0x3f19999a) }, + { "Thin", float32as(-0.700000, 0xbf333333) }, + + get os2 table + if os2table.Len() >= 78 { + usWidthClass := uint16be(b[6:8]) - 1 + xx this handles the case where the original usWidthClass == 0 + if usWidthClass > 8 { + panose := b[0x23] - 2 + if panose > 6 { + xx TODO + } else { + switch panose { + case 0, 1, 2: + width = float32as(0.000000, 0x0) + case 3: + width = float32as(0.200000, 0x3e4ccccd) + case 4: + width = float32as(-0.200000, 0xbe4ccccd) + case 5: + width = float32as(0.400000, 0x3ecccccd) + case 6: + width = float32as(-0.400000, 0xbecccccd) + } + } + } + switch usWidthClass { + case 0: + width = float32as(-0.700000, 0xbf333333) + case 1: + width = float32as(-0.500000, 0xbf000000) + case 2: + width = float32as(-0.200000, 0xbe4ccccd) + case 3: + width = float32as(-0.100000, 0xbdcccccd) + case 4: + width = float32as(0.000000, 0x0) + case 5: + width = float32as(0.100000, 0x3dcccccd) + case 6: + width = float32as(0.400000, 0x3ecccccd) + case 7: + width = float32as(0.600000, 0x3f19999a) + case 8: + width = float32as(0.800000, 0x3f4ccccd) + } + } + + headtable := f.CopyTable('head') + if headtable != nil { + if headtable.Len() >= 54 { + x := b[0x2d] + if (x & 0x20) != 0 { + width = float32as(-0.200000, 0xbe4ccccd) + } else if (x & 0x40) != 0 { + width = float32as(0.200000, 0x3e4ccccd) + } + } + } + + xx and if all else fails + return float32as(0.000000, 0x0) +} + +func (f *CTFont) Width() float64 { + if f.IsRegistered() { + return f.RegistryWidth() + } + + width := 0.0 + hasWidth := false + + if there is an OS2 table { + var usWidthClass uint16 + + valid := false + if it's 78 bytes or more { + usWidthClass = uint16be(table[6:8]) + if usWeightClass <= 10 { + valid = true + } else { + valid = false + } + } else { + usWidthClass = 0 + valid = true + } + if valid { + ten := float64as(10.000000, 0x4024000000000000) + negPointFive := float64as(-0.500000, 0xbfe0000000000000) + width = (float64(usWidthClass) div ten) + negPointFive + hasWidth = true + } + } + + then there's the style glossary strings comparison: + { "semi condensed", float32as(-0.100000, 0xbdcccccd) }, + { "extra expanded", float32as(0.400000, 0x3ecccccd) }, + { "semicondensed", float32as(-0.100000, 0xbdcccccd) }, + { "extraexpanded", float32as(0.400000, 0x3ecccccd) }, + { "semi expanded", float32as(0.100000, 0x3dcccccd) }, + { "semiexpanded", float32as(0.100000, 0x3dcccccd) }, + { "extra narrow", float32as(-0.500000, 0xbf000000) }, + { "extranarrow", float32as(-0.500000, 0xbf000000) }, + { "extra wide", float32as(0.800000, 0x3f4ccccd) }, + { "condensed", float32as(-0.200000, 0xbe4ccccd) }, + { "extrawide", float32as(0.800000, 0x3f4ccccd) }, + { "extended", float32as(0.200000, 0x3e4ccccd) }, + { "expanded", float32as(0.200000, 0x3e4ccccd) }, + { "narrow", float32as(-0.400000, 0xbecccccd) }, + { "wide", float32as(0.600000, 0x3f19999a) }, + { "thin", float32as(-0.700000, 0xbf333333) }, + + otherwise just return float64as(0.000000, 0x0) +} diff --git a/doc/export/ctwidthscombined b/doc/export/ctwidthscombined new file mode 100644 index 00000000..fe4ad9d9 --- /dev/null +++ b/doc/export/ctwidthscombined @@ -0,0 +1,77 @@ +-0.100000 + style string contains "Semi Cond" + style string contains "Semi Condensed"; unregistered fonts only (see below) + style string contains "SemiCondensed" + OS2 width 4 + +-0.200000 + ('head' table byte 0x2d) & 0x20 != 0 + ATSD style "cond" + panose 6 + style string contains "Cond" + style string contains "Condensed" + OS2 width 3 + +-0.400000 + panose 8 + style string contains "Compact" + style string contains "Narrow" + +-0.500000 + style string contains "Compressed" + style string contains "Ext Cond" + style string contains "Ext Condensed" + style string contains "Extra Cond" + style string contains "Extra Condensed" + style string contains "Extra Narrow" + style string contains "ExtraNarrow" + OS2 width 2 + +-0.700000 + style string contains "Ext Compressed" + style string contains "Extra Compressed" + style string contains "Semi Condensed" (this is probably a typo, since another "Semi Condensed" with a value of -0.1 follows this in the table it comes from); registered fonts only + style string contains "Thin" + style string contains "Ultra Compressed" + style string contains "Ultra Cond" + style string contains "Ultra Condensed" + OS2 width 1 + +0.000000 + default + ATSD style "med" + panose 2, 3, 4 + OS2 width 5 + +0.100000 + style string contains "Semi Expanded" + style string contains "Semi Extended" + style string contains "SemiExpanded" + OS2 width 6 + +0.200000 + ('head' table byte 0x2d) & 0x40 != 0 + ATSD style "ext" + panose 5 + style string contains "Expanded" + style string contains "Extended" + +0.400000 + panose 7 + style string contains "Ext Expanded" + style string contains "Ext Extended" + style string contains "Extra Expanded" + style string contains "Extra Extended" + style string contains "ExtraExpanded" + OS2 width 7 + +0.600000 + style string contains "Wide" + OS2 width 8 + +0.800000 + style string contains "Extra Wide" + style string contains "ExtraWide" + style string contains "Ultra Expanded" + style string contains "Ultra Extended" + OS2 width 9 diff --git a/doc/export/ctwidthsprocessed b/doc/export/ctwidthsprocessed new file mode 100644 index 00000000..26f9fcc2 --- /dev/null +++ b/doc/export/ctwidthsprocessed @@ -0,0 +1,73 @@ +-0.100000 0xbdcccccd registered "Semi Cond" +-0.100000 0xbdcccccd registered "Semi Condensed" +-0.100000 0xbdcccccd registered "SemiCondensed" +-0.100000 0xbdcccccd registered OS2 4 +-0.100000 0xbdcccccd unregistered "semi condensed" +-0.100000 0xbdcccccd unregistered "semicondensed" +-0.200000 0xbe4ccccd head[0x2d] & 0x20 +-0.200000 0xbe4ccccd metadata "cond" +-0.200000 0xbe4ccccd panose 6 +-0.200000 0xbe4ccccd registered "Cond" +-0.200000 0xbe4ccccd registered "Condensed" +-0.200000 0xbe4ccccd registered OS2 3 +-0.200000 0xbe4ccccd unregistered "condensed" +-0.400000 0xbecccccd panose 8 +-0.400000 0xbecccccd registered "Compact" +-0.400000 0xbecccccd registered "Narrow" +-0.400000 0xbecccccd unregistered "narrow" +-0.500000 0xbf000000 registered "Compressed" +-0.500000 0xbf000000 registered "Ext Cond" +-0.500000 0xbf000000 registered "Ext Condensed" +-0.500000 0xbf000000 registered "Extra Cond" +-0.500000 0xbf000000 registered "Extra Condensed" +-0.500000 0xbf000000 registered "Extra Narrow" +-0.500000 0xbf000000 registered "ExtraNarrow" +-0.500000 0xbf000000 registered OS2 2 +-0.500000 0xbf000000 unregistered "extra narrow" +-0.500000 0xbf000000 unregistered "extranarrow" +-0.700000 0xbf333333 registered "Ext Compressed" +-0.700000 0xbf333333 registered "Extra Compressed" +-0.700000 0xbf333333 registered "Semi Condensed" +-0.700000 0xbf333333 registered "Thin" +-0.700000 0xbf333333 registered "Ultra Compressed" +-0.700000 0xbf333333 registered "Ultra Cond" +-0.700000 0xbf333333 registered "Ultra Condensed" +-0.700000 0xbf333333 registered OS2 1 +-0.700000 0xbf333333 unregistered "thin" +0.000000 0x0 default +0.000000 0x0 metadata "med" +0.000000 0x0 panose 2, 3, 4 +0.000000 0x0 registered OS2 5 +0.000000 0x0 registered default +0.100000 0x3dcccccd registered "Semi Expanded" +0.100000 0x3dcccccd registered "Semi Extended" +0.100000 0x3dcccccd registered "SemiExpanded" +0.100000 0x3dcccccd registered OS2 6 +0.100000 0x3dcccccd unregistered "semi expanded" +0.100000 0x3dcccccd unregistered "semiexpanded" +0.200000 0x3e4ccccd head[0x2d] & 0x40 +0.200000 0x3e4ccccd metadata "ext" +0.200000 0x3e4ccccd panose 5 +0.200000 0x3e4ccccd registered "Expanded" +0.200000 0x3e4ccccd registered "Extended" +0.200000 0x3e4ccccd unregistered "expanded" +0.200000 0x3e4ccccd unregistered "extended" +0.400000 0x3ecccccd panose 7 +0.400000 0x3ecccccd registered "Ext Expanded" +0.400000 0x3ecccccd registered "Ext Extended" +0.400000 0x3ecccccd registered "Extra Expanded" +0.400000 0x3ecccccd registered "Extra Extended" +0.400000 0x3ecccccd registered "ExtraExpanded" +0.400000 0x3ecccccd registered OS2 7 +0.400000 0x3ecccccd unregistered "extra expanded" +0.400000 0x3ecccccd unregistered "extraexpanded" +0.600000 0x3f19999a registered "Wide" +0.600000 0x3f19999a registered OS2 8 +0.600000 0x3f19999a unregistered "wide" +0.800000 0x3f4ccccd registered "Extra Wide" +0.800000 0x3f4ccccd registered "ExtraWide" +0.800000 0x3f4ccccd registered "Ultra Expanded" +0.800000 0x3f4ccccd registered "Ultra Extended" +0.800000 0x3f4ccccd registered OS2 9 +0.800000 0x3f4ccccd unregistered "extra wide" +0.800000 0x3f4ccccd unregistered "extrawide" diff --git a/doc/export/ctwidthvalues b/doc/export/ctwidthvalues new file mode 100644 index 00000000..fbccfc3f --- /dev/null +++ b/doc/export/ctwidthvalues @@ -0,0 +1,112 @@ +metadata "med": float32as(0.000000, 0x0) +metadata "cond": float32as(-0.200000, 0xbe4ccccd) +metadata "ext": float32as(0.200000, 0x3e4ccccd) + +registered "Extra Compressed": float32as(-0.700000, 0xbf333333) +registered "Ultra Compressed": float32as(-0.700000, 0xbf333333) +registered "Ultra Condensed": float32as(-0.700000, 0xbf333333) +registered "Extra Condensed": float32as(-0.500000, 0xbf000000) +registered "Extra Extended": float32as(0.400000, 0x3ecccccd) +registered "Ext Compressed": float32as(-0.700000, 0xbf333333) +registered "Ultra Expanded": float32as(0.800000, 0x3f4ccccd) +registered "Ultra Extended": float32as(0.800000, 0x3f4ccccd) +registered "Extra Expanded": float32as(0.400000, 0x3ecccccd) +registered "Semi Condensed": float32as(-0.700000, 0xbf333333) +registered "Semi Condensed": float32as(-0.100000, 0xbdcccccd) +registered "Ext Condensed": float32as(-0.500000, 0xbf000000) +registered "SemiCondensed": float32as(-0.100000, 0xbdcccccd) +registered "ExtraExpanded": float32as(0.400000, 0x3ecccccd) +registered "Semi Expanded": float32as(0.100000, 0x3dcccccd) +registered "Semi Extended": float32as(0.100000, 0x3dcccccd) +registered "Ext Expanded": float32as(0.400000, 0x3ecccccd) +registered "Ext Extended": float32as(0.400000, 0x3ecccccd) +registered "SemiExpanded": float32as(0.100000, 0x3dcccccd) +registered "Extra Narrow": float32as(-0.500000, 0xbf000000) +registered "ExtraNarrow": float32as(-0.500000, 0xbf000000) +registered "Extra Wide": float32as(0.800000, 0x3f4ccccd) +registered "Ultra Cond": float32as(-0.700000, 0xbf333333) +registered "Compressed": float32as(-0.500000, 0xbf000000) +registered "Extra Cond": float32as(-0.500000, 0xbf000000) +registered "Semi Cond": float32as(-0.100000, 0xbdcccccd) +registered "Condensed": float32as(-0.200000, 0xbe4ccccd) +registered "ExtraWide": float32as(0.800000, 0x3f4ccccd) +registered "Extended": float32as(0.200000, 0x3e4ccccd) +registered "Expanded": float32as(0.200000, 0x3e4ccccd) +registered "Ext Cond": float32as(-0.500000, 0xbf000000) +registered "Narrow": float32as(-0.400000 , 0xbecccccd) +registered "Compact": float32as(-0.400000, 0xbecccccd) +registered "Cond": float32as(-0.200000, 0xbe4ccccd) +registered "Wide": float32as(0.600000, 0x3f19999a) +registered "Thin": float32as(-0.700000, 0xbf333333) + +panose 2, 3, 4: float32as(0.000000, 0x0) +panose 5: float32as(0.200000, 0x3e4ccccd) +panose 6: float32as(-0.200000, 0xbe4ccccd) +panose 7: float32as(0.400000, 0x3ecccccd) +panose 8: float32as(-0.400000, 0xbecccccd) + +registered OS2 1: float32as(-0.700000, 0xbf333333) +registered OS2 2: float32as(-0.500000, 0xbf000000) +registered OS2 3: float32as(-0.200000, 0xbe4ccccd) +registered OS2 4: float32as(-0.100000, 0xbdcccccd) +registered OS2 5: float32as(0.000000, 0x0) +registered OS2 6: float32as(0.100000, 0x3dcccccd) +registered OS2 7: float32as(0.400000, 0x3ecccccd) +registered OS2 8: float32as(0.600000, 0x3f19999a) +registered OS2 9: float32as(0.800000, 0x3f4ccccd) + +head[0x2d] & 0x20: float32as(-0.200000, 0xbe4ccccd) +head[0x2d] & 0x40: float32as(0.200000, 0x3e4ccccd) + +registered default: float32as(0.000000, 0x0) + +func (f *CTFont) Width() float64 { + if f.IsRegistered() { + return f.RegistryWidth() + } + + width := 0.0 + hasWidth := false + + if there is an OS2 table { + var usWidthClass uint16 + + valid := false + if it's 78 bytes or more { + usWidthClass = uint16be(table[6:8]) + if usWeightClass <= 10 { + valid = true + } else { + valid = false + } + } else { + usWidthClass = 0 + valid = true + } + if valid { + ten := float64as(10.000000, 0x4024000000000000) + negPointFive := float64as(-0.500000, 0xbfe0000000000000) + width = (float64(usWidthClass) div ten) + negPointFive + hasWidth = true + } + } + + then there's the style glossary strings comparison: +unregistered "semi condensed": float32as(-0.100000, 0xbdcccccd) +unregistered "extra expanded": float32as(0.400000, 0x3ecccccd) +unregistered "semicondensed": float32as(-0.100000, 0xbdcccccd) +unregistered "extraexpanded": float32as(0.400000, 0x3ecccccd) +unregistered "semi expanded": float32as(0.100000, 0x3dcccccd) +unregistered "semiexpanded": float32as(0.100000, 0x3dcccccd) +unregistered "extra narrow": float32as(-0.500000, 0xbf000000) +unregistered "extranarrow": float32as(-0.500000, 0xbf000000) +unregistered "extra wide": float32as(0.800000, 0x3f4ccccd) +unregistered "condensed": float32as(-0.200000, 0xbe4ccccd) +unregistered "extrawide": float32as(0.800000, 0x3f4ccccd) +unregistered "extended": float32as(0.200000, 0x3e4ccccd) +unregistered "expanded": float32as(0.200000, 0x3e4ccccd) +unregistered "narrow": float32as(-0.400000, 0xbecccccd) +unregistered "wide": float32as(0.600000, 0x3f19999a) +unregistered "thin": float32as(-0.700000, 0xbf333333) + +default: float64as(0.000000, 0x0) diff --git a/doc/export/fvar.swift b/doc/export/fvar.swift new file mode 100644 index 00000000..3d81c9d8 --- /dev/null +++ b/doc/export/fvar.swift @@ -0,0 +1,73 @@ +// 2 november 2017 +import Cocoa +import CoreText + +func fourccString(_ k: FourCharCode) -> String { + var c: [UInt8] = [0, 0, 0, 0] + c[0] = UInt8((k >> 24) & 0xFF) + c[1] = UInt8((k >> 16) & 0xFF) + c[2] = UInt8((k >> 8) & 0xFF) + c[3] = UInt8(k & 0xFF) + return String(bytes: c, encoding: .utf8)! +} + +var weightMin: Double = 0 +var weightMax: Double = 0 +var weightDef: Double = 0 +var weightVals: [String: Double] = [:] + +let attrs: [String: String] = [ + kCTFontFamilyNameAttribute as String: "Skia", +] +let bd = CTFontDescriptorCreateWithAttributes(attrs as CFDictionary) +let matches = CTFontDescriptorCreateMatchingFontDescriptors(bd, nil) as! [CTFontDescriptor] +let mfont = CTFontCreateWithFontDescriptor(matches[0], 0.0, nil) +let master = CTFontCopyVariationAxes(mfont) as! [NSDictionary] +master.forEach { d in + print("axis {") + d.forEach { k, v in + if (k as! String) == (kCTFontVariationAxisIdentifierKey as String) { + let c = v as! FourCharCode + print("\t\(k) \(fourccString(c))") + } else { + print("\t\(k) \(v)") + } + } + print("}") + if (d[kCTFontVariationAxisNameKey] as! String) == "Weight" { + weightMin = d[kCTFontVariationAxisMinimumValueKey] as! Double + weightMax = d[kCTFontVariationAxisMaximumValueKey] as! Double + weightDef = d[kCTFontVariationAxisDefaultValueKey] as! Double + } +} +print("") +matches.forEach { d in + let f = CTFontDescriptorCopyAttribute(d, kCTFontVariationAttribute) as! [FourCharCode: Double] + let n = CTFontDescriptorCopyAttribute(d, kCTFontStyleNameAttribute) as! String + print("\(n) {") + f.forEach { k, v in + print("\t\(fourccString(k)) \(v)") + } + print("}") + weightVals[n] = weightDef + if let v = f[2003265652] { + weightVals[n] = v + } +} +print("") +weightVals.forEach { k, v in + let basicScaled = (v - weightMin) / (weightMax - weightMin) + print("\(k) basic scaled = \(basicScaled) (OS2 \(UInt16(basicScaled * 1000)))") + // https://www.microsoft.com/typography/otspec/otvaroverview.htm#CSN + var opentypeScaled: Double = 0 + if v < weightDef { + opentypeScaled = -((weightDef - v) / (weightDef - weightMin)) + } else if v > weightDef { + opentypeScaled = (v - weightDef) / (weightMax - weightDef) + } + print("\(k) opentype scaled = \(opentypeScaled)") +} +print("") +print("\(String(describing: CTFontDescriptorCreateMatchingFontDescriptors(CTFontDescriptorCreateCopyWithVariation(matches[0], FourCharCode(2003265652) as CFNumber, CGFloat(weightMax)), Set([kCTFontVariationAttribute as String]) as CFSet)))") +print("") +print("\(CTFontCopyTable(mfont, CTFontTableTag(kCTFontTableAvar), []) != nil)") diff --git a/doc/export/ttfixedtest.go b/doc/export/ttfixedtest.go new file mode 100644 index 00000000..217afa50 --- /dev/null +++ b/doc/export/ttfixedtest.go @@ -0,0 +1,58 @@ +// 2 november 2017 +package main + +import ( + "fmt" +) + +type fixed1616 uint32 +type fixed214 uint16 + +func fixed1616To214(f fixed1616) fixed214 { + f += 0x00000002 + g := int32(f) >> 2 + return fixed214(uint32(g) & 0xFFFF) +} + +func (f fixed1616) In214Range() bool { + base := int16((f >> 16) & 0xFFFF) + return base >= -2 && base < 2 +} + +func (f fixed1616) String() string { + base := int16((f >> 16) & 0xFFFF) + frac := float64(f & 0xFFFF) / 65536 + res := float64(base) + frac + return fmt.Sprintf("%f 0x%08X", res, uint32(f)) +} + +func (f fixed214) String() string { + base := []int16{ + 0, + 1, + -2, + -1, + }[(f & 0xC000) >> 14] + frac := float64(f & 0x3FFF) / 16384 + res := float64(base) + frac + return fmt.Sprintf("%f 0x%04X", res, uint16(f)) +} + +func main() { + fmt.Println(fixed214(0x7fff)) + fmt.Println(fixed214(0x8000)) + fmt.Println(fixed214(0x4000)) + fmt.Println(fixed214(0xc000)) + fmt.Println(fixed214(0x7000)) + fmt.Println(fixed214(0x0000)) + fmt.Println(fixed214(0x0001)) + fmt.Println(fixed214(0xffff)) + + fmt.Println() + + for i := uint64(0x00000000); i <= 0xFFFFFFFF; i++ { + j := fixed1616(i) + if !j.In214Range() { continue } + fmt.Println(j, "->", fixed1616To214(j)) + } +} diff --git a/doc/export/weightlist1.sh b/doc/export/weightlist1.sh new file mode 100644 index 00000000..0a1267ea --- /dev/null +++ b/doc/export/weightlist1.sh @@ -0,0 +1,8 @@ +# 21 october 2017 +gawk ' +BEGIN { FS = "\t+" } +!/float..as/ { next } +{ i = 0; if ($1 == "") i++ } +(NF-i) != 2 { next } +{ print } +' "$@" diff --git a/doc/export/weightlist2.sh b/doc/export/weightlist2.sh new file mode 100644 index 00000000..df2c546d --- /dev/null +++ b/doc/export/weightlist2.sh @@ -0,0 +1,10 @@ +# 21 october 2017 +gawk ' +{ + gsub(/float..as\(/, "") + gsub(/,/, "", $(NF - 1)) + gsub(/\)$/, "") + split($0, parts, /:/) + print $(NF - 1) "\t" $NF "\t" parts[1] +} +' "$@" diff --git a/doc/export/weightlist3.sh b/doc/export/weightlist3.sh new file mode 100644 index 00000000..f772226b --- /dev/null +++ b/doc/export/weightlist3.sh @@ -0,0 +1,3 @@ +# 21 october 2017 +sort -t$'\t' -k1,1 -k2,2 "$@" | + column -t -s$'\t' diff --git a/doc/export/writewidths.c b/doc/export/writewidths.c new file mode 100644 index 00000000..2ba7d01b --- /dev/null +++ b/doc/export/writewidths.c @@ -0,0 +1,3 @@ +// 22 october 2017 +extern int realMain(void); +int main(void) { return realMain(); } diff --git a/doc/export/writewidths.out b/doc/export/writewidths.out new file mode 100644 index 00000000..83f6b413 --- /dev/null +++ b/doc/export/writewidths.out @@ -0,0 +1,11 @@ +unregistered OS2 0: float64as(-0.5, 0xbfe0000000000000) +unregistered OS2 1: float64as(-0.4, 0xbfd999999999999a) +unregistered OS2 2: float64as(-0.3, 0xbfd3333333333333) +unregistered OS2 3: float64as(-0.2, 0xbfc999999999999a) +unregistered OS2 4: float64as(-0.1, 0xbfb9999999999998) +unregistered OS2 5: float64as(0, 0x0000000000000000) +unregistered OS2 6: float64as(0.1, 0x3fb9999999999998) +unregistered OS2 7: float64as(0.2, 0x3fc9999999999998) +unregistered OS2 8: float64as(0.3, 0x3fd3333333333334) +unregistered OS2 9: float64as(0.4, 0x3fd999999999999a) +unregistered OS2 10: float64as(0.5, 0x3fe0000000000000) diff --git a/doc/export/writewidths.processed b/doc/export/writewidths.processed new file mode 100644 index 00000000..e0437d81 --- /dev/null +++ b/doc/export/writewidths.processed @@ -0,0 +1,11 @@ +-0.1 0xbfb9999999999998 unregistered OS2 4 +-0.2 0xbfc999999999999a unregistered OS2 3 +-0.3 0xbfd3333333333333 unregistered OS2 2 +-0.4 0xbfd999999999999a unregistered OS2 1 +-0.5 0xbfe0000000000000 unregistered OS2 0 +0 0x0000000000000000 unregistered OS2 5 +0.1 0x3fb9999999999998 unregistered OS2 6 +0.2 0x3fc9999999999998 unregistered OS2 7 +0.3 0x3fd3333333333334 unregistered OS2 8 +0.4 0x3fd999999999999a unregistered OS2 9 +0.5 0x3fe0000000000000 unregistered OS2 10 diff --git a/doc/export/writewidths.s b/doc/export/writewidths.s new file mode 100644 index 00000000..d10208d5 --- /dev/null +++ b/doc/export/writewidths.s @@ -0,0 +1,51 @@ +# 22 october 2017 +# clang -o writewidths writewidths.c writewidths.s -g -Wall -Wextra -pedantic -g +# thanks to: +# - http://www.idryman.org/blog/2014/12/02/writing-64-bit-assembly-on-mac-os-x/ +# - https://developer.apple.com/library/content/documentation/DeveloperTools/Reference/Assembler/060-i386_Addressing_Modes_and_Assembler_Instructions/i386_intructions.html#//apple_ref/doc/uid/TP30000825-TPXREF101 +# - https://stackoverflow.com/questions/46309041/trivial-macos-assembly-64-bit-program-has-incorrect-stack-alignment +# - https://www.google.com/search?q=macos+implement+main+in+assembly+-nasm&oq=macos+implement+main+in+assembly+-nasm&gs_l=psy-ab.3...12877.13839.0.13988.6.6.0.0.0.0.117.407.4j1.5.0....0...1.1.64.psy-ab..1.0.0....0.et6MkokjvwA +# - https://stackoverflow.com/questions/2529185/what-are-cfi-directives-in-gnu-assembler-gas-used-for + +.section __DATA,__data + +double10: + .quad 0x4024000000000000 +doubleNeg05: + .quad 0xbfe0000000000000 + +fmt: + .asciz "unregistered OS2 %ld:\tfloat64as(%g, 0x%016lx)\n" + +.section __TEXT,__text +.globl _realMain +_realMain: + pushq %rbp + movq %rsp, %rbp + addq $8, %rsp + + xorq %rcx, %rcx +loop: + pushq %rcx + # the code from core text + movzwl %cx, %ecx + xorps %xmm0, %xmm0 + cvtsi2sdl %ecx, %xmm0 + divsd double10(%rip), %xmm0 + addsd doubleNeg05(%rip), %xmm0 + # end core text code + popq %rcx + pushq %rcx + movd %xmm0, %rdx + movzwq %cx, %rsi + leaq fmt(%rip), %rdi + callq _printf + popq %rcx + incw %cx + cmpw $10, %cx + jbe loop + + xorq %rax, %rax + subq $8, %rsp + popq %rbp + ret diff --git a/doc/form b/doc/form new file mode 100644 index 00000000..f24a94dc --- /dev/null +++ b/doc/form @@ -0,0 +1 @@ +hiding a control also hides its label diff --git a/doc/main b/doc/main new file mode 100644 index 00000000..9fa9c369 --- /dev/null +++ b/doc/main @@ -0,0 +1 @@ +after uiQuit or if uiShouldQuit returns nonzero, uiQueueMain's effect is undefined diff --git a/doc/mainsteps b/doc/mainsteps new file mode 100644 index 00000000..f572b21c --- /dev/null +++ b/doc/mainsteps @@ -0,0 +1 @@ +the function passed to mainsteps must not return until uiQuit itself has been called; otherwise the results are undefined diff --git a/doc/misctests/gtkprogresstable.c b/doc/misctests/gtkprogresstable.c new file mode 100644 index 00000000..220cb370 --- /dev/null +++ b/doc/misctests/gtkprogresstable.c @@ -0,0 +1,136 @@ +// 25 june 2018 +#include + +GtkWidget *mainwin; +GtkWidget *vbox; +GtkWidget *hbox; +GtkWidget *startProgress; +GtkWidget *startTable; +GtkWidget *progressbar; +GtkWidget *scrolledWindow; +GtkListStore *model; +GtkWidget *treeview; +GtkWidget *hbox2; + +static gboolean pulseProgress(gpointer data) +{ + gtk_progress_bar_pulse(GTK_PROGRESS_BAR(progressbar)); + return TRUE; +} + +static void onStartProgressClicked(GtkButton *button, gpointer data) +{ + gtk_widget_set_sensitive(startProgress, FALSE); + g_timeout_add(100, pulseProgress, NULL); +} + +gboolean pbarStarted = FALSE; +gint pbarValue; + +static void pbarDataFunc(GtkTreeViewColumn *col, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data) +{ + if (!pbarStarted) { + g_object_set(r, + "pulse", -1, + "value", 0, + NULL); + return; + } + pbarValue++; + if (pbarValue == G_MAXINT) + pbarValue = 1; + g_object_set(r, "pulse", pbarValue, NULL); +} + +static gboolean pulseTable(gpointer data) +{ + GtkTreePath *path; + GtkTreeIter iter; + + path = gtk_tree_path_new_from_indices(0, -1); + gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path); + gtk_tree_model_row_changed(GTK_TREE_MODEL(model), path, &iter); + gtk_tree_path_free(path); + return TRUE; +} + +static void onStartTableClicked(GtkButton *button, gpointer data) +{ + pbarStarted = TRUE; + pbarValue = 0; + + gtk_widget_set_sensitive(startTable, FALSE); + g_timeout_add(100, pulseTable, NULL); +} + +static gboolean onClosing(GtkWidget *win, GdkEvent *e, gpointer data) +{ + gtk_main_quit(); + return FALSE; +} + +int main(void) +{ + GtkTreeIter iter; + GtkTreeViewColumn *col; + GtkCellRenderer *r; + + gtk_init(NULL, NULL); + + mainwin = gtk_window_new(GTK_WINDOW_TOPLEVEL); + g_signal_connect(mainwin, "delete-event", G_CALLBACK(onClosing), NULL); + + vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 12); + gtk_container_add(GTK_CONTAINER(mainwin), vbox); + + hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); + gtk_widget_set_halign(hbox, GTK_ALIGN_CENTER); + gtk_container_add(GTK_CONTAINER(vbox), hbox); + + startProgress = gtk_button_new_with_label("Start Progress Bar"); + g_signal_connect(startProgress, "clicked", G_CALLBACK(onStartProgressClicked), NULL); + gtk_container_add(GTK_CONTAINER(hbox), startProgress); + + startTable = gtk_button_new_with_label("Start Table Cell Renderer"); + g_signal_connect(startTable, "clicked", G_CALLBACK(onStartTableClicked), NULL); + gtk_container_add(GTK_CONTAINER(hbox), startTable); + + progressbar = gtk_progress_bar_new(); + gtk_container_add(GTK_CONTAINER(vbox), progressbar); + + scrolledWindow = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledWindow), GTK_SHADOW_IN); + gtk_widget_set_vexpand(scrolledWindow, TRUE); + gtk_container_add(GTK_CONTAINER(vbox), scrolledWindow); + + model = gtk_list_store_new(1, G_TYPE_INT); + gtk_list_store_append(model, &iter); + gtk_list_store_set(model, &iter, + 0, 0, + -1); + + treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); + gtk_container_add(GTK_CONTAINER(scrolledWindow), treeview); + + col = gtk_tree_view_column_new(); + gtk_tree_view_column_set_resizable(col, TRUE); + gtk_tree_view_column_set_title(col, "Column"); + gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), col); + + r = gtk_cell_renderer_progress_new(); + gtk_tree_view_column_pack_start(col, r, TRUE); + gtk_tree_view_column_set_cell_data_func(col, r, pbarDataFunc, NULL, NULL); + + hbox2 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); + gtk_widget_set_halign(hbox2, GTK_ALIGN_CENTER); + gtk_container_add(GTK_CONTAINER(vbox), hbox2); + + gtk_container_add(GTK_CONTAINER(hbox2), gtk_button_new_with_label("These buttons")); + gtk_container_add(GTK_CONTAINER(hbox2), gtk_button_new_with_label("do nothing")); + gtk_container_add(GTK_CONTAINER(hbox2), gtk_button_new_with_label("when clicked")); + + gtk_widget_show_all(mainwin); + gtk_main(); + return 0; +} diff --git a/doc/misctests/winbuttonexplorertheme.cpp b/doc/misctests/winbuttonexplorertheme.cpp new file mode 100644 index 00000000..de70db66 --- /dev/null +++ b/doc/misctests/winbuttonexplorertheme.cpp @@ -0,0 +1,907 @@ +// 9 october 2018 +#define UNICODE +#define _UNICODE +#define STRICT +#define STRICT_TYPED_ITEMIDS +#define WINVER 0x0600 /* from Microsoft's winnls.h */ +#define _WIN32_WINNT 0x0600 +#define _WIN32_WINDOWS 0x0600 /* from Microsoft's pdh.h */ +#define _WIN32_IE 0x0700 +#define NTDDI_VERSION 0x06000000 +#include +#include +#include +#include +#include +#include +#include +#include + +// cl winbuttonexplorertheme.cpp -MT -link user32.lib kernel32.lib gdi32.lib comctl32.lib uxtheme.lib msimg32.lib windows.res + +void diele(const char *func) +{ + DWORD le; + + le = GetLastError(); + fprintf(stderr, "%s: %I32u\n", func, le); + exit(EXIT_FAILURE); +} + +void diehr(const char *func, HRESULT hr) +{ + fprintf(stderr, "%s: 0x%08I32X\n", func, hr); + exit(EXIT_FAILURE); +} + +// TODO if we merge this into libui proper, this will need to be deduplicated +static inline HRESULT lastErrorToHRESULT(DWORD lastError) +{ + if (lastError == 0) + return E_FAIL; + return HRESULT_FROM_WIN32(lastError); +} + +HINSTANCE hInstance; +HWND leftButtons[5]; +HWND rightButtons[3]; + +class commandModuleStyleParams { +public: + virtual int partID_CMOD_MODULEBACKGROUND(void) const = 0; + virtual int partID_CMOD_TASKBUTTON(void) const = 0; + virtual int partID_CMOD_SPLITBUTTONLEFT(void) const = 0; + virtual int partID_CMOD_SPLITBUTTONRIGHT(void) const = 0; + virtual int partID_CMOD_MENUGLYPH(void) const = 0; + virtual int partID_CMOD_OVERFLOWGLYPH(void) const = 0; + + virtual int stateID_CMODS_NORMAL(void) const = 0; + virtual int stateID_CMODS_HOT(void) const = 0; + virtual int stateID_CMODS_PRESSED(void) const = 0; + virtual int stateID_CMODS_KEYFOCUSED(void) const = 0; + virtual int stateID_CMODS_NEARHOT(void) const = 0; + + virtual HRESULT backgroundGradientColors(HTHEME theme, COLORREF *colors) const = 0; + virtual HRESULT buttonTextColor(HTHEME theme, UINT uItemState, COLORREF *color) const = 0; + virtual BOOL buttonTextShadowed(UINT uItemState) const = 0; + + virtual int folderBarMarginsLeftDIP(void) const = 0; + virtual int folderBarMarginsTopDIP(void) const = 0; + virtual int folderBarMarginsRightDIP(void) const = 0; + virtual int folderBarMarginsBottomDIP(void) const = 0; + + virtual int buttonMarginsXDIP(void) const = 0; + virtual int buttonMarginsYDIP(void) const = 0; + virtual int buttonTextArrowSeparationXDIP(void) const = 0; +}; + +class commandModuleStyleParamsVista : public commandModuleStyleParams { +public: + virtual int partID_CMOD_MODULEBACKGROUND(void) const { return 1; } + virtual int partID_CMOD_TASKBUTTON(void) const { return 2; } + virtual int partID_CMOD_SPLITBUTTONLEFT(void) const { return 3; } + virtual int partID_CMOD_SPLITBUTTONRIGHT(void) const { return 4; } + virtual int partID_CMOD_MENUGLYPH(void) const { return 5; } + virtual int partID_CMOD_OVERFLOWGLYPH(void) const { return 6; } + + virtual int stateID_CMODS_NORMAL(void) const { return 1; } + virtual int stateID_CMODS_HOT(void) const { return 2; } + virtual int stateID_CMODS_PRESSED(void) const { return 3; } + virtual int stateID_CMODS_KEYFOCUSED(void) const { return 4; } + virtual int stateID_CMODS_NEARHOT(void) const { return 5; } + + virtual HRESULT backgroundGradientColors(HTHEME theme, COLORREF *colors) const + { + if (colors == NULL) + return E_POINTER; +#define argb(a, r, g, b) ((((COLORREF) ((BYTE) (a))) << 24) | RGB(r, g, b)) + colors[0] = argb(255, 4, 80, 130); + colors[1] = argb(255, 17, 101, 132); + colors[2] = argb(255, 29, 121, 134); + return S_OK; + } + + virtual HRESULT buttonTextColor(HTHEME theme, UINT uItemState, COLORREF *color) const + { + if (color == NULL) + return E_POINTER; + *color = GetSysColor(COLOR_WINDOW); + if ((uItemState & CDIS_DISABLED) != 0) + *color = argb(255, 161, 204, 210); +#undef argb + return S_OK; + } + + virtual BOOL buttonTextShadowed(UINT uItemState) const + { + return (uItemState & CDIS_DISABLED) == 0; + } + + virtual int folderBarMarginsLeftDIP(void) const { return 3; } + virtual int folderBarMarginsTopDIP(void) const { return 2; } + virtual int folderBarMarginsRightDIP(void) const { return 3; } + virtual int folderBarMarginsBottomDIP(void) const { return 3; } + + virtual int buttonMarginsXDIP(void) const { return 6; } + virtual int buttonMarginsYDIP(void) const { return 5; } + virtual int buttonTextArrowSeparationXDIP(void) const { return 3; } +}; + +class commandModuleStyleParams7 : public commandModuleStyleParams { + virtual int partID_CMOD_MODULEBACKGROUND(void) const { return 1; } + virtual int partID_CMOD_TASKBUTTON(void) const { return 3; } + virtual int partID_CMOD_SPLITBUTTONLEFT(void) const { return 4; } + virtual int partID_CMOD_SPLITBUTTONRIGHT(void) const { return 5; } + virtual int partID_CMOD_MENUGLYPH(void) const { return 6; } + virtual int partID_CMOD_OVERFLOWGLYPH(void) const { return 7; } + + virtual int stateID_CMODS_NORMAL(void) const { return 1; } + virtual int stateID_CMODS_HOT(void) const { return 2; } + virtual int stateID_CMODS_PRESSED(void) const { return 3; } + virtual int stateID_CMODS_KEYFOCUSED(void) const { return 4; } + /*TODO verify this*/virtual int stateID_CMODS_NEARHOT(void) const { return 5; } + + virtual HRESULT backgroundGradientColors(HTHEME theme, COLORREF *colors) const + { + HRESULT hr; + + if (colors == NULL) + return E_POINTER; + hr = GetThemeColor(theme, + 2, 0, + TMT_GRADIENTCOLOR1, colors + 0); + if (hr != S_OK) + return hr; + hr = GetThemeColor(theme, + 2, 0, + TMT_GRADIENTCOLOR2, colors + 1); + if (hr != S_OK) + return hr; + return GetThemeColor(theme, + 2, 0, + TMT_GRADIENTCOLOR3, colors + 2); + } + + virtual HRESULT buttonTextColor(HTHEME theme, UINT uItemState, COLORREF *color) const + { + int state; + + if (color == NULL) + return E_POINTER; + state = 1; + if ((uItemState & CDIS_DISABLED) != 0) + state = 6; + // TODO check if 3 is the correct part ID for all button types + return GetThemeColor(theme, + 3, state, + TMT_TEXTCOLOR, color); + } + + virtual BOOL buttonTextShadowed(UINT uItemState) const + { + return FALSE; + } + + virtual int folderBarMarginsLeftDIP(void) const { return 3; } + virtual int folderBarMarginsTopDIP(void) const { return 2; } + virtual int folderBarMarginsRightDIP(void) const { return 9; } + virtual int folderBarMarginsBottomDIP(void) const { return 3; } + + virtual int buttonMarginsXDIP(void) const { return 13; } + virtual int buttonMarginsYDIP(void) const { return 5; } + virtual int buttonTextArrowSeparationXDIP(void) const { return 1; } +}; + +// all coordinates are in client space +struct buttonMetrics { + SIZE fittingSize; + int baseX; + int baseY; + int dpiX; + int dpiY; + BOOL hasText; + SIZE textSize; + BOOL hasArrow; + SIZE arrowSize; +}; + +struct buttonRects { + RECT clientRect; + RECT textRect; + RECT arrowRect; +}; + +class commandModuleStyle { +public: + virtual HRESULT drawFolderBar(commandModuleStyleParams *p, HDC dc, RECT *rcWindow, RECT *rcPaint) const = 0; + virtual HRESULT buttonMetrics(commandModuleStyleParams *p, HWND button, HDC dc, struct buttonMetrics *m) const = 0; + virtual HRESULT buttonRects(commandModuleStyleParams *p, HWND button, struct buttonMetrics *m, struct buttonRects *r) const = 0; + virtual HRESULT drawButton(commandModuleStyleParams *p, HWND button, HDC dc, UINT uItemState, RECT *rcPaint) const = 0; +}; + +class commandModuleStyleThemed : public commandModuleStyle { + HTHEME theme; + HTHEME textstyleTheme; +public: + commandModuleStyleThemed(HTHEME theme, HTHEME textstyleTheme) + { + this->theme = theme; + this->textstyleTheme = textstyleTheme; + } + + virtual HRESULT drawFolderBar(commandModuleStyleParams *p, HDC dc, RECT *rcWindow, RECT *rcPaint) const + { + COLORREF colors[3]; + TRIVERTEX vertices[4]; + static GRADIENT_RECT gr[2] = { + { 0, 1 }, + { 2, 3 }, + }; + HRESULT hr; + + hr = p->backgroundGradientColors(this->theme, colors); + if (hr != S_OK) + return hr; + + vertices[0].x = rcWindow->left; + vertices[0].y = rcWindow->top; + vertices[0].Red = ((COLOR16) GetRValue(colors[0])) << 8; + vertices[0].Green = ((COLOR16) GetGValue(colors[0])) << 8; + vertices[0].Blue = ((COLOR16) GetBValue(colors[0])) << 8; + vertices[0].Alpha = ((COLOR16) LOBYTE(colors[0] >> 24)) << 8; + + vertices[1].x = (rcWindow->right - rcWindow->left) / 2; + vertices[1].y = rcWindow->bottom; + vertices[1].Red = ((COLOR16) GetRValue(colors[1])) << 8; + vertices[1].Green = ((COLOR16) GetGValue(colors[1])) << 8; + vertices[1].Blue = ((COLOR16) GetBValue(colors[1])) << 8; + vertices[1].Alpha = ((COLOR16) LOBYTE(colors[1] >> 24)) << 8; + + vertices[2] = vertices[1]; + vertices[2].y = rcWindow->top; + + vertices[3].x = rcWindow->right; + vertices[3].y = rcWindow->bottom; + vertices[3].Red = ((COLOR16) GetRValue(colors[2])) << 8; + vertices[3].Green = ((COLOR16) GetGValue(colors[2])) << 8; + vertices[3].Blue = ((COLOR16) GetBValue(colors[2])) << 8; + vertices[3].Alpha = ((COLOR16) LOBYTE(colors[2] >> 24)) << 8; + + if (GradientFill(dc, vertices, 4, (PVOID) gr, 2, GRADIENT_FILL_RECT_H) == FALSE) + return lastErrorToHRESULT(GetLastError()); + return DrawThemeBackground(this->theme, dc, + p->partID_CMOD_MODULEBACKGROUND(), 0, + rcWindow, rcPaint); + } + +#define hasNonsplitArrow(button) ((button) == leftButtons[0] || (button) == leftButtons[1] || (button) == leftButtons[2]) + +#define dlgUnitsToX(dlg, baseX) MulDiv((dlg), (baseX), 4) +#define dlgUnitsToY(dlg, baseY) MulDiv((dlg), (baseY), 8) +// TODO verify the parameter order +#define dipsToX(dip, dpiX) MulDiv((dip), (dpiX), 96) +#define dipsToY(dip, dpiY) MulDiv((dip), (dpiY), 96) + + // TODO for win7: the sizes are correct (according to UI Automation) but they don't visually match? + virtual HRESULT buttonMetrics(commandModuleStyleParams *p, HWND button, HDC dc, struct buttonMetrics *m) const + { + BOOL releaseDC; + TEXTMETRICW tm; + RECT r; + int minStdButtonHeight; + HRESULT hr; + + if (m == NULL) + return E_POINTER; + + releaseDC = FALSE; + if (dc == NULL) { + dc = GetDC(button); + if (dc == NULL) + return lastErrorToHRESULT(GetLastError()); + releaseDC = TRUE; + } + + ZeroMemory(&tm, sizeof (TEXTMETRICW)); + hr = GetThemeTextMetrics(this->textstyleTheme, dc, + TEXT_BODYTEXT, 0, + &tm); + if (hr != S_OK) + goto fail; + hr = GetThemeTextExtent(this->textstyleTheme, dc, + TEXT_BODYTEXT, 0, + L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 52, 0, + NULL, &r); + if (hr != S_OK) + goto fail; + m->baseX = (int) (((r.right - r.left) / 26 + 1) / 2); + m->baseY = (int) tm.tmHeight; + m->dpiX = GetDeviceCaps(dc, LOGPIXELSX); + m->dpiY = GetDeviceCaps(dc, LOGPIXELSY); + + m->fittingSize.cx = 0; + m->fittingSize.cy = 0; + + m->hasText = TRUE; + if (m->hasText) { + LRESULT n; + WCHAR *buf; + LOGFONTW lf; + + n = SendMessageW(button, WM_GETTEXTLENGTH, 0, 0); + buf = new WCHAR[n + 1]; + GetWindowTextW(button, buf, n + 1); + hr = GetThemeTextExtent(this->textstyleTheme, dc, + TEXT_BODYTEXT, 0, + buf, n, DT_CENTER, + NULL, &r); + delete[] buf; + if (hr != S_OK) + goto fail; + m->textSize.cx = r.right - r.left; + m->textSize.cy = r.bottom - r.top; + m->fittingSize.cx += m->textSize.cx; + m->fittingSize.cy += m->textSize.cy; + + // dui70.dll adds this to the width when "overhang" is enabled, and it seems to be enabled for our cases, but I can't tell what conditions it's enabled for... + // and yes, it seems to be using the raw lfHeight value here :/ + // TODO find the right TMT constant + hr = GetThemeFont(this->textstyleTheme, dc, + 4, 0, + TMT_FONT, &lf); + if (hr != S_OK) + goto fail; + m->fittingSize.cx += 2 * (abs(lf.lfHeight) / 6); + } + + m->hasArrow = hasNonsplitArrow(button); + if (m->hasArrow) { + // TS_MIN returns 1x1 and TS_DRAW returns 0x0, so... + hr = GetThemePartSize(theme, dc, + p->partID_CMOD_MENUGLYPH(), 0, + NULL, TS_TRUE, &(m->arrowSize)); + if (hr != S_OK) + goto fail; + m->fittingSize.cx += m->arrowSize.cx; + // TODO I don't think dui70.dll takes this into consideration... + if (m->fittingSize.cy < m->arrowSize.cy) + m->fittingSize.cy = m->arrowSize.cy; + m->fittingSize.cx += dipsToX(p->buttonTextArrowSeparationXDIP(), m->dpiX); + } + + m->fittingSize.cx += dipsToX(p->buttonMarginsXDIP(), m->dpiX) * 2; + m->fittingSize.cy += dipsToY(p->buttonMarginsYDIP(), m->dpiY) * 2; + + // and dui70.dll seems to do a variant of this but for text buttons only... + minStdButtonHeight = dlgUnitsToY(14, m->baseY); + if (m->fittingSize.cy < minStdButtonHeight) + m->fittingSize.cy = minStdButtonHeight; + + hr = S_OK; + fail: + if (releaseDC) + // TODO when migrating this to libui, this will need to be renamed to indicate we are intentionally ignoring errors + ReleaseDC(button, dc); + return hr; + }; + + // TODO check errors + virtual HRESULT buttonRects(commandModuleStyleParams *p, HWND button, struct buttonMetrics *m, struct buttonRects *r) const + { + if (r == NULL) + return E_POINTER; + + GetClientRect(button, &(r->clientRect)); + + if (m->hasText) + r->textRect = r->clientRect; + + if (m->hasArrow) { + r->arrowRect = r->clientRect; + r->arrowRect.left = r->arrowRect.right; + r->arrowRect.left -= dipsToX(p->buttonMarginsXDIP(), m->dpiX); + r->arrowRect.right = r->arrowRect.left; + r->arrowRect.left -= m->arrowSize.cx; + r->arrowRect.top += ((r->arrowRect.bottom - r->arrowRect.top) - m->arrowSize.cy) / 2; + r->arrowRect.bottom = r->arrowRect.top + m->arrowSize.cy; + + if (m->hasText) { + r->textRect.right = r->arrowRect.left - dipsToX(p->buttonTextArrowSeparationXDIP(), m->dpiX); + r->textRect.right += dipsToX(p->buttonMarginsXDIP(), m->dpiX); + } + } + + return S_OK; + } + + // TODO check errors fully + virtual HRESULT drawButton(commandModuleStyleParams *p, HWND button, HDC dc, UINT uItemState, RECT *rcPaint) const + { + struct buttonMetrics m; + struct buttonRects r; + int part, state; + HRESULT hr; + + hr = this->buttonMetrics(p, button, dc, &m); + if (hr != S_OK) + return hr; + hr = this->buttonRects(p, button, &m, &r); + if (hr != S_OK) + return hr; + + part = p->partID_CMOD_TASKBUTTON(); + state = p->stateID_CMODS_NORMAL(); + if ((uItemState & CDIS_FOCUS) != 0) + state = p->stateID_CMODS_KEYFOCUSED(); + if ((uItemState & CDIS_HOT) != 0) + state = p->stateID_CMODS_HOT(); + if ((uItemState & CDIS_SELECTED) != 0) + state = p->stateID_CMODS_PRESSED(); + hr = DrawThemeParentBackground(button, dc, rcPaint); + if (hr != S_OK) + return hr; + hr = DrawThemeBackground(this->theme, dc, + part, state, + &(r.clientRect), rcPaint); + if (hr != S_OK) + return hr; + + if (m.hasText) { + int textState; + COLORREF textColor; + LRESULT n; + WCHAR *buf; + DTTOPTS dttopts; + + hr = p->buttonTextColor(this->theme, uItemState, &textColor); + if (hr != S_OK) + return hr; + n = SendMessageW(button, WM_GETTEXTLENGTH, 0, 0); + buf = new WCHAR[n + 1]; + GetWindowTextW(button, buf, n + 1); + ZeroMemory(&dttopts, sizeof (DTTOPTS)); + dttopts.dwSize = sizeof (DTTOPTS); + dttopts.dwFlags = DTT_TEXTCOLOR; + dttopts.crText = textColor; + hr = DrawThemeTextEx(this->textstyleTheme, dc, + TEXT_BODYTEXT, 0, + buf, n, DT_CENTER | DT_VCENTER | DT_SINGLELINE, + &(r.textRect), &dttopts); + delete[] buf; + if (hr != S_OK) + return hr; + } + + if (m.hasArrow) { + // TODO verify the state ID on all platforms + hr = DrawThemeBackground(this->theme, dc, + p->partID_CMOD_MENUGLYPH(), 0, + &(r.arrowRect), rcPaint); + if (hr != S_OK) + return hr; + } + + return S_OK; + } +}; + +#if 0 +// TODO check errors +void drawExplorerChevron(HTHEME theme, HDC dc, HWND rebar, WPARAM band, RECT *rcPaint) +{ + REBARBANDINFOW rbi; + RECT r; + int state; + + ZeroMemory(&rbi, sizeof (REBARBANDINFOW)); + rbi.cbSize = sizeof (REBARBANDINFOW); + rbi.fMask = RBBIM_CHILD | RBBIM_CHEVRONLOCATION | RBBIM_CHEVRONSTATE; + SendMessageW(rebar, RB_GETBANDINFOW, band, (LPARAM) (&rbi)); + if ((rbi.uChevronState & STATE_SYSTEM_INVISIBLE) != 0) + return; + state = 1; + // TODO check if this is correct + if ((rbi.uChevronState & STATE_SYSTEM_FOCUSED) != 0) + state = 4; + if ((rbi.uChevronState & STATE_SYSTEM_HOTTRACKED) != 0) + state = 2; + if ((rbi.uChevronState & STATE_SYSTEM_PRESSED) != 0) + state = 3; + r = rbi.rcChevronLocation; + // TODO commctrl.h says this should be correct for the chevron rect, but it's not? +// MapWindowRect(rbi.hwndChild, rebar, &r); + DrawThemeBackground(theme, dc, + 3, state, + &r, rcPaint); + DrawThemeBackground(theme, dc, + 7, 1, + &r, rcPaint); +} +#endif + +HICON shieldIcon; +HICON applicationIcon; +HICON helpIcon; +HIMAGELIST rightList; + +HTHEME theme = NULL; +HTHEME textstyleTheme = NULL; +const char *which = "7"; +commandModuleStyle *cms = NULL; +commandModuleStyleParams *cmsp = NULL; +HIMAGELIST dropdownArrowList = NULL; + +static struct { + const WCHAR *text; + BOOL dropdown; +} leftbarButtons[] = { + { L"Organize", TRUE }, + { L"Include in library", TRUE }, + { L"Share with", TRUE }, + { L"Burn", FALSE }, + { L"New folder", FALSE }, +}; + +// TODO check errors +LRESULT drawExplorerButton(NMCUSTOMDRAW *nm) +{ + HRESULT hr; + + if (nm->dwDrawStage != CDDS_PREPAINT) + return CDRF_DODEFAULT; + hr = cms->drawButton(cmsp, nm->hdr.hwndFrom, + nm->hdc, nm->uItemState, &(nm->rc)); + if (hr != S_OK) + return CDRF_DODEFAULT; + return CDRF_SKIPDEFAULT; +} + +void onWM_CREATE(HWND hwnd) +{ + int buttonx, buttony; + int i; + + buttonx = 5; + buttony = 5; + for (i = 0; i < 5; i++) { + // TODO split buttons don't support arrow navigation? + leftButtons[i] = CreateWindowExW(0, + L"BUTTON", leftbarButtons[i].text, + WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, + buttonx, buttony, + 150, 30, + hwnd, (HMENU) (100 + i), hInstance, NULL); + if (leftButtons[i] == NULL) + diele("CreateWindowExW(L\"BUTTON\")"); + buttonx += 150; + } +} + +// TODO check errors +void updateTheme(HWND hwnd) +{ + if (cmsp != NULL) { + delete cmsp; + cmsp = NULL; + } + if (cms != NULL) { + delete cms; + cms = NULL; + } + if (textstyleTheme != NULL) { + CloseThemeData(textstyleTheme); + textstyleTheme = NULL; + } + if (theme != NULL) { + CloseThemeData(theme); + theme = NULL; + } + + theme = OpenThemeData(hwnd, L"CommandModule"); + textstyleTheme = OpenThemeData(hwnd, L"TEXTSTYLE"); + cms = new commandModuleStyleThemed(theme, textstyleTheme); + if (strcmp(which, "vista") == 0) + cmsp = new commandModuleStyleParamsVista; + else + cmsp = new commandModuleStyleParams7; +} + +void repositionButtons(HWND hwnd) +{ + HDWP dwp; + int buttonx, buttony; + HDC dc; + int dpiX; + int dpiY; + struct buttonMetrics m; + int i; + + dc = GetDC(hwnd); + if (dc == NULL) + diele("GetDC()"); + dpiX = GetDeviceCaps(dc, LOGPIXELSX); + dpiY = GetDeviceCaps(dc, LOGPIXELSY); + // TODO check error + ReleaseDC(hwnd, dc); + + dwp = BeginDeferWindowPos(5); + if (dwp == NULL) + diele("BeginDeferWindowPos()"); + buttonx = dipsToX(cmsp->folderBarMarginsLeftDIP(), dpiX); + buttony = dipsToY(cmsp->folderBarMarginsTopDIP(), dpiY); + for (i = 0; i < 5; i++) { + cms->buttonMetrics(cmsp, leftButtons[i], NULL, &m); + dwp = DeferWindowPos(dwp, leftButtons[i], NULL, + buttonx, buttony, m.fittingSize.cx, m.fittingSize.cy, + SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); + if (dwp == NULL) + diele("DeferWindowPos()"); + buttonx += m.fittingSize.cx; + } + if (EndDeferWindowPos(dwp) == 0) + diele("EndDeferWindowPos()"); +} + +// TODO check errors +void folderBarRect(HWND hwnd, HDC dc, RECT *r) +{ + int dpiX; + int dpiY; + RECT child; + int i; + + dpiX = GetDeviceCaps(dc, LOGPIXELSX); + dpiY = GetDeviceCaps(dc, LOGPIXELSY); + + GetClientRect(hwnd, r); + r->right -= r->left; + r->left = 0; + r->top = 0; + r->bottom = 0; + + for (i = 0; i < 5; i++) { + GetWindowRect(leftButtons[i], &child); + if (r->bottom < (child.bottom - child.top)) + r->bottom = (child.bottom - child.top); + } + + r->bottom += dipsToY(cmsp->folderBarMarginsTopDIP(), dpiY); + r->bottom += dipsToY(cmsp->folderBarMarginsBottomDIP(), dpiY); +} + +#if 0 +// TODO check errors +void handleEvents(HWND hwnd, WPARAM wParam) +{ + LRESULT n; + const WCHAR *selRebar = NULL, *selToolbar = NULL; + WCHAR *bufRebar = NULL, *bufToolbar = NULL; + BOOL changeRebar = FALSE, changeToolbar = FALSE; + BOOL invalidate = FALSE; + WPARAM check; + + switch (wParam) { + case MAKEWPARAM(300, CBN_SELCHANGE): + n = SendMessageW(rebarCombo, CB_GETCURSEL, 0, 0); + selRebar = rebarThemes[n]; + changeRebar = TRUE; + break; + case MAKEWPARAM(301, CBN_SELCHANGE): + n = SendMessageW(toolbarCombo, CB_GETCURSEL, 0, 0); + selToolbar = toolbarThemes[n]; + changeToolbar = TRUE; + break; + case MAKEWPARAM(200, BN_CLICKED): + drawmode = 0; + n = SendMessageW(rebarCombo, WM_GETTEXTLENGTH, 0, 0); + bufRebar = new WCHAR[n + 1]; + GetWindowTextW(rebarCombo, bufRebar, n + 1); + n = SendMessageW(toolbarCombo, WM_GETTEXTLENGTH, 0, 0); + bufToolbar = new WCHAR[n + 1]; + GetWindowTextW(toolbarCombo, bufToolbar, n + 1); + selRebar = bufRebar; + selToolbar = bufToolbar; + changeRebar = TRUE; + changeToolbar = TRUE; + break; + case MAKEWPARAM(201, BN_CLICKED): + drawmode = 1; + invalidate = TRUE; + break; + case MAKEWPARAM(302, BN_CLICKED): + ShowWindow(leftbar, SW_HIDE); + check = BST_CHECKED; + if (SendMessage(toolbarTransparentCheckbox, BM_GETCHECK, 0, 0) == BST_CHECKED) + check = BST_UNCHECKED; + SendMessage(toolbarTransparentCheckbox, BM_SETCHECK, check, 0); + if (check == BST_CHECKED) + SendMessageW(leftbar, TB_SETSTYLE, 0, toolbarStyles | TBSTYLE_LIST | TBSTYLE_TRANSPARENT); + else + SendMessageW(leftbar, TB_SETSTYLE, 0, toolbarStyles | TBSTYLE_LIST); + ShowWindow(leftbar, SW_SHOW); + break; + case MAKEWPARAM(202, BN_CLICKED): + SetFocus(leftbar); + break; + } + if (changeRebar) { + if (selRebar != NULL && wcscmp(selRebar, L"NULL") == 0) + selRebar = NULL; + if (selRebar != NULL && *selRebar != L'\0') + SendMessageW(rebar, RB_SETWINDOWTHEME, 0, (LPARAM) selRebar); + else + SetWindowTheme(rebar, selRebar, selRebar); + invalidate = TRUE; + } + if (changeToolbar) { + if (selToolbar != NULL && wcscmp(selToolbar, L"NULL") == 0) + selToolbar = NULL; + if (selToolbar != NULL && *selToolbar != L'\0') { + SendMessageW(leftbar, TB_SETWINDOWTHEME, 0, (LPARAM) selToolbar); + SendMessageW(rightbar, TB_SETWINDOWTHEME, 0, (LPARAM) selToolbar); + } else { + SetWindowTheme(leftbar, selToolbar, selToolbar); + SetWindowTheme(rightbar, selToolbar, selToolbar); + } + invalidate = TRUE; + } + if (invalidate) + InvalidateRect(hwnd, NULL, TRUE); + if (bufRebar != NULL) + delete[] bufRebar; + if (bufToolbar != NULL) + delete[] bufToolbar; +} +#endif + +LRESULT CALLBACK wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + HDC dc; + PAINTSTRUCT ps; + NMHDR *nm = (NMHDR *) lParam; + int i; + + switch (uMsg) { + case WM_CREATE: + onWM_CREATE(hwnd); + updateTheme(hwnd); + repositionButtons(hwnd); + break; + case WM_CLOSE: + PostQuitMessage(0); + break; + case WM_SIZE: + repositionButtons(hwnd); + // TODO check errors + InvalidateRect(hwnd, NULL, TRUE); + break; + case WM_THEMECHANGED: + updateTheme(hwnd); + repositionButtons(hwnd); + break; + case WM_PAINT: + // TODO check errors + dc = BeginPaint(hwnd, &ps); + {RECT w; + folderBarRect(hwnd, dc, &w); + cms->drawFolderBar(cmsp, dc, &w, &(ps.rcPaint));} + EndPaint(hwnd, &ps); + return 0; + case WM_PRINTCLIENT: + {RECT w, paint; + folderBarRect(hwnd, (HDC) wParam, &w); + GetClientRect(hwnd,&paint); + cms->drawFolderBar(cmsp, (HDC) wParam, &w, &w);} + return 0; +#if 0 + case WM_COMMAND: + handleEvents(hwnd, wParam); + break; +#endif + case WM_NOTIFY: + switch (nm->code) { + case NM_CUSTOMDRAW: + for (i = 0; i < 5; i++) + if (nm->hwndFrom == leftButtons[i]) + return drawExplorerButton((NMCUSTOMDRAW *) nm); + break; + } + break; + } + return DefWindowProcW(hwnd, uMsg, wParam, lParam); +} + +EXTERN_C IMAGE_DOS_HEADER __ImageBase; + +int main(int argc, char *argv[]) +{ + STARTUPINFOW si; + int nCmdShow; + INITCOMMONCONTROLSEX icc; + HICON hDefaultIcon; + HCURSOR hDefaultCursor; + WNDCLASSW wc; + HWND mainwin; + MSG msg; + HRESULT hr; + + if (argc > 1) + which = argv[1]; + + hInstance = (HINSTANCE) (&__ImageBase); + nCmdShow = SW_SHOWDEFAULT; + GetStartupInfoW(&si); + if ((si.dwFlags & STARTF_USESHOWWINDOW) != 0) + nCmdShow = si.wShowWindow; + + ZeroMemory(&icc, sizeof (INITCOMMONCONTROLSEX)); + icc.dwSize = sizeof (INITCOMMONCONTROLSEX); + icc.dwICC = ICC_STANDARD_CLASSES | ICC_BAR_CLASSES | ICC_COOL_CLASSES; + if (InitCommonControlsEx(&icc) == 0) + diele("InitCommonControlsEx()"); + + hDefaultIcon = LoadIconW(NULL, IDI_APPLICATION); + if (hDefaultIcon == NULL) + diele("LoadIconW(IDI_APPLICATION)"); + hDefaultCursor = LoadCursorW(NULL, IDC_ARROW); + if (hDefaultCursor == NULL) + diele("LoadCursorW(IDC_ARROW)"); + + hr = LoadIconMetric(NULL, IDI_SHIELD, LIM_SMALL, &shieldIcon); + if (hr != S_OK) + diehr("LoadIconMetric(IDI_SHIELD)", hr); + hr = LoadIconMetric(NULL, IDI_APPLICATION, LIM_SMALL, &applicationIcon); + if (hr != S_OK) + diehr("LoadIconMetric(IDI_APPLICATION)", hr); + hr = LoadIconMetric(NULL, IDI_QUESTION, LIM_SMALL, &helpIcon); + if (hr != S_OK) + diehr("LoadIconMetric(IDI_QUESTION)", hr); + rightList = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), + ILC_COLOR32, 0, 3); + if (rightList == NULL) + diele("ImageList_Create()"); + if (ImageList_ReplaceIcon(rightList, -1, shieldIcon) == -1) + diele("ImageList_ReplaceIcon(IDI_SHIELD)"); + if (ImageList_ReplaceIcon(rightList, -1, applicationIcon) == -1) + diele("ImageList_ReplaceIcon(IDI_APPLICATION)"); + if (ImageList_ReplaceIcon(rightList, -1, helpIcon) == -1) + diele("ImageList_ReplaceIcon(IDI_QUESTION)"); + + ZeroMemory(&wc, sizeof (WNDCLASSW)); + wc.lpszClassName = L"mainwin"; + wc.lpfnWndProc = wndproc; + wc.hInstance = hInstance; + wc.hIcon = hDefaultIcon; + wc.hCursor = hDefaultCursor; + wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1); + if (RegisterClassW(&wc) == 0) + diele("RegisterClassW()"); + + mainwin = CreateWindowExW(0, + L"mainwin", L"Main Window", + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + NULL, NULL, hInstance, NULL); + if (mainwin == NULL) + diele("CreateWindowExW(L\"mainwin\")"); + + ShowWindow(mainwin, nCmdShow); + if (UpdateWindow(mainwin) == 0) + diele("UpdateWindow()"); + + for (;;) { + int res; + + res = GetMessageW(&msg, NULL, 0, 0); + if (res < 0) + diele("GetMessageW()"); + if (res == 0) + break; + if (IsDialogMessageW(mainwin, &msg) == 0) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + return 0; +} diff --git a/doc/misctests/winrebarexplorertheme.cpp b/doc/misctests/winrebarexplorertheme.cpp new file mode 100644 index 00000000..ddb08192 --- /dev/null +++ b/doc/misctests/winrebarexplorertheme.cpp @@ -0,0 +1,676 @@ +// 9 october 2018 +#define UNICODE +#define _UNICODE +#define STRICT +#define STRICT_TYPED_ITEMIDS +#define WINVER 0x0600 /* from Microsoft's winnls.h */ +#define _WIN32_WINNT 0x0600 +#define _WIN32_WINDOWS 0x0600 /* from Microsoft's pdh.h */ +#define _WIN32_IE 0x0700 +#define NTDDI_VERSION 0x06000000 +#include +#include +#include +#include +#include +#include +#include +#include + +// cl winrebarexplorertheme.cpp -MT -link user32.lib kernel32.lib gdi32.lib comctl32.lib uxtheme.lib msimg32.lib windows.res + +void diele(const char *func) +{ + DWORD le; + + le = GetLastError(); + fprintf(stderr, "%s: %I32u\n", func, le); + exit(EXIT_FAILURE); +} + +void diehr(const char *func, HRESULT hr) +{ + fprintf(stderr, "%s: 0x%08I32X\n", func, hr); + exit(EXIT_FAILURE); +} + +HINSTANCE hInstance; +HWND rebar; +HWND leftbar; +HWND rightbar; +HWND rebarCombo; +HWND toolbarCombo; +HWND toolbarTransparentCheckbox; + +HICON shieldIcon; +HICON applicationIcon; +HICON helpIcon; +HIMAGELIST rightList; + +#define toolbarStyles (WS_CHILD | CCS_NODIVIDER | CCS_NOPARENTALIGN | CCS_NORESIZE | TBSTYLE_FLAT) + +static struct { + const WCHAR *text; + BOOL dropdown; +} leftbarButtons[] = { + { L"Organize", TRUE }, + { L"Include in library", TRUE }, + { L"Share with", TRUE }, + { L"Burn", FALSE }, + { L"New folder", FALSE }, +}; + +// TODO check errors +// TODO extract colors from the theme +void drawExplorerBackground(HTHEME theme, HDC dc, RECT *rcWindow, RECT *rcPaint) +{ + static TRIVERTEX vertices[] = { + { 0, 0, 4 << 8, 80 << 8, 130 << 8, 255 << 8 }, + { 0, 0, 17 << 8, 101 << 8, 132 << 8, 255 << 8 }, + { 0, 0, 17 << 8, 101 << 8, 132 << 8, 255 << 8 }, + { 0, 0, 29 << 8, 121 << 8, 134 << 8, 255 << 8 }, + }; + static GRADIENT_RECT gr[2] = { + { 0, 1 }, + { 2, 3 }, + }; + + vertices[0].x = rcPaint->left; + vertices[0].y = 0; + vertices[1].x = rcPaint->right; + vertices[1].y = (rcWindow->bottom - rcWindow->top) / 2; + vertices[2].x = rcPaint->left; + vertices[2].y = (rcWindow->bottom - rcWindow->top) / 2; + vertices[3].x = rcPaint->right; + vertices[3].y = rcWindow->bottom - rcWindow->top; + GradientFill(dc, vertices, 4, (PVOID) gr, 2, GRADIENT_FILL_RECT_V); + DrawThemeBackground(theme, dc, + 1, 0, + rcWindow, rcPaint); +} + +// TODO check errors +void drawExplorerChevron(HTHEME theme, HDC dc, HWND rebar, WPARAM band, RECT *rcPaint) +{ + REBARBANDINFOW rbi; + RECT r; + int state; + + ZeroMemory(&rbi, sizeof (REBARBANDINFOW)); + rbi.cbSize = sizeof (REBARBANDINFOW); + rbi.fMask = RBBIM_CHILD | RBBIM_CHEVRONLOCATION | RBBIM_CHEVRONSTATE; + SendMessageW(rebar, RB_GETBANDINFOW, band, (LPARAM) (&rbi)); + if ((rbi.uChevronState & STATE_SYSTEM_INVISIBLE) != 0) + return; + state = 1; + // TODO check if this is correct + if ((rbi.uChevronState & STATE_SYSTEM_FOCUSED) != 0) + state = 4; + if ((rbi.uChevronState & STATE_SYSTEM_HOTTRACKED) != 0) + state = 2; + if ((rbi.uChevronState & STATE_SYSTEM_PRESSED) != 0) + state = 3; + r = rbi.rcChevronLocation; + // TODO commctrl.h says this should be correct for the chevron rect, but it's not? +// MapWindowRect(rbi.hwndChild, rebar, &r); + DrawThemeBackground(theme, dc, + 3, state, + &r, rcPaint); + DrawThemeBackground(theme, dc, + 7, 1, + &r, rcPaint); +} + +// TODO check errors +LRESULT customDrawExplorerRebar(NMCUSTOMDRAW *nm) +{ + HTHEME theme; + RECT r; + + if (nm->dwDrawStage != CDDS_PREPAINT) + return CDRF_DODEFAULT; + theme = OpenThemeData(nm->hdr.hwndFrom, L"CommandModule"); + GetClientRect(nm->hdr.hwndFrom, &r); + drawExplorerBackground(theme, nm->hdc, &r, &(nm->rc)); + // TODO dwItemSpec is often invalid?! + drawExplorerChevron(theme, nm->hdc, nm->hdr.hwndFrom, 0, &(nm->rc)); + CloseThemeData(theme); + return CDRF_SKIPDEFAULT; +} + +// TODO check errors +LRESULT customDrawExplorerToolbar(NMTBCUSTOMDRAW *nm) +{ + HWND toolbar, rebar; + WPARAM itemIndex; + TBBUTTON tbb; + HTHEME theme; + RECT r; + int part, state; + + toolbar = nm->nmcd.hdr.hwndFrom; + switch (nm->nmcd.dwDrawStage) { + case CDDS_PREPAINT: + theme = OpenThemeData(toolbar, L"CommandModule"); + rebar = GetParent(toolbar); + GetWindowRect(rebar, &r); + MapWindowRect(NULL, toolbar, &r); + drawExplorerBackground(theme, nm->nmcd.hdc, &r, &(nm->nmcd.rc)); + CloseThemeData(theme); + return CDRF_NOTIFYITEMDRAW; + case CDDS_ITEMPREPAINT: + itemIndex = (WPARAM) SendMessageW(toolbar, TB_COMMANDTOINDEX, nm->nmcd.dwItemSpec, 0); + ZeroMemory(&tbb, sizeof (TBBUTTON)); + SendMessageW(toolbar, TB_GETBUTTON, itemIndex, (LPARAM) (&tbb)); + theme = OpenThemeData(toolbar, L"CommandModule"); + part = 3; + if ((tbb.fsStyle & BTNS_DROPDOWN) != 0) + part = 4; + state = 1; + // TODO this doesn't work; both keyboard and mouse are listed as HOT + if ((nm->nmcd.uItemState & CDIS_FOCUS) != 0) + state = 4; + if ((nm->nmcd.uItemState & CDIS_HOT) != 0) + state = 2; + if ((nm->nmcd.uItemState & CDIS_SELECTED) != 0) + state = 3; + SendMessageW(toolbar, TB_GETITEMRECT, itemIndex, (LPARAM) (&r)); + DrawThemeBackground(theme, nm->nmcd.hdc, + 3, state, + &r, &(nm->nmcd.rc)); + CloseThemeData(theme); + return TBCDRF_NOBACKGROUND; + } + return CDRF_DODEFAULT; +} + +static struct { + const WCHAR *text; + LRESULT (*handleRebar)(NMCUSTOMDRAW *nm); + LRESULT (*handleToolbar)(NMTBCUSTOMDRAW *nm); +} drawmodes[] = { + { L"SetWindowTheme()", NULL, NULL }, + { L"Custom Draw Explorer", customDrawExplorerRebar, customDrawExplorerToolbar }, + { NULL, NULL }, +}; + +int drawmode = 0; + +static const WCHAR *rebarThemes[] = { + L"NULL", + L"", + L"AlternateRebar", + L"BrowserTabBar", + L"Communications", + L"Default", + L"ExplorerBar", + L"Help", + L"InactiveNavbar", + L"InactiveNavbarComposited", + L"ITBarBase", + L"MaxInactiveNavbar", + L"MaxInactiveNavbarComposited", + L"MaxNavbar", + L"MaxNavbarComposited", + L"Media", + L"Navbar", + L"NavbarBase", + L"NavbarComposited", + L"NavbarNonTopmost", + L"Rebar", + L"RebarStyle", + L"TaskBar", + L"TaskBarComposited", + NULL, +}; + +static WCHAR *toolbarThemes[] = { + L"NULL", + L"", + L"Alternate", + L"BB", + L"BBComposited", + L"Communications", + L"ExplorerMenu", + L"Go", + L"GoComposited", + L"InactiveBB", + L"InactiveBBComposited", + L"InactiveGo", + L"InactiveGoComposited", + L"InfoPaneToolbar", + L"LVPopup", + L"LVPopupBottom", + L"MaxBB", + L"MaxBBComposited", + L"MaxGo", + L"MaxGoComposited", + L"MaxInactiveBB", + L"MaxInactiveBBComposited", + L"MaxInactiveGo", + L"MaxInactiveGoComposited", + L"Media", + L"Placesbar", + L"SearchButton", + L"SearchButtonComposited", + L"StartMenu", + L"TaskBar", + L"TaskBarComposited", + L"TaskBarVert", + L"TaskBarVertComposited", + L"Toolbar", + L"ToolbarStyle", + L"TrayNotify", + L"TrayNotifyComposited", + NULL, +}; + +// TODO toolbarThemes + +void onWM_CREATE(HWND hwnd) +{ + TBBUTTON tbb[5]; + RECT btnrect; + DWORD tbbtnsize; + LONG tbsizex, tbsizey; + REBARBANDINFOW rbi; + HWND button; + LONG buttonx, buttony; + LONG combox, comboy; + int i; + + rebar = CreateWindowExW(0, + REBARCLASSNAMEW, NULL, + WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | CCS_NODIVIDER | CCS_TOP | RBS_FIXEDORDER, + 0, 0, 0, 0, + hwnd, (HMENU) 100, hInstance, NULL); + if (rebar == NULL) + diele("CreateWindowExW(REBARCLASSNAMEW)"); + + leftbar = CreateWindowExW(0, + TOOLBARCLASSNAMEW, NULL, + toolbarStyles | TBSTYLE_LIST | TBSTYLE_TRANSPARENT, + 0, 0, 0, 0, + hwnd, (HMENU) 101, hInstance, NULL); + if (leftbar == NULL) + diele("CreateWindowExW(TOOLBARCLASSNAMEW) leftbar"); + SendMessageW(leftbar, TB_BUTTONSTRUCTSIZE, sizeof (TBBUTTON), 0); + // I_IMAGENONE causes the button text to be left-aligned; don't use it +// if (SendMessageW(leftbar, TB_SETBITMAPSIZE, 0, 0) == FALSE) +// diele("TB_SETBITMAPSIZE"); + SendMessageW(leftbar, TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_DRAWDDARROWS | TBSTYLE_EX_HIDECLIPPEDBUTTONS | TBSTYLE_EX_MIXEDBUTTONS); + // TODO this *should* be DIPs... + // TODO figure out where the *2 is documented +// SendMessageW(leftbar, TB_SETPADDING, 0, MAKELPARAM(6 * 2, 5 * 2)); + ZeroMemory(tbb, 5 * sizeof (TBBUTTON)); + for (i = 0; i < 5; i++) { + tbb[i].iBitmap = 0; + tbb[i].idCommand = i; + tbb[i].fsState = TBSTATE_ENABLED; + tbb[i].fsStyle = BTNS_AUTOSIZE | BTNS_BUTTON | BTNS_NOPREFIX | BTNS_SHOWTEXT; + if (leftbarButtons[i].dropdown) + tbb[i].fsStyle |= BTNS_DROPDOWN | BTNS_WHOLEDROPDOWN; + tbb[i].iString = (INT_PTR) (leftbarButtons[i].text); + } + if (SendMessageW(leftbar, TB_ADDBUTTONSW, 5, (LPARAM) tbb) == FALSE) + diele("TB_ADDBUTTONSW"); + + tbsizex = 0; + for (i = 0; i < 5; i++) { + if (SendMessageW(leftbar, TB_GETITEMRECT, (WPARAM) i, (LPARAM) (&btnrect)) == FALSE) + diele("TB_GETITEMRECT"); + tbsizex += btnrect.right - btnrect.left; + } + tbbtnsize = (DWORD) SendMessageW(leftbar, TB_GETBUTTONSIZE, 0, 0); + tbsizey = HIWORD(tbbtnsize); + + ZeroMemory(&rbi, sizeof (REBARBANDINFOW)); + rbi.cbSize = sizeof (REBARBANDINFOW); + rbi.fMask = RBBIM_CHILD | RBBIM_STYLE | RBBIM_SIZE | RBBIM_CHILDSIZE | RBBIM_IDEALSIZE | RBBIM_ID; + rbi.fStyle = RBBS_NOGRIPPER | RBBS_CHILDEDGE | RBBS_USECHEVRON | RBBS_HIDETITLE; + rbi.hwndChild = leftbar; + rbi.cx = tbsizex; + rbi.cyChild = tbsizey; + rbi.cxMinChild = 0; + rbi.cyMinChild = tbsizey; + rbi.cxIdeal = tbsizex; + rbi.wID = 0; + if (SendMessageW(rebar, RB_INSERTBANDW, (WPARAM) (-1), (LPARAM) (&rbi)) == 0) + diele("RB_INSERTBANDW leftbar"); + + rightbar = CreateWindowExW(0, + TOOLBARCLASSNAMEW, NULL, + toolbarStyles | TBSTYLE_TRANSPARENT, + 0, 0, 0, 0, + hwnd, (HMENU) 102, hInstance, NULL); + if (rightbar == NULL) + diele("CreateWindowExW(TOOLBARCLASSNAMEW) rightbar"); + SendMessageW(rightbar, TB_BUTTONSTRUCTSIZE, sizeof (TBBUTTON), 0); + SendMessageW(rightbar, TB_SETIMAGELIST, 0, (LPARAM) rightList); + SendMessageW(rightbar, TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_DRAWDDARROWS); + // TODO this *should* be DIPs... + // TODO figure out where the *2 is documented +// SendMessageW(rightbar, TB_SETPADDING, 0, MAKELPARAM(6 * 2, 5 * 2)); + ZeroMemory(tbb, 5 * sizeof (TBBUTTON)); + tbb[0].iBitmap = 0; + tbb[0].idCommand = 0; + tbb[0].fsState = TBSTATE_ENABLED; + tbb[0].fsStyle = BTNS_AUTOSIZE | BTNS_BUTTON | BTNS_DROPDOWN; + tbb[1].iBitmap = 1; + tbb[1].idCommand = 1; + tbb[1].fsState = TBSTATE_ENABLED; + tbb[1].fsStyle = BTNS_AUTOSIZE | BTNS_BUTTON; + tbb[2].iBitmap = 2; + tbb[2].idCommand = 2; + tbb[2].fsState = TBSTATE_ENABLED; + tbb[2].fsStyle = BTNS_AUTOSIZE | BTNS_BUTTON; + if (SendMessageW(rightbar, TB_ADDBUTTONSW, 3, (LPARAM) tbb) == FALSE) + diele("TB_ADDBUTTONSW"); + // TODO check error + // TODO figure out why this works here but not elsewhere +// SendMessageW(rightbar, TB_SETBUTTONSIZE, 0, 0); + + tbsizex = 0; + for (i = 0; i < 3; i++) { + if (SendMessageW(rightbar, TB_GETITEMRECT, (WPARAM) i, (LPARAM) (&btnrect)) == FALSE) + diele("TB_GETITEMRECT"); + tbsizex += btnrect.right - btnrect.left; + } + tbbtnsize = (DWORD) SendMessageW(rightbar, TB_GETBUTTONSIZE, 0, 0); + tbsizey = HIWORD(tbbtnsize); + + ZeroMemory(&rbi, sizeof (REBARBANDINFOW)); + rbi.cbSize = sizeof (REBARBANDINFOW); + rbi.fMask = RBBIM_CHILD | RBBIM_STYLE | RBBIM_SIZE | RBBIM_CHILDSIZE | RBBIM_ID; + rbi.fStyle = RBBS_NOGRIPPER | RBBS_HIDETITLE; + rbi.hwndChild = rightbar; + rbi.cx = tbsizex; + rbi.cyChild = tbsizey; + rbi.cxMinChild = tbsizex; + rbi.cyMinChild = tbsizey; + rbi.wID = 1; + if (SendMessageW(rebar, RB_INSERTBANDW, (WPARAM) (-1), (LPARAM) (&rbi)) == 0) + diele("RB_INSERTBANDW rightbar"); + + buttonx = 10; + buttony = 40; +#define buttonwid 200 +#define buttonht 25 + for (i = 0; drawmodes[i].text != NULL; i++) { + button = CreateWindowExW(0, + L"BUTTON", drawmodes[i].text, + WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, + buttonx, buttony, + buttonwid, buttonht, + hwnd, (HMENU) (200 + i), hInstance, NULL); + if (button == NULL) + diele("CreateWIndowExW(L\"BUTTON\")"); + if (i == 0) { + combox = buttonx + buttonwid + 5; + comboy = buttony; + } + buttony += buttonht + 5; + } + button = CreateWindowExW(0, + L"BUTTON", L"Give Toolbar Focus", + WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, + buttonx, buttony, + buttonwid, buttonht, + hwnd, (HMENU) (200 + i), hInstance, NULL); + if (button == NULL) + diele("CreateWIndowExW(L\"BUTTON\")"); + rebarCombo = CreateWindowExW(WS_EX_CLIENTEDGE, + L"COMBOBOX", L"", + WS_CHILD | WS_VISIBLE | CBS_DROPDOWN, + combox, comboy, + buttonwid, buttonht, + hwnd, (HMENU) 300, hInstance, NULL); + if (rebarCombo == NULL) + diele("CreateWindowExW(L\"COMBOBOX\")"); + for (i = 0; rebarThemes[i] != NULL; i++) + // TODO check error + SendMessageW(rebarCombo, CB_ADDSTRING, 0, (LPARAM) (rebarThemes[i])); + comboy += buttonht + 5; + toolbarCombo = CreateWindowExW(WS_EX_CLIENTEDGE, + L"COMBOBOX", L"", + WS_CHILD | WS_VISIBLE | CBS_DROPDOWN, + combox, comboy, + buttonwid, buttonht, + hwnd, (HMENU) 301, hInstance, NULL); + if (toolbarCombo == NULL) + diele("CreateWindowExW(L\"COMBOBOX\")"); + for (i = 0; toolbarThemes[i] != NULL; i++) + // TODO check error + SendMessageW(toolbarCombo, CB_ADDSTRING, 0, (LPARAM) (toolbarThemes[i])); + comboy += buttonht + 5; + toolbarTransparentCheckbox = CreateWindowExW(0, + L"BUTTON", L"Transparent toolbar", + WS_CHILD | WS_VISIBLE | BS_CHECKBOX, + combox, comboy, + buttonwid, buttonht, + hwnd, (HMENU) 302, hInstance, NULL); + if (toolbarTransparentCheckbox == NULL) + diele("CreateWindowExW(L\"BUTTON\")"); + SendMessage(toolbarTransparentCheckbox, BM_SETCHECK, BST_CHECKED, 0); +} + +// TODO it seems like I shouldn't have to do this? +void repositionRebar(HWND hwnd) +{ + RECT win, rb; + + if (GetClientRect(hwnd, &win) == 0) + diele("GetClientRect()"); + if (GetWindowRect(rebar, &rb) == 0) + diele("GetWindowRect()"); + if (SetWindowPos(rebar, NULL, + 0, 0, win.right - win.left, rb.bottom - rb.top, + SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER) == 0) + diele("SetWindowPos()"); +} + +// TODO check errors +void handleEvents(HWND hwnd, WPARAM wParam) +{ + LRESULT n; + const WCHAR *selRebar = NULL, *selToolbar = NULL; + WCHAR *bufRebar = NULL, *bufToolbar = NULL; + BOOL changeRebar = FALSE, changeToolbar = FALSE; + BOOL invalidate = FALSE; + WPARAM check; + + switch (wParam) { + case MAKEWPARAM(300, CBN_SELCHANGE): + n = SendMessageW(rebarCombo, CB_GETCURSEL, 0, 0); + selRebar = rebarThemes[n]; + changeRebar = TRUE; + break; + case MAKEWPARAM(301, CBN_SELCHANGE): + n = SendMessageW(toolbarCombo, CB_GETCURSEL, 0, 0); + selToolbar = toolbarThemes[n]; + changeToolbar = TRUE; + break; + case MAKEWPARAM(200, BN_CLICKED): + drawmode = 0; + n = SendMessageW(rebarCombo, WM_GETTEXTLENGTH, 0, 0); + bufRebar = new WCHAR[n + 1]; + GetWindowTextW(rebarCombo, bufRebar, n + 1); + n = SendMessageW(toolbarCombo, WM_GETTEXTLENGTH, 0, 0); + bufToolbar = new WCHAR[n + 1]; + GetWindowTextW(toolbarCombo, bufToolbar, n + 1); + selRebar = bufRebar; + selToolbar = bufToolbar; + changeRebar = TRUE; + changeToolbar = TRUE; + break; + case MAKEWPARAM(201, BN_CLICKED): + drawmode = 1; + invalidate = TRUE; + break; + case MAKEWPARAM(302, BN_CLICKED): + ShowWindow(leftbar, SW_HIDE); + check = BST_CHECKED; + if (SendMessage(toolbarTransparentCheckbox, BM_GETCHECK, 0, 0) == BST_CHECKED) + check = BST_UNCHECKED; + SendMessage(toolbarTransparentCheckbox, BM_SETCHECK, check, 0); + if (check == BST_CHECKED) + SendMessageW(leftbar, TB_SETSTYLE, 0, toolbarStyles | TBSTYLE_LIST | TBSTYLE_TRANSPARENT); + else + SendMessageW(leftbar, TB_SETSTYLE, 0, toolbarStyles | TBSTYLE_LIST); + ShowWindow(leftbar, SW_SHOW); + break; + case MAKEWPARAM(202, BN_CLICKED): + SetFocus(leftbar); + break; + } + if (changeRebar) { + if (selRebar != NULL && wcscmp(selRebar, L"NULL") == 0) + selRebar = NULL; + if (selRebar != NULL && *selRebar != L'\0') + SendMessageW(rebar, RB_SETWINDOWTHEME, 0, (LPARAM) selRebar); + else + SetWindowTheme(rebar, selRebar, selRebar); + invalidate = TRUE; + } + if (changeToolbar) { + if (selToolbar != NULL && wcscmp(selToolbar, L"NULL") == 0) + selToolbar = NULL; + if (selToolbar != NULL && *selToolbar != L'\0') { + SendMessageW(leftbar, TB_SETWINDOWTHEME, 0, (LPARAM) selToolbar); + SendMessageW(rightbar, TB_SETWINDOWTHEME, 0, (LPARAM) selToolbar); + } else { + SetWindowTheme(leftbar, selToolbar, selToolbar); + SetWindowTheme(rightbar, selToolbar, selToolbar); + } + invalidate = TRUE; + } + if (invalidate) + InvalidateRect(hwnd, NULL, TRUE); + if (bufRebar != NULL) + delete[] bufRebar; + if (bufToolbar != NULL) + delete[] bufToolbar; +} + +LRESULT CALLBACK wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + NMHDR *nm = (NMHDR *) lParam; + + switch (uMsg) { + case WM_CREATE: + onWM_CREATE(hwnd); + break; + case WM_CLOSE: + PostQuitMessage(0); + break; + case WM_SIZE: + repositionRebar(hwnd); + break; + case WM_COMMAND: + handleEvents(hwnd, wParam); + break; + case WM_NOTIFY: + switch (nm->code) { + case NM_CUSTOMDRAW: + if (drawmode == 0) + break; + if (nm->hwndFrom == rebar) + return (*(drawmodes[drawmode].handleRebar))((NMCUSTOMDRAW *) nm); + else if (nm->hwndFrom == leftbar || nm->hwndFrom == rightbar) + return (*(drawmodes[drawmode].handleToolbar))((NMTBCUSTOMDRAW *) nm); + break; + } + break; + } + return DefWindowProcW(hwnd, uMsg, wParam, lParam); +} + +EXTERN_C IMAGE_DOS_HEADER __ImageBase; + +int main(void) +{ + STARTUPINFOW si; + int nCmdShow; + INITCOMMONCONTROLSEX icc; + HICON hDefaultIcon; + HCURSOR hDefaultCursor; + WNDCLASSW wc; + HWND mainwin; + MSG msg; + HRESULT hr; + + hInstance = (HINSTANCE) (&__ImageBase); + nCmdShow = SW_SHOWDEFAULT; + GetStartupInfoW(&si); + if ((si.dwFlags & STARTF_USESHOWWINDOW) != 0) + nCmdShow = si.wShowWindow; + + ZeroMemory(&icc, sizeof (INITCOMMONCONTROLSEX)); + icc.dwSize = sizeof (INITCOMMONCONTROLSEX); + icc.dwICC = ICC_STANDARD_CLASSES | ICC_BAR_CLASSES | ICC_COOL_CLASSES; + if (InitCommonControlsEx(&icc) == 0) + diele("InitCommonControlsEx()"); + + hDefaultIcon = LoadIconW(NULL, IDI_APPLICATION); + if (hDefaultIcon == NULL) + diele("LoadIconW(IDI_APPLICATION)"); + hDefaultCursor = LoadCursorW(NULL, IDC_ARROW); + if (hDefaultCursor == NULL) + diele("LoadCursorW(IDC_ARROW)"); + + hr = LoadIconMetric(NULL, IDI_SHIELD, LIM_SMALL, &shieldIcon); + if (hr != S_OK) + diehr("LoadIconMetric(IDI_SHIELD)", hr); + hr = LoadIconMetric(NULL, IDI_APPLICATION, LIM_SMALL, &applicationIcon); + if (hr != S_OK) + diehr("LoadIconMetric(IDI_APPLICATION)", hr); + hr = LoadIconMetric(NULL, IDI_QUESTION, LIM_SMALL, &helpIcon); + if (hr != S_OK) + diehr("LoadIconMetric(IDI_QUESTION)", hr); + rightList = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), + ILC_COLOR32, 0, 3); + if (rightList == NULL) + diele("ImageList_Create()"); + if (ImageList_ReplaceIcon(rightList, -1, shieldIcon) == -1) + diele("ImageList_ReplaceIcon(IDI_SHIELD)"); + if (ImageList_ReplaceIcon(rightList, -1, applicationIcon) == -1) + diele("ImageList_ReplaceIcon(IDI_APPLICATION)"); + if (ImageList_ReplaceIcon(rightList, -1, helpIcon) == -1) + diele("ImageList_ReplaceIcon(IDI_QUESTION)"); + + ZeroMemory(&wc, sizeof (WNDCLASSW)); + wc.lpszClassName = L"mainwin"; + wc.lpfnWndProc = wndproc; + wc.hInstance = hInstance; + wc.hIcon = hDefaultIcon; + wc.hCursor = hDefaultCursor; + wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1); + if (RegisterClassW(&wc) == 0) + diele("RegisterClassW()"); + + mainwin = CreateWindowExW(0, + L"mainwin", L"Main Window", + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + NULL, NULL, hInstance, NULL); + if (mainwin == NULL) + diele("CreateWindowExW(L\"mainwin\")"); + + ShowWindow(mainwin, nCmdShow); + if (UpdateWindow(mainwin) == 0) + diele("UpdateWindow()"); + + for (;;) { + int res; + + res = GetMessageW(&msg, NULL, 0, 0); + if (res < 0) + diele("GetMessageW()"); + if (res == 0) + break; + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + return 0; +} diff --git a/doc/names.md b/doc/names.md new file mode 100644 index 00000000..02036def --- /dev/null +++ b/doc/names.md @@ -0,0 +1,41 @@ +TODO clean this up + +TODO note that you -fvisibility=hidden means nothing in static libraries, hence this (confirmed on OS X) + +In general, all names that begin with "ui" and are followed by a capital letter and all names htat begin with "uipriv" and are followed by a capita lletter are reserved by libui. This applies even in C++, where name mangling may affect the actual names in the object file. + +# Reserved names; for users + +All reserved names in libui are defined by a prefix followed by any uppercase letter in ASCII. The bullet lists before list those prefixes. + +Global-scope identifiers of any form (variables, constant names, functions, structure names, union names, C++ class names, enum type names, enum value names, C++ namespace names, GObject class and interface struct names, and Objective-C class and protocol name identifiers) and macro names: + +- `ui` +- `uipriv` + +GObject and Objective-C class, interface, and protocol name strings, in the form they take in their respective runtime memory (e.g. when passed to `g_type_from_name()` and `NSClassFromString()`, respectively): + +- `uipriv` + +Objective-C method names: + +- `initWithUipriv` +- `initWithFrame:uipriv` (TODO probably worth removing) +- `uipriv` +- `isUipriv` (for compatibility with KVO and `@property` statements) +- `setUipriv` (for compatibility with KVO and `@property` statements) + +Objective-C ivar names: + +- `uipriv` +- `_uipriv` (for compatibility with KVO and `@property` statements) + +Objective-C property names: + +- `uipriv` + +TODO GObject macros (in libui's source code), properties, and signals + +# Developer notes + +TODO diff --git a/doc/slider b/doc/slider new file mode 100644 index 00000000..5a6ac040 --- /dev/null +++ b/doc/slider @@ -0,0 +1 @@ +if min >= max then they are swapped diff --git a/doc/spinbox b/doc/spinbox new file mode 100644 index 00000000..5a6ac040 --- /dev/null +++ b/doc/spinbox @@ -0,0 +1 @@ +if min >= max then they are swapped diff --git a/doc/static b/doc/static new file mode 100644 index 00000000..fe1a09ce --- /dev/null +++ b/doc/static @@ -0,0 +1,2 @@ +comctl6 +libui.res diff --git a/doc/windowmovesize b/doc/windowmovesize new file mode 100644 index 00000000..ec8bd966 --- /dev/null +++ b/doc/windowmovesize @@ -0,0 +1,3 @@ +you should never need to use these functions +they are provided only for the cases when ABSOLUTELY NECESSARY +the operating system may ignore your requests, for instance, if you are giving it invalid numbers or a size too small to fit; this is all system-defined diff --git a/doc/winstatic b/doc/winstatic new file mode 100644 index 00000000..5f163cfd --- /dev/null +++ b/doc/winstatic @@ -0,0 +1 @@ +libui uses resources starting at 29000 diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 00000000..b68f9706 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,62 @@ +# 3 june 2016 + +if(WIN32) + set(_EXAMPLE_RESOURCES_RC resources.rc) +endif() + +macro(_add_example _name) + _add_exec(${_name} ${ARGN}) + # because Microsoft's toolchain is dumb + if(MSVC) + set_property(TARGET ${_name} APPEND_STRING PROPERTY + LINK_FLAGS " /ENTRY:mainCRTStartup") + endif() +endmacro() + +_add_example(controlgallery + controlgallery/main.c + ${_EXAMPLE_RESOURCES_RC} +) + +_add_example(histogram + histogram/main.c + ${_EXAMPLE_RESOURCES_RC} +) + +_add_example(cpp-multithread + cpp-multithread/main.cpp + ${_EXAMPLE_RESOURCES_RC} +) +if(NOT WIN32) + target_link_libraries(cpp-multithread pthread) +endif() +if(APPLE) + # since we use a deployment target of 10.8, the non-C++11-compliant libstdc++ is chosen by default; we need C++11 + # see issue #302 for more details + target_compile_options(cpp-multithread PRIVATE --stdlib=libc++) + target_link_libraries(cpp-multithread --stdlib=libc++) +endif() + +_add_example(drawtext + drawtext/main.c + ${_EXAMPLE_RESOURCES_RC} +) + +_add_example(timer + timer/main.c + ${_EXAMPLE_RESOURCES_RC} +) + +_add_example(datetime + datetime/main.c + ${_EXAMPLE_RESOURCES_RC} +) + +add_custom_target(examples + DEPENDS + controlgallery + histogram + cpp-multithread + drawtext + timer + datetime) diff --git a/examples/controlgallery/darwin.png b/examples/controlgallery/darwin.png old mode 100755 new mode 100644 index 99d0e8b0..f61b54ba Binary files a/examples/controlgallery/darwin.png and b/examples/controlgallery/darwin.png differ diff --git a/examples/controlgallery/main.c b/examples/controlgallery/main.c index 711db1bd..c0d536c1 100644 --- a/examples/controlgallery/main.c +++ b/examples/controlgallery/main.c @@ -3,25 +3,331 @@ #include #include "../../ui.h" -// TODOs -// - rename variables in main() -// - make both columns the same size? - -static uiWindow *mainwin; - static int onClosing(uiWindow *w, void *data) { - uiControlDestroy(uiControl(mainwin)); uiQuit(); - return 0; + return 1; } -static int shouldQuit(void *data) +static int onShouldQuit(void *data) { + uiWindow *mainwin = uiWindow(data); + uiControlDestroy(uiControl(mainwin)); return 1; } +static uiControl *makeBasicControlsPage(void) +{ + uiBox *vbox; + uiBox *hbox; + uiGroup *group; + uiForm *entryForm; + + vbox = uiNewVerticalBox(); + uiBoxSetPadded(vbox, 1); + + hbox = uiNewHorizontalBox(); + uiBoxSetPadded(hbox, 1); + uiBoxAppend(vbox, uiControl(hbox), 0); + + uiBoxAppend(hbox, + uiControl(uiNewButton("Button")), + 0); + uiBoxAppend(hbox, + uiControl(uiNewCheckbox("Checkbox")), + 0); + + uiBoxAppend(vbox, + uiControl(uiNewLabel("This is a label. Right now, labels can only span one line.")), + 0); + + uiBoxAppend(vbox, + uiControl(uiNewHorizontalSeparator()), + 0); + + group = uiNewGroup("Entries"); + uiGroupSetMargined(group, 1); + uiBoxAppend(vbox, uiControl(group), 1); + + entryForm = uiNewForm(); + uiFormSetPadded(entryForm, 1); + uiGroupSetChild(group, uiControl(entryForm)); + + uiFormAppend(entryForm, + "Entry", + uiControl(uiNewEntry()), + 0); + uiFormAppend(entryForm, + "Password Entry", + uiControl(uiNewPasswordEntry()), + 0); + uiFormAppend(entryForm, + "Search Entry", + uiControl(uiNewSearchEntry()), + 0); + uiFormAppend(entryForm, + "Multiline Entry", + uiControl(uiNewMultilineEntry()), + 1); + uiFormAppend(entryForm, + "Multiline Entry No Wrap", + uiControl(uiNewNonWrappingMultilineEntry()), + 1); + + return uiControl(vbox); +} + +// TODO make these not global +static uiSpinbox *spinbox; +static uiSlider *slider; +static uiProgressBar *pbar; + +static void onSpinboxChanged(uiSpinbox *s, void *data) +{ + uiSliderSetValue(slider, uiSpinboxValue(s)); + uiProgressBarSetValue(pbar, uiSpinboxValue(s)); +} + +static void onSliderChanged(uiSlider *s, void *data) +{ + uiSpinboxSetValue(spinbox, uiSliderValue(s)); + uiProgressBarSetValue(pbar, uiSliderValue(s)); +} + +static uiControl *makeNumbersPage() +{ + uiBox *hbox; + uiGroup *group; + uiBox *vbox; + uiProgressBar *ip; + uiCombobox *cbox; + uiEditableCombobox *ecbox; + uiRadioButtons *rb; + + hbox = uiNewHorizontalBox(); + uiBoxSetPadded(hbox, 1); + + group = uiNewGroup("Numbers"); + uiGroupSetMargined(group, 1); + uiBoxAppend(hbox, uiControl(group), 1); + + vbox = uiNewVerticalBox(); + uiBoxSetPadded(vbox, 1); + uiGroupSetChild(group, uiControl(vbox)); + + spinbox = uiNewSpinbox(0, 100); + slider = uiNewSlider(0, 100); + pbar = uiNewProgressBar(); + uiSpinboxOnChanged(spinbox, onSpinboxChanged, NULL); + uiSliderOnChanged(slider, onSliderChanged, NULL); + uiBoxAppend(vbox, uiControl(spinbox), 0); + uiBoxAppend(vbox, uiControl(slider), 0); + uiBoxAppend(vbox, uiControl(pbar), 0); + + ip = uiNewProgressBar(); + uiProgressBarSetValue(ip, -1); + uiBoxAppend(vbox, uiControl(ip), 0); + + group = uiNewGroup("Lists"); + uiGroupSetMargined(group, 1); + uiBoxAppend(hbox, uiControl(group), 1); + + vbox = uiNewVerticalBox(); + uiBoxSetPadded(vbox, 1); + uiGroupSetChild(group, uiControl(vbox)); + + cbox = uiNewCombobox(); + uiComboboxAppend(cbox, "Combobox Item 1"); + uiComboboxAppend(cbox, "Combobox Item 2"); + uiComboboxAppend(cbox, "Combobox Item 3"); + uiBoxAppend(vbox, uiControl(cbox), 0); + + ecbox = uiNewEditableCombobox(); + uiEditableComboboxAppend(ecbox, "Editable Item 1"); + uiEditableComboboxAppend(ecbox, "Editable Item 2"); + uiEditableComboboxAppend(ecbox, "Editable Item 3"); + uiBoxAppend(vbox, uiControl(ecbox), 0); + + rb = uiNewRadioButtons(); + uiRadioButtonsAppend(rb, "Radio Button 1"); + uiRadioButtonsAppend(rb, "Radio Button 2"); + uiRadioButtonsAppend(rb, "Radio Button 3"); + uiBoxAppend(vbox, uiControl(rb), 0); + + return uiControl(hbox); +} + +// TODO make this not global +static uiWindow *mainwin; + +static void onOpenFileClicked(uiButton *b, void *data) +{ + uiEntry *entry = uiEntry(data); + char *filename; + + filename = uiOpenFile(mainwin); + if (filename == NULL) { + uiEntrySetText(entry, "(cancelled)"); + return; + } + uiEntrySetText(entry, filename); + uiFreeText(filename); +} + +static void onSaveFileClicked(uiButton *b, void *data) +{ + uiEntry *entry = uiEntry(data); + char *filename; + + filename = uiSaveFile(mainwin); + if (filename == NULL) { + uiEntrySetText(entry, "(cancelled)"); + return; + } + uiEntrySetText(entry, filename); + uiFreeText(filename); +} + +static void onMsgBoxClicked(uiButton *b, void *data) +{ + uiMsgBox(mainwin, + "This is a normal message box.", + "More detailed information can be shown here."); +} + +static void onMsgBoxErrorClicked(uiButton *b, void *data) +{ + uiMsgBoxError(mainwin, + "This message box describes an error.", + "More detailed information can be shown here."); +} + +static uiControl *makeDataChoosersPage(void) +{ + uiBox *hbox; + uiBox *vbox; + uiGrid *grid; + uiButton *button; + uiEntry *entry; + uiGrid *msggrid; + + hbox = uiNewHorizontalBox(); + uiBoxSetPadded(hbox, 1); + + vbox = uiNewVerticalBox(); + uiBoxSetPadded(vbox, 1); + uiBoxAppend(hbox, uiControl(vbox), 0); + + uiBoxAppend(vbox, + uiControl(uiNewDatePicker()), + 0); + uiBoxAppend(vbox, + uiControl(uiNewTimePicker()), + 0); + uiBoxAppend(vbox, + uiControl(uiNewDateTimePicker()), + 0); + + uiBoxAppend(vbox, + uiControl(uiNewFontButton()), + 0); + uiBoxAppend(vbox, + uiControl(uiNewColorButton()), + 0); + + uiBoxAppend(hbox, + uiControl(uiNewVerticalSeparator()), + 0); + + vbox = uiNewVerticalBox(); + uiBoxSetPadded(vbox, 1); + uiBoxAppend(hbox, uiControl(vbox), 1); + + grid = uiNewGrid(); + uiGridSetPadded(grid, 1); + uiBoxAppend(vbox, uiControl(grid), 0); + + button = uiNewButton("Open File"); + entry = uiNewEntry(); + uiEntrySetReadOnly(entry, 1); + uiButtonOnClicked(button, onOpenFileClicked, entry); + uiGridAppend(grid, uiControl(button), + 0, 0, 1, 1, + 0, uiAlignFill, 0, uiAlignFill); + uiGridAppend(grid, uiControl(entry), + 1, 0, 1, 1, + 1, uiAlignFill, 0, uiAlignFill); + + button = uiNewButton("Save File"); + entry = uiNewEntry(); + uiEntrySetReadOnly(entry, 1); + uiButtonOnClicked(button, onSaveFileClicked, entry); + uiGridAppend(grid, uiControl(button), + 0, 1, 1, 1, + 0, uiAlignFill, 0, uiAlignFill); + uiGridAppend(grid, uiControl(entry), + 1, 1, 1, 1, + 1, uiAlignFill, 0, uiAlignFill); + + msggrid = uiNewGrid(); + uiGridSetPadded(msggrid, 1); + uiGridAppend(grid, uiControl(msggrid), + 0, 2, 2, 1, + 0, uiAlignCenter, 0, uiAlignStart); + + button = uiNewButton("Message Box"); + uiButtonOnClicked(button, onMsgBoxClicked, NULL); + uiGridAppend(msggrid, uiControl(button), + 0, 0, 1, 1, + 0, uiAlignFill, 0, uiAlignFill); + button = uiNewButton("Error Box"); + uiButtonOnClicked(button, onMsgBoxErrorClicked, NULL); + uiGridAppend(msggrid, uiControl(button), + 1, 0, 1, 1, + 0, uiAlignFill, 0, uiAlignFill); + + return uiControl(hbox); +} + +int main(void) +{ + uiInitOptions options; + const char *err; + uiTab *tab; + + memset(&options, 0, sizeof (uiInitOptions)); + err = uiInit(&options); + if (err != NULL) { + fprintf(stderr, "error initializing libui: %s", err); + uiFreeInitError(err); + return 1; + } + + mainwin = uiNewWindow("libui Control Gallery", 640, 480, 1); + uiWindowOnClosing(mainwin, onClosing, NULL); + uiOnShouldQuit(onShouldQuit, mainwin); + + tab = uiNewTab(); + uiWindowSetChild(mainwin, uiControl(tab)); + uiWindowSetMargined(mainwin, 1); + + uiTabAppend(tab, "Basic Controls", makeBasicControlsPage()); + uiTabSetMargined(tab, 0, 1); + + uiTabAppend(tab, "Numbers and Lists", makeNumbersPage()); + uiTabSetMargined(tab, 1, 1); + + uiTabAppend(tab, "Data Choosers", makeDataChoosersPage()); + uiTabSetMargined(tab, 2, 1); + + uiControlShow(uiControl(mainwin)); + uiMain(); + return 0; +} + +#if 0 + static void openClicked(uiMenuItem *item, uiWindow *w, void *data) { char *filename; @@ -52,7 +358,7 @@ static uiSpinbox *spinbox; static uiSlider *slider; static uiProgressBar *progressbar; -static void update(intmax_t value) +static void update(int value) { uiSpinboxSetValue(spinbox, value); uiSliderSetValue(slider, value); @@ -82,6 +388,7 @@ int main(void) uiBox *inner2; uiEntry *entry; uiCombobox *cbox; + uiEditableCombobox *ecbox; uiRadioButtons *rb; uiTab *tab; @@ -165,6 +472,10 @@ int main(void) uiControl(uiNewFontButton()), 0); + uiBoxAppend(inner, + uiControl(uiNewColorButton()), + 0); + inner2 = uiNewVerticalBox(); uiBoxSetPadded(inner2, 1); uiBoxAppend(hbox, uiControl(inner2), 1); @@ -202,11 +513,11 @@ int main(void) uiComboboxAppend(cbox, "Combobox Item 3"); uiBoxAppend(inner, uiControl(cbox), 0); - cbox = uiNewEditableCombobox(); - uiComboboxAppend(cbox, "Editable Item 1"); - uiComboboxAppend(cbox, "Editable Item 2"); - uiComboboxAppend(cbox, "Editable Item 3"); - uiBoxAppend(inner, uiControl(cbox), 0); + ecbox = uiNewEditableCombobox(); + uiEditableComboboxAppend(ecbox, "Editable Item 1"); + uiEditableComboboxAppend(ecbox, "Editable Item 2"); + uiEditableComboboxAppend(ecbox, "Editable Item 3"); + uiBoxAppend(inner, uiControl(ecbox), 0); rb = uiNewRadioButtons(); uiRadioButtonsAppend(rb, "Radio Button 1"); @@ -225,3 +536,5 @@ int main(void) uiUninit(); return 0; } + +#endif diff --git a/examples/controlgallery/unix.png b/examples/controlgallery/unix.png index 2aa5ea0a..0c58d090 100644 Binary files a/examples/controlgallery/unix.png and b/examples/controlgallery/unix.png differ diff --git a/examples/controlgallery/windows.png b/examples/controlgallery/windows.png index 5917ca0f..4be832f7 100755 Binary files a/examples/controlgallery/windows.png and b/examples/controlgallery/windows.png differ diff --git a/examples/datetime/main.c b/examples/datetime/main.c new file mode 100644 index 00000000..5f10eb0f --- /dev/null +++ b/examples/datetime/main.c @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include "../../ui.h" + +uiDateTimePicker *dtboth, *dtdate, *dttime; + +const char *timeFormat(uiDateTimePicker *d) +{ + const char *fmt; + + if (d == dtboth) + fmt = "%c"; + else if (d == dtdate) + fmt = "%x"; + else if (d == dttime) + fmt = "%X"; + else + fmt = ""; + return fmt; +} + +void onChanged(uiDateTimePicker *d, void *data) +{ + struct tm time; + char buf[64]; + + uiDateTimePickerTime(d, &time); + strftime(buf, sizeof (buf), timeFormat(d), &time); + uiLabelSetText(uiLabel(data), buf); +} + +void onClicked(uiButton *b, void *data) +{ + intptr_t now; + time_t t; + struct tm tmbuf; + + now = (intptr_t) data; + t = 0; + if (now) + t = time(NULL); + tmbuf = *localtime(&t); + + if (now) { + uiDateTimePickerSetTime(dtdate, &tmbuf); + uiDateTimePickerSetTime(dttime, &tmbuf); + } else + uiDateTimePickerSetTime(dtboth, &tmbuf); +} + +int onClosing(uiWindow *w, void *data) +{ + uiQuit(); + return 1; +} + +int main(void) +{ + uiInitOptions o; + const char *err; + uiWindow *w; + uiGrid *g; + uiLabel *l; + uiButton *b; + + memset(&o, 0, sizeof (uiInitOptions)); + err = uiInit(&o); + if (err != NULL) { + fprintf(stderr, "error initializing ui: %s\n", err); + uiFreeInitError(err); + return 1; + } + + w = uiNewWindow("Date / Time", 320, 240, 0); + uiWindowSetMargined(w, 1); + + g = uiNewGrid(); + uiGridSetPadded(g, 1); + uiWindowSetChild(w, uiControl(g)); + + dtboth = uiNewDateTimePicker(); + dtdate = uiNewDatePicker(); + dttime = uiNewTimePicker(); + + uiGridAppend(g, uiControl(dtboth), + 0, 0, 2, 1, + 1, uiAlignFill, 0, uiAlignFill); + uiGridAppend(g, uiControl(dtdate), + 0, 1, 1, 1, + 1, uiAlignFill, 0, uiAlignFill); + uiGridAppend(g, uiControl(dttime), + 1, 1, 1, 1, + 1, uiAlignFill, 0, uiAlignFill); + + l = uiNewLabel(""); + uiGridAppend(g, uiControl(l), + 0, 2, 2, 1, + 1, uiAlignCenter, 0, uiAlignFill); + uiDateTimePickerOnChanged(dtboth, onChanged, l); + l = uiNewLabel(""); + uiGridAppend(g, uiControl(l), + 0, 3, 1, 1, + 1, uiAlignCenter, 0, uiAlignFill); + uiDateTimePickerOnChanged(dtdate, onChanged, l); + l = uiNewLabel(""); + uiGridAppend(g, uiControl(l), + 1, 3, 1, 1, + 1, uiAlignCenter, 0, uiAlignFill); + uiDateTimePickerOnChanged(dttime, onChanged, l); + + b = uiNewButton("Now"); + uiButtonOnClicked(b, onClicked, (void *) 1); + uiGridAppend(g, uiControl(b), + 0, 4, 1, 1, + 1, uiAlignFill, 1, uiAlignEnd); + b = uiNewButton("Unix epoch"); + uiButtonOnClicked(b, onClicked, (void *) 0); + uiGridAppend(g, uiControl(b), + 1, 4, 1, 1, + 1, uiAlignFill, 1, uiAlignEnd); + + uiWindowOnClosing(w, onClosing, NULL); + uiControlShow(uiControl(w)); + uiMain(); + return 0; +} diff --git a/examples/drawtext/main.c b/examples/drawtext/main.c new file mode 100644 index 00000000..d94d2572 --- /dev/null +++ b/examples/drawtext/main.c @@ -0,0 +1,219 @@ +// 10 march 2018 +#include +#include +#include "../../ui.h" + +uiWindow *mainwin; +uiArea *area; +uiAreaHandler handler; +uiFontButton *fontButton; +uiCombobox *alignment; + +uiAttributedString *attrstr; + +static void appendWithAttribute(const char *what, uiAttribute *attr, uiAttribute *attr2) +{ + size_t start, end; + + start = uiAttributedStringLen(attrstr); + end = start + strlen(what); + uiAttributedStringAppendUnattributed(attrstr, what); + uiAttributedStringSetAttribute(attrstr, attr, start, end); + if (attr2 != NULL) + uiAttributedStringSetAttribute(attrstr, attr2, start, end); +} + +static void makeAttributedString(void) +{ + uiAttribute *attr, *attr2; + uiOpenTypeFeatures *otf; + + attrstr = uiNewAttributedString( + "Drawing strings with libui is done with the uiAttributedString and uiDrawTextLayout objects.\n" + "uiAttributedString lets you have a variety of attributes: "); + + attr = uiNewFamilyAttribute("Courier New"); + appendWithAttribute("font family", attr, NULL); + uiAttributedStringAppendUnattributed(attrstr, ", "); + + attr = uiNewSizeAttribute(18); + appendWithAttribute("font size", attr, NULL); + uiAttributedStringAppendUnattributed(attrstr, ", "); + + attr = uiNewWeightAttribute(uiTextWeightBold); + appendWithAttribute("font weight", attr, NULL); + uiAttributedStringAppendUnattributed(attrstr, ", "); + + attr = uiNewItalicAttribute(uiTextItalicItalic); + appendWithAttribute("font italicness", attr, NULL); + uiAttributedStringAppendUnattributed(attrstr, ", "); + + attr = uiNewStretchAttribute(uiTextStretchCondensed); + appendWithAttribute("font stretch", attr, NULL); + uiAttributedStringAppendUnattributed(attrstr, ", "); + + attr = uiNewColorAttribute(0.75, 0.25, 0.5, 0.75); + appendWithAttribute("text color", attr, NULL); + uiAttributedStringAppendUnattributed(attrstr, ", "); + + attr = uiNewBackgroundAttribute(0.5, 0.5, 0.25, 0.5); + appendWithAttribute("text background color", attr, NULL); + uiAttributedStringAppendUnattributed(attrstr, ", "); + + + attr = uiNewUnderlineAttribute(uiUnderlineSingle); + appendWithAttribute("underline style", attr, NULL); + uiAttributedStringAppendUnattributed(attrstr, ", "); + + uiAttributedStringAppendUnattributed(attrstr, "and "); + attr = uiNewUnderlineAttribute(uiUnderlineDouble); + attr2 = uiNewUnderlineColorAttribute(uiUnderlineColorCustom, 1.0, 0.0, 0.5, 1.0); + appendWithAttribute("underline color", attr, attr2); + uiAttributedStringAppendUnattributed(attrstr, ". "); + + uiAttributedStringAppendUnattributed(attrstr, "Furthermore, there are attributes allowing for "); + attr = uiNewUnderlineAttribute(uiUnderlineSuggestion); + attr2 = uiNewUnderlineColorAttribute(uiUnderlineColorSpelling, 0, 0, 0, 0); + appendWithAttribute("special underlines for indicating spelling errors", attr, attr2); + uiAttributedStringAppendUnattributed(attrstr, " (and other types of errors) "); + + uiAttributedStringAppendUnattributed(attrstr, "and control over OpenType features such as ligatures (for instance, "); + otf = uiNewOpenTypeFeatures(); + uiOpenTypeFeaturesAdd(otf, 'l', 'i', 'g', 'a', 0); + attr = uiNewFeaturesAttribute(otf); + appendWithAttribute("afford", attr, NULL); + uiAttributedStringAppendUnattributed(attrstr, " vs. "); + uiOpenTypeFeaturesAdd(otf, 'l', 'i', 'g', 'a', 1); + attr = uiNewFeaturesAttribute(otf); + appendWithAttribute("afford", attr, NULL); + uiFreeOpenTypeFeatures(otf); + uiAttributedStringAppendUnattributed(attrstr, ").\n"); + + uiAttributedStringAppendUnattributed(attrstr, "Use the controls opposite to the text to control properties of the text."); +} + +static void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *p) +{ + uiDrawTextLayout *textLayout; + uiFontDescriptor defaultFont; + uiDrawTextLayoutParams params; + + params.String = attrstr; + uiFontButtonFont(fontButton, &defaultFont); + params.DefaultFont = &defaultFont; + params.Width = p->AreaWidth; + params.Align = (uiDrawTextAlign) uiComboboxSelected(alignment); + textLayout = uiDrawNewTextLayout(¶ms); + uiDrawText(p->Context, textLayout, 0, 0); + uiDrawFreeTextLayout(textLayout); + uiFreeFontButtonFont(&defaultFont); +} + +static void handlerMouseEvent(uiAreaHandler *a, uiArea *area, uiAreaMouseEvent *e) +{ + // do nothing +} + +static void handlerMouseCrossed(uiAreaHandler *ah, uiArea *a, int left) +{ + // do nothing +} + +static void handlerDragBroken(uiAreaHandler *ah, uiArea *a) +{ + // do nothing +} + +static int handlerKeyEvent(uiAreaHandler *ah, uiArea *a, uiAreaKeyEvent *e) +{ + // reject all keys + return 0; +} + +static void onFontChanged(uiFontButton *b, void *data) +{ + uiAreaQueueRedrawAll(area); +} + +static void onComboboxSelected(uiCombobox *b, void *data) +{ + uiAreaQueueRedrawAll(area); +} + +static int onClosing(uiWindow *w, void *data) +{ + uiControlDestroy(uiControl(mainwin)); + uiQuit(); + return 0; +} + +static int shouldQuit(void *data) +{ + uiControlDestroy(uiControl(mainwin)); + return 1; +} + +int main(void) +{ + uiInitOptions o; + const char *err; + uiBox *hbox, *vbox; + uiForm *form; + + handler.Draw = handlerDraw; + handler.MouseEvent = handlerMouseEvent; + handler.MouseCrossed = handlerMouseCrossed; + handler.DragBroken = handlerDragBroken; + handler.KeyEvent = handlerKeyEvent; + + memset(&o, 0, sizeof (uiInitOptions)); + err = uiInit(&o); + if (err != NULL) { + fprintf(stderr, "error initializing ui: %s\n", err); + uiFreeInitError(err); + return 1; + } + + uiOnShouldQuit(shouldQuit, NULL); + + makeAttributedString(); + + mainwin = uiNewWindow("libui Text-Drawing Example", 640, 480, 1); + uiWindowSetMargined(mainwin, 1); + uiWindowOnClosing(mainwin, onClosing, NULL); + + hbox = uiNewHorizontalBox(); + uiBoxSetPadded(hbox, 1); + uiWindowSetChild(mainwin, uiControl(hbox)); + + vbox = uiNewVerticalBox(); + uiBoxSetPadded(vbox, 1); + uiBoxAppend(hbox, uiControl(vbox), 0); + + fontButton = uiNewFontButton(); + uiFontButtonOnChanged(fontButton, onFontChanged, NULL); + uiBoxAppend(vbox, uiControl(fontButton), 0); + + form = uiNewForm(); + uiFormSetPadded(form, 1); + // TODO on OS X if this is set to 1 then the window can't resize; does the form not have the concept of stretchy trailing space? + uiBoxAppend(vbox, uiControl(form), 0); + + alignment = uiNewCombobox(); + // note that the items match with the values of the uiDrawTextAlign values + uiComboboxAppend(alignment, "Left"); + uiComboboxAppend(alignment, "Center"); + uiComboboxAppend(alignment, "Right"); + uiComboboxSetSelected(alignment, 0); // start with left alignment + uiComboboxOnSelected(alignment, onComboboxSelected, NULL); + uiFormAppend(form, "Alignment", uiControl(alignment), 0); + + area = uiNewArea(&handler); + uiBoxAppend(hbox, uiControl(area), 1); + + uiControlShow(uiControl(mainwin)); + uiMain(); + uiFreeAttributedString(attrstr); + uiUninit(); + return 0; +} diff --git a/examples/example.static.manifest b/examples/example.static.manifest new file mode 100644 index 00000000..d8e83a83 --- /dev/null +++ b/examples/example.static.manifest @@ -0,0 +1,32 @@ + + + +Your application description here. + + + + + + + + + + + + + + + + diff --git a/examples/histogram/main.c b/examples/histogram/main.c index 669a9033..f2b0e793 100644 --- a/examples/histogram/main.c +++ b/examples/histogram/main.c @@ -9,6 +9,7 @@ uiWindow *mainwin; uiArea *histogram; uiAreaHandler handler; uiSpinbox *datapoints[10]; +uiColorButton *colorButton; int currentPoint = -1; // some metrics @@ -94,6 +95,7 @@ static void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *p) uiDrawStrokeParams sp; uiDrawMatrix m; double graphWidth, graphHeight; + double graphR, graphG, graphB, graphA; // fill the area with white setSolidBrush(&brush, colorWhite, 1.0); @@ -134,15 +136,23 @@ static void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *p) uiDrawMatrixTranslate(&m, xoffLeft, yoffTop); uiDrawTransform(p->Context, &m); + // now get the color for the graph itself and set up the brush + uiColorButtonColor(colorButton, &graphR, &graphG, &graphB, &graphA); + brush.Type = uiDrawBrushTypeSolid; + brush.R = graphR; + brush.G = graphG; + brush.B = graphB; + // we set brush->A below to different values for the fill and stroke + // now create the fill for the graph below the graph line path = constructGraph(graphWidth, graphHeight, 1); - setSolidBrush(&brush, colorDodgerBlue, 0.5); + brush.A = graphA / 2; uiDrawFill(p->Context, path, &brush); uiDrawFreePath(path); // now draw the histogram line path = constructGraph(graphWidth, graphHeight, 0); - setSolidBrush(&brush, colorDodgerBlue, 1.0); + brush.A = graphA; uiDrawStroke(p->Context, path, &brush, &sp); uiDrawFreePath(path); @@ -216,6 +226,11 @@ static void onDatapointChanged(uiSpinbox *s, void *data) uiAreaQueueRedrawAll(histogram); } +static void onColorChanged(uiColorButton *b, void *data) +{ + uiAreaQueueRedrawAll(histogram); +} + static int onClosing(uiWindow *w, void *data) { uiControlDestroy(uiControl(mainwin)); @@ -235,6 +250,7 @@ int main(void) const char *err; uiBox *hbox, *vbox; int i; + uiDrawBrush brush; handler.Draw = handlerDraw; handler.MouseEvent = handlerMouseEvent; @@ -272,6 +288,17 @@ int main(void) uiBoxAppend(vbox, uiControl(datapoints[i]), 0); } + colorButton = uiNewColorButton(); + // TODO inline these + setSolidBrush(&brush, colorDodgerBlue, 1.0); + uiColorButtonSetColor(colorButton, + brush.R, + brush.G, + brush.B, + brush.A); + uiColorButtonOnChanged(colorButton, onColorChanged, NULL); + uiBoxAppend(vbox, uiControl(colorButton), 0); + histogram = uiNewArea(&handler); uiBoxAppend(hbox, uiControl(histogram), 1); diff --git a/examples/resources.rc b/examples/resources.rc index b55e24ec..49f486c1 100644 --- a/examples/resources.rc +++ b/examples/resources.rc @@ -6,4 +6,8 @@ // this is the Common Controls 6 manifest // TODO set up the string values here // 1 is the value of CREATEPROCESS_MANIFEST_RESOURCE_ID and 24 is the value of RT_MANIFEST; we use it directly to avoid needing to share winapi.h with the tests and examples +#ifndef _UI_STATIC 1 24 "example.manifest" +#else +1 24 "example.static.manifest" +#endif diff --git a/examples/timer/main.c b/examples/timer/main.c new file mode 100644 index 00000000..d1b80b97 --- /dev/null +++ b/examples/timer/main.c @@ -0,0 +1,64 @@ +#include +#include +#include +#include "../../ui.h" + +uiMultilineEntry *e; + +int sayTime(void *data) +{ + time_t t; + char *s; + + t = time(NULL); + s = ctime(&t); + + uiMultilineEntryAppend(e, s); + return 1; +} + +int onClosing(uiWindow *w, void *data) +{ + uiQuit(); + return 1; +} + +void saySomething(uiButton *b, void *data) +{ + uiMultilineEntryAppend(e, "Saying something\n"); +} + +int main(void) +{ + uiInitOptions o; + uiWindow *w; + uiBox *b; + uiButton *btn; + + memset(&o, 0, sizeof (uiInitOptions)); + if (uiInit(&o) != NULL) + abort(); + + w = uiNewWindow("Hello", 320, 240, 0); + uiWindowSetMargined(w, 1); + + b = uiNewVerticalBox(); + uiBoxSetPadded(b, 1); + uiWindowSetChild(w, uiControl(b)); + + e = uiNewMultilineEntry(); + uiMultilineEntrySetReadOnly(e, 1); + + btn = uiNewButton("Say Something"); + uiButtonOnClicked(btn, saySomething, NULL); + uiBoxAppend(b, uiControl(btn), 0); + + uiBoxAppend(b, uiControl(e), 1); + + uiTimer(1000, sayTime, NULL); + + uiWindowOnClosing(w, onClosing, NULL); + uiControlShow(uiControl(w)); + uiMain(); + return 0; +} diff --git a/stats.osxdrawtext b/stats.osxdrawtext new file mode 100644 index 00000000..15c9c6c5 --- /dev/null +++ b/stats.osxdrawtext @@ -0,0 +1,43 @@ +diff --git a/darwin/drawtext.m b/darwin/drawtext.m +index a84b68b..c95bbde 100644 +--- a/darwin/drawtext.m ++++ b/darwin/drawtext.m +@@ -108,7 +108,7 @@ static CFAttributedStringRef attrstrToCoreFoundation(uiAttributedString *s, uiDr + boundsNoLeading = CTLineGetBoundsWithOptions(line, kCTLineBoundsExcludeTypographicLeading); + + // this is equivalent to boundsNoLeading.size.height + boundsNoLeading.origin.y (manually verified) +- ascent = bounds.size.height + bounds.origin.y; ++if(i!=5) ascent = bounds.size.height + bounds.origin.y; + descent = -boundsNoLeading.origin.y; + // TODO does this preserve leading sign? + leading = -bounds.origin.y - descent; +@@ -119,11 +119,20 @@ static CFAttributedStringRef attrstrToCoreFoundation(uiAttributedString *s, uiDr + if (leading > 0) + leading = floor(leading + 0.5); + ++NSLog(@"line %d", (int)i); ++NSLog(@"ypos %g", ypos); ++if (i>0) { ++NSLog(@"expected Y: %g", metrics[i - 1].Y - metrics[i - 1].Height); ++} ++ + metrics[i].X = origins[i].x; + metrics[i].Y = origins[i].y - descent - leading; + metrics[i].Width = bounds.size.width; + metrics[i].Height = ascent + descent + leading; + ++NSLog(@"o %g a %g d %g l %g", origins[i].y, ascent, descent, leading); ++NSLog(@"actual Y: %g height: %g", metrics[i].Y, metrics[i].Height); ++ + metrics[i].BaselineY = origins[i].y; + metrics[i].Ascent = ascent; + metrics[i].Descent = descent; +@@ -148,7 +157,7 @@ static CFAttributedStringRef attrstrToCoreFoundation(uiAttributedString *s, uiDr + metrics[i].BaselineY = size.height - metrics[i].BaselineY; + // TODO also adjust by metrics[i].Height? + } +- ++NSLog(@"==="); + uiFree(origins); + return metrics; + } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 00000000..621b43e9 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,40 @@ +# 3 june 2016 + +if(WIN32) + set(_TEST_RESOURCES_RC resources.rc) +endif() + +_add_exec(tester + drawtests.c + images.c + main.c + menus.c + page1.c + page2.c + page3.c + page4.c + page5.c + page6.c + page7.c + page7a.c + page7b.c + page7c.c +# page8.c +# page9.c +# page10.c + page11.c + page12.c + page13.c + page14.c + page15.c + page16.c + spaced.c + ${_TEST_RESOURCES_RC} +) +target_include_directories(tester + PRIVATE test +) +set_target_properties(tester PROPERTIES + OUTPUT_NAME test + WIN32_EXECUTABLE FALSE +) diff --git a/test/GNUfiles.mk b/test/GNUfiles.mk deleted file mode 100644 index 2b9501b9..00000000 --- a/test/GNUfiles.mk +++ /dev/null @@ -1,28 +0,0 @@ -# 22 april 2015 - -CFILES += \ - test/drawtests.c \ - test/main.c \ - test/menus.c \ - test/page1.c \ - test/page2.c \ - test/page3.c \ - test/page4.c \ - test/page5.c \ - test/page6.c \ - test/page7.c \ - test/page7a.c \ - test/page7b.c \ - test/page7c.c \ - test/page8.c \ - test/page9.c \ - test/page10.c \ - test/spaced.c - -HFILES += \ - test/test.h - -ifeq ($(OS),windows) -RCFILES += \ - test/resources.rc -endif diff --git a/test/OLD_page16.c b/test/OLD_page16.c new file mode 100644 index 00000000..80ac0139 --- /dev/null +++ b/test/OLD_page16.c @@ -0,0 +1,143 @@ +// 21 june 2016 +#include "test.h" + +static uiTableModelHandler mh; + +static int modelNumColumns(uiTableModelHandler *mh, uiTableModel *m) +{ + return 9; +} + +static uiTableModelColumnType modelColumnType(uiTableModelHandler *mh, uiTableModel *m, int column) +{ + if (column == 3 || column == 4) + return uiTableModelColumnColor; + if (column == 5) + return uiTableModelColumnImage; + if (column == 7 || column == 8) + return uiTableModelColumnInt; + return uiTableModelColumnString; +} + +static int modelNumRows(uiTableModelHandler *mh, uiTableModel *m) +{ + return 15; +} + +static uiImage *img[2]; +static char row9text[1024]; +static int yellowRow = -1; +static int checkStates[15]; + +static void *modelCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, int col) +{ + char buf[256]; + + if (col == 3) { + if (row == yellowRow) + return uiTableModelGiveColor(1, 1, 0, 1); + if (row == 3) + return uiTableModelGiveColor(1, 0, 0, 1); + if (row == 11) + return uiTableModelGiveColor(0, 0.5, 1, 0.5); + return NULL; + } + if (col == 4) { + if ((row % 2) == 1) + return uiTableModelGiveColor(0.5, 0, 0.75, 1); + return NULL; + } + if (col == 5) { + if (row < 8) + return img[0]; + return img[1]; + } + if (col == 7) + return uiTableModelGiveInt(checkStates[row]); + if (col == 8) { + if (row == 0) + return uiTableModelGiveInt(0); + if (row == 13) + return uiTableModelGiveInt(100); + if (row == 14) + return uiTableModelGiveInt(-1); + return uiTableModelGiveInt(50); + } + switch (col) { + case 0: + sprintf(buf, "Row %d", row); + break; + case 2: + if (row == 9) + return uiTableModelStrdup(row9text); + // fall through + case 1: + strcpy(buf, "Part"); + break; + case 6: + strcpy(buf, "Make Yellow"); + break; + } + return uiTableModelStrdup(buf); +} + +static void modelSetCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, int col, const void *val) +{ + if (row == 9 && col == 2) + strcpy(row9text, (const char *) val); + if (col == 6) + yellowRow = row; + if (col == 7) + checkStates[row] = uiTableModelTakeInt(val); +} + +uiBox *makePage16(void) +{ + uiBox *page16; + uiTableModel *m; + uiTable *t; + uiTableColumn *tc; + + img[0] = uiNewImage(16, 16); + appendImageNamed(img[0], "andlabs_16x16test_24june2016.png"); + appendImageNamed(img[0], "andlabs_32x32test_24june2016.png"); + img[1] = uiNewImage(16, 16); + appendImageNamed(img[1], "tango-icon-theme-0.8.90_16x16_x-office-spreadsheet.png"); + appendImageNamed(img[1], "tango-icon-theme-0.8.90_32x32_x-office-spreadsheet.png"); + + strcpy(row9text, "Part"); + + memset(checkStates, 0, 15 * sizeof (int)); + + page16 = newVerticalBox(); + + mh.NumColumns = modelNumColumns; + mh.ColumnType = modelColumnType; + mh.NumRows = modelNumRows; + mh.CellValue = modelCellValue; + mh.SetCellValue = modelSetCellValue; + m = uiNewTableModel(&mh); + + t = uiNewTable(m); + uiBoxAppend(page16, uiControl(t), 1); + + uiTableAppendTextColumn(t, "Column 1", 0); + + tc = uiTableAppendColumn(t, "Column 2"); + uiTableColumnAppendImagePart(tc, 5, 0); + uiTableColumnAppendTextPart(tc, 1, 0); + uiTableColumnAppendTextPart(tc, 2, 1); + uiTableColumnPartSetTextColor(tc, 1, 4); + uiTableColumnPartSetEditable(tc, 2, 1); + + uiTableSetRowBackgroundColorModelColumn(t, 3); + + tc = uiTableAppendColumn(t, "Buttons"); + uiTableColumnAppendCheckboxPart(tc, 7, 0); + uiTableColumnAppendButtonPart(tc, 6, 1); + + tc = uiTableAppendColumn(t, "Progress Bar"); + uiTableColumnAppendProgressBarPart(tc, 8, 0); + + return page16; +} diff --git a/test/drawtests.c b/test/drawtests.c index ab1ca8bc..5c409294 100644 --- a/test/drawtests.c +++ b/test/drawtests.c @@ -4,6 +4,7 @@ // TODO // - test multiple clips // - test saving and restoring clips +// - copy tests from https://github.com/Microsoft/WinObjC struct drawtest { const char *name; @@ -87,16 +88,16 @@ static void drawOriginal(uiAreaDrawParams *p) uiDrawPathArcTo(path, 400, 100, 50, - 30. * (M_PI / 180.), - 300. * (M_PI / 180.), + 30. * (uiPi / 180.), + 300. * (uiPi / 180.), 0); // the sweep test below doubles as a clockwise test so a checkbox isn't needed anymore uiDrawPathLineTo(path, 400, 100); uiDrawPathNewFigureWithArc(path, 510, 100, 50, - 30. * (M_PI / 180.), - 300. * (M_PI / 180.), + 30. * (uiPi / 180.), + 300. * (uiPi / 180.), 0); uiDrawPathCloseFigure(path); // and now with 330 to make sure sweeps work properly @@ -104,15 +105,15 @@ static void drawOriginal(uiAreaDrawParams *p) uiDrawPathArcTo(path, 400, 210, 50, - 30. * (M_PI / 180.), - 330. * (M_PI / 180.), + 30. * (uiPi / 180.), + 330. * (uiPi / 180.), 0); uiDrawPathLineTo(path, 400, 210); uiDrawPathNewFigureWithArc(path, 510, 210, 50, - 30. * (M_PI / 180.), - 330. * (M_PI / 180.), + 30. * (uiPi / 180.), + 330. * (uiPi / 180.), 0); uiDrawPathCloseFigure(path); uiDrawPathEnd(path); @@ -160,7 +161,7 @@ static void drawArcs(uiAreaDrawParams *p) path = uiDrawNewPath(uiDrawFillModeWinding); - add = (2.0 * M_PI) / 12; + add = (2.0 * uiPi) / 12; x = start + rad; y = start + rad; @@ -196,7 +197,7 @@ static void drawArcs(uiAreaDrawParams *p) uiDrawPathNewFigureWithArc(path, x, y, rad, - (M_PI / 4), angle, + (uiPi / 4), angle, 0); angle += add; x += 2 * rad + step; @@ -210,7 +211,7 @@ static void drawArcs(uiAreaDrawParams *p) uiDrawPathArcTo(path, x, y, rad, - (M_PI / 4), angle, + (uiPi / 4), angle, 0); angle += add; x += 2 * rad + step; @@ -223,7 +224,7 @@ static void drawArcs(uiAreaDrawParams *p) uiDrawPathNewFigureWithArc(path, x, y, rad, - M_PI + (M_PI / 5), angle, + uiPi + (uiPi / 5), angle, 0); angle += add; x += 2 * rad + step; @@ -237,7 +238,7 @@ static void drawArcs(uiAreaDrawParams *p) uiDrawPathArcTo(path, x, y, rad, - M_PI + (M_PI / 5), angle, + uiPi + (uiPi / 5), angle, 0); angle += add; x += 2 * rad + step; @@ -519,7 +520,7 @@ static void drawD2DRadialBrush(uiAreaDrawParams *p) 75, 75, 75, 0, - 2 * M_PI, + 2 * uiPi, 0); uiDrawPathEnd(path); @@ -596,7 +597,7 @@ static void drawD2DPathGeometries(uiAreaDrawParams *p) uiDrawPathNewFigureWithArc(sun, (440.0 - 270.0) / 2 + 270.0, 255, 85, - M_PI, M_PI, + uiPi, uiPi, 0); uiDrawPathCloseFigure(sun); uiDrawPathEnd(sun); @@ -730,22 +731,22 @@ static void drawD2DGeometryGroup(uiAreaDrawParams *p) uiDrawPathNewFigureWithArc(alternate, 105, 105, 25, - 0, 2 * M_PI, + 0, 2 * uiPi, 0); uiDrawPathNewFigureWithArc(alternate, 105, 105, 50, - 0, 2 * M_PI, + 0, 2 * uiPi, 0); uiDrawPathNewFigureWithArc(alternate, 105, 105, 75, - 0, 2 * M_PI, + 0, 2 * uiPi, 0); uiDrawPathNewFigureWithArc(alternate, 105, 105, 100, - 0, 2 * M_PI, + 0, 2 * uiPi, 0); uiDrawPathEnd(alternate); @@ -753,22 +754,22 @@ static void drawD2DGeometryGroup(uiAreaDrawParams *p) uiDrawPathNewFigureWithArc(winding, 105, 105, 25, - 0, 2 * M_PI, + 0, 2 * uiPi, 0); uiDrawPathNewFigureWithArc(winding, 105, 105, 50, - 0, 2 * M_PI, + 0, 2 * uiPi, 0); uiDrawPathNewFigureWithArc(winding, 105, 105, 75, - 0, 2 * M_PI, + 0, 2 * uiPi, 0); uiDrawPathNewFigureWithArc(winding, 105, 105, 100, - 0, 2 * M_PI, + 0, 2 * uiPi, 0); uiDrawPathEnd(winding); @@ -850,7 +851,7 @@ static void drawD2DRotate(uiAreaDrawParams *p) uiDrawMatrixSetIdentity(&m); uiDrawMatrixRotate(&m, 468.0, 331.5, - 45.0 * (M_PI / 180)); + 45.0 * (uiPi / 180)); uiDrawTransform(p->Context, &m); uiDrawFill(p->Context, path, &fill); @@ -868,7 +869,7 @@ static void drawD2DRotate(uiAreaDrawParams *p) uiDrawMatrixSetIdentity(&m); uiDrawMatrixRotate(&m, 438.0, 301.5, - 45.0 * (M_PI / 180)); + 45.0 * (uiPi / 180)); uiDrawTransform(p->Context, &m); uiDrawFill(p->Context, path, &fill); @@ -993,7 +994,7 @@ void drawD2DSkew(uiAreaDrawParams *p) uiDrawMatrixSetIdentity(&m); uiDrawMatrixSkew(&m, 126.0, 301.5, - 45.0 * (M_PI / 180), 0); + 45.0 * (uiPi / 180), 0); uiDrawTransform(p->Context, &m); uiDrawFill(p->Context, path, &fill); @@ -1011,7 +1012,7 @@ void drawD2DSkew(uiAreaDrawParams *p) uiDrawMatrixSetIdentity(&m); uiDrawMatrixSkew(&m, 0, 0, - 45.0 * (M_PI / 180), 0); + 45.0 * (uiPi / 180), 0); uiDrawTransform(p->Context, &m); uiDrawFill(p->Context, path, &fill); @@ -1111,7 +1112,7 @@ static void drawD2DMultiTransforms(uiAreaDrawParams *p) uiDrawMatrixSetIdentity(&mrotate); uiDrawMatrixRotate(&mrotate, 330.0, 70.0, - 45.0 * (M_PI / 180)); + 45.0 * (uiPi / 180)); // save for when we do the opposite one uiDrawSave(p->Context); @@ -1136,7 +1137,7 @@ static void drawD2DMultiTransforms(uiAreaDrawParams *p) uiDrawMatrixSetIdentity(&mrotate); uiDrawMatrixRotate(&mrotate, 70.0, 70.0, - 45.0 * (M_PI / 180)); + 45.0 * (uiPi / 180)); uiDrawStroke(p->Context, path, &original, &originalsp); @@ -1256,8 +1257,8 @@ static void drawCSArc(uiAreaDrawParams *p) double xc = 128.0; double yc = 128.0; double radius = 100.0; - double angle1 = 45.0 * (M_PI / 180.0); - double angle2 = 180.0 * (M_PI / 180.0); + double angle1 = 45.0 * (uiPi / 180.0); + double angle2 = 180.0 * (uiPi / 180.0); uiDrawBrush source; uiDrawStrokeParams sp; uiDrawPath *path; @@ -1289,7 +1290,7 @@ static void drawCSArc(uiAreaDrawParams *p) uiDrawPathNewFigureWithArc(path, xc, yc, 10.0, - 0, 2 * M_PI, + 0, 2 * uiPi, 0); uiDrawPathEnd(path); uiDrawFill(p->Context, path, &source); @@ -1319,8 +1320,8 @@ static void drawCSArcNegative(uiAreaDrawParams *p) double xc = 128.0; double yc = 128.0; double radius = 100.0; - double angle1 = 45.0 * (M_PI / 180.0); - double angle2 = 180.0 * (M_PI / 180.0); + double angle1 = 45.0 * (uiPi / 180.0); + double angle2 = 180.0 * (uiPi / 180.0); uiDrawBrush source; uiDrawStrokeParams sp; uiDrawPath *path; @@ -1352,7 +1353,7 @@ static void drawCSArcNegative(uiAreaDrawParams *p) uiDrawPathNewFigureWithArc(path, xc, yc, 10.0, - 0, 2 * M_PI, + 0, 2 * uiPi, 0); uiDrawPathEnd(path); uiDrawFill(p->Context, path, &source); @@ -1396,7 +1397,7 @@ static void drawCSClip(uiAreaDrawParams *p) uiDrawPathNewFigureWithArc(path, 128.0, 128.0, 76.8, - 0, 2 * M_PI, + 0, 2 * uiPi, 0); uiDrawPathEnd(path); uiDrawClip(p->Context, path); @@ -1639,12 +1640,12 @@ static void drawCSFillStyle(uiAreaDrawParams *p) uiDrawPathNewFigureWithArc(path, 64, 64, 40, - 0, 2*M_PI, + 0, 2*uiPi, 0); uiDrawPathNewFigureWithArc(path, 192, 64, 40, - 0, -2*M_PI, + 0, -2*uiPi, 1); uiDrawPathEnd(path); @@ -1663,12 +1664,12 @@ static void drawCSFillStyle(uiAreaDrawParams *p) uiDrawPathNewFigureWithArc(path, 64, 64, 40, - 0, 2*M_PI, + 0, 2*uiPi, 0); uiDrawPathNewFigureWithArc(path, 192, 64, 40, - 0, -2*M_PI, + 0, -2*uiPi, 1); uiDrawPathEnd(path); @@ -1728,7 +1729,7 @@ static void drawCSRoundRect(uiAreaDrawParams *p) corner_radius = height / 10.0; /* and corner curvature radius */ double radius = corner_radius / aspect; - double degrees = M_PI / 180.0; + double degrees = uiPi / 180.0; uiDrawBrush source; uiDrawStrokeParams sp; @@ -1748,25 +1749,25 @@ static void drawCSRoundRect(uiAreaDrawParams *p) uiDrawPathNewFigureWithArc(path, x + width - radius, y + radius, radius, - -90 * degrees, M_PI / 2, + -90 * degrees, uiPi / 2, 0); // bottom right corner uiDrawPathArcTo(path, x + width - radius, y + height - radius, radius, - 0 * degrees, M_PI / 2, + 0 * degrees, uiPi / 2, 0); // bottom left corner uiDrawPathArcTo(path, x + radius, y + height - radius, radius, - 90 * degrees, M_PI / 2, + 90 * degrees, uiPi / 2, 0); // top left corner uiDrawPathArcTo(path, x + radius, y + radius, radius, - 180 * degrees, M_PI / 2, + 180 * degrees, uiPi / 2, 0); uiDrawPathCloseFigure(path); uiDrawPathEnd(path); @@ -1932,8 +1933,6 @@ static void drawQ2DCreateWindowGC(uiAreaDrawParams *p) // TODO Text page, if any? -// TODO Paths page - static const struct drawtest tests[] = { { "Original uiArea test", drawOriginal }, { "Arc test", drawArcs }, @@ -1966,7 +1965,7 @@ static const struct drawtest tests[] = { { NULL, NULL }, }; -void runDrawTest(intmax_t n, uiAreaDrawParams *p) +void runDrawTest(int n, uiAreaDrawParams *p) { (*(tests[n].draw))(p); } diff --git a/test/images.c b/test/images.c new file mode 100644 index 00000000..31107f77 --- /dev/null +++ b/test/images.c @@ -0,0 +1,682 @@ +// auto-generated by images/gen.go +#include "test.h" + +static const uint8_t dat0[] = { + 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, + 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, + 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, + 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, + 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, + 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, + 0x8A, 0xC3, 0xFF, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, +}; + +static const uint8_t dat1[] = { + 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, + 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, + 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, + 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, + 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF, + 0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, + 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, + 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, + 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, + 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF, + 0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, + 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, + 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, + 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, + 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, + 0x8A, 0xC3, 0xFF, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0x8A, 0xC3, 0xFF, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0x8A, 0xC3, 0xFF, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, + 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, + 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, +}; + +static const uint8_t dat2[] = { + 0x67, 0x67, 0x67, 0xAC, 0x81, 0x81, 0x81, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x81, 0x81, 0x81, 0xFF, + 0x81, 0x81, 0x81, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x81, 0x81, 0x81, 0xFF, + 0x81, 0x81, 0x81, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x81, 0x81, 0x81, 0xFF, + 0x81, 0x81, 0x81, 0xFF, 0x2B, 0x2B, 0x2B, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB7, 0xB7, 0xB7, 0xFF, 0xB7, 0xB7, 0xB7, 0xFF, + 0xB7, 0xB7, 0xB8, 0xFF, 0x9E, 0x9E, 0x9F, 0xFF, 0xB8, 0xB8, 0xB8, 0xFF, 0xB8, 0xB8, 0xB8, 0xFF, + 0x9F, 0x9F, 0x9F, 0xFF, 0xB8, 0xB8, 0xB9, 0xFF, 0xB8, 0xB8, 0xB9, 0xFF, 0x9F, 0x9F, 0x9F, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9E, 0x9E, 0x9E, 0xFF, 0x9F, 0x9F, 0x9E, 0xFF, + 0x9F, 0x9F, 0x9F, 0xFF, 0x9F, 0x9F, 0x9F, 0xFF, 0x9F, 0x9F, 0x9F, 0xFF, 0x9F, 0x9F, 0x9F, 0xFF, + 0x9F, 0x9F, 0x9F, 0xFF, 0x9F, 0x9F, 0x9F, 0xFF, 0x9F, 0x9F, 0x9F, 0xFF, 0x9F, 0x9F, 0x9F, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB7, 0xB8, 0xB7, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF, + 0xC3, 0xC3, 0xC3, 0xFF, 0x9F, 0x9F, 0x9F, 0xFF, 0xEE, 0xEE, 0xEE, 0xFF, 0xEE, 0xEE, 0xEE, 0xFF, + 0xC2, 0xC2, 0xC2, 0xFF, 0xEE, 0xEF, 0xEE, 0xFF, 0xEF, 0xEE, 0xEF, 0xFF, 0xC2, 0xC2, 0xC2, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0xB1, 0xB1, 0xFF, 0xB8, 0xB8, 0xB8, 0xFF, + 0xB9, 0xB8, 0xB9, 0xFF, 0x9F, 0x9F, 0x9F, 0xFF, 0xE0, 0xE0, 0xE0, 0xFF, 0xE0, 0xE1, 0xE0, 0xFF, + 0xC2, 0xC2, 0xC2, 0xFF, 0xE1, 0xE0, 0xE0, 0xFF, 0xE1, 0xE1, 0xE1, 0xFF, 0xC2, 0xC2, 0xC2, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB9, 0xB8, 0xB8, 0xFF, 0xC4, 0xC4, 0xC4, 0xFF, + 0xC3, 0xC4, 0xC4, 0xFF, 0x9F, 0x9F, 0x9F, 0xFF, 0xEE, 0xEE, 0xEF, 0xFF, 0xEF, 0xEE, 0xEF, 0xFF, + 0xC2, 0xC2, 0xC2, 0xFF, 0xEF, 0xEF, 0xEF, 0xFF, 0xF0, 0xEF, 0xEF, 0xFF, 0xC2, 0xC2, 0xC2, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0xB1, 0xB1, 0xFF, 0xB9, 0xB9, 0xB8, 0xFF, + 0xB9, 0xB9, 0xB9, 0xFF, 0x9F, 0x9F, 0x9F, 0xFF, 0xC9, 0xA6, 0xA5, 0xFF, 0xAE, 0x3A, 0x36, 0xFF, + 0xA5, 0x0F, 0x0C, 0xFF, 0xA4, 0x02, 0x01, 0xFF, 0xA4, 0x02, 0x01, 0xFF, 0xA4, 0x0D, 0x0A, 0xFF, + 0xB0, 0x3B, 0x37, 0xFF, 0x8D, 0x69, 0x69, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB9, 0xB9, 0xB9, 0xFF, 0xC4, 0xC4, 0xC4, 0xFF, + 0xC4, 0xC4, 0xC4, 0xFF, 0x9E, 0x40, 0x3D, 0xFF, 0xA7, 0x0A, 0x07, 0xFF, 0xC6, 0x64, 0x53, 0xFF, + 0xCB, 0x71, 0x5C, 0xFF, 0xC9, 0x73, 0x5D, 0xFF, 0xC5, 0x71, 0x5B, 0xFF, 0xBF, 0x6C, 0x59, 0xFF, + 0xC0, 0x6E, 0x61, 0xFF, 0xAC, 0x20, 0x1D, 0xFF, 0x7E, 0x29, 0x27, 0xC1, 0x19, 0x19, 0x19, 0x19, + 0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB2, 0xB2, 0xB2, 0xFF, 0xB9, 0xB9, 0xB9, 0xFF, + 0xA6, 0x5C, 0x5B, 0xFF, 0xAF, 0x20, 0x17, 0xFF, 0xCB, 0x72, 0x5D, 0xFF, 0xC7, 0x65, 0x4D, 0xFF, + 0xBB, 0x53, 0x38, 0xFF, 0xB6, 0x51, 0x36, 0xFF, 0xB0, 0x4E, 0x35, 0xFF, 0xB3, 0x5D, 0x47, 0xFF, + 0xAE, 0x5B, 0x45, 0xFF, 0xA9, 0x57, 0x43, 0xFF, 0xAA, 0x2F, 0x28, 0xFF, 0x64, 0x17, 0x16, 0xA1, + 0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB9, 0xB9, 0xB9, 0xFF, 0xC4, 0xC4, 0xC4, 0xFF, + 0xA5, 0x14, 0x12, 0xFF, 0xCA, 0x6F, 0x5A, 0xFF, 0xBF, 0x54, 0x39, 0xFF, 0xBA, 0x52, 0x37, 0xFF, + 0xB4, 0x4F, 0x36, 0xFF, 0xAF, 0x4D, 0x34, 0xFF, 0x88, 0x65, 0x56, 0xFF, 0x76, 0x5F, 0x5A, 0xFF, + 0x71, 0x5B, 0x56, 0xFF, 0x6B, 0x58, 0x53, 0xFF, 0x77, 0x60, 0x5B, 0xFF, 0x98, 0x0C, 0x0B, 0xF7, + 0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB2, 0xB2, 0xB2, 0xFF, 0xB9, 0xB9, 0xB9, 0xFF, + 0xA4, 0x02, 0x01, 0xFF, 0xC7, 0x69, 0x51, 0xFF, 0xB8, 0x51, 0x37, 0xFF, 0xB2, 0x4E, 0x36, 0xFF, + 0xAD, 0x4C, 0x34, 0xFF, 0xAE, 0x65, 0x3A, 0xFF, 0x95, 0x99, 0x5D, 0xFF, 0x1D, 0x84, 0xA0, 0xFF, + 0x17, 0x7F, 0x99, 0xFF, 0x3D, 0x91, 0xA5, 0xFF, 0x3B, 0x8E, 0xA0, 0xFF, 0xA2, 0x01, 0x01, 0xFF, + 0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB9, 0xB9, 0xB9, 0xFF, 0xC4, 0xC4, 0xC4, 0xFF, + 0x9F, 0x12, 0x12, 0xFF, 0xCA, 0x7E, 0x6D, 0xFF, 0xB8, 0x5F, 0x48, 0xFF, 0xAB, 0x4C, 0x33, 0xFF, + 0xA6, 0x49, 0x32, 0xFF, 0xB1, 0x8B, 0x44, 0xFF, 0xB8, 0xBC, 0x50, 0xFF, 0x35, 0x80, 0x86, 0xFF, + 0x50, 0x9C, 0xAD, 0xFF, 0x27, 0x83, 0x97, 0xFF, 0x49, 0x6E, 0x77, 0xFF, 0x92, 0x08, 0x08, 0xF7, + 0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xBC, 0x78, 0x78, 0xFF, 0x9C, 0x1D, 0x1A, 0xFF, 0xBD, 0x7E, 0x6D, 0xFF, 0xBE, 0x7F, 0x70, 0xFF, + 0xBD, 0x83, 0x74, 0xFF, 0xCD, 0xC7, 0x87, 0xFF, 0xCB, 0xD0, 0x89, 0xFF, 0xB4, 0xB4, 0x8A, 0xFF, + 0x5C, 0x93, 0xA0, 0xFF, 0x44, 0x65, 0x6D, 0xFF, 0x7B, 0x16, 0x18, 0xFF, 0x66, 0x1D, 0x1D, 0xA1, + 0x69, 0x69, 0x69, 0xB4, 0x81, 0x81, 0x81, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x81, 0x81, 0x81, 0xFF, + 0x81, 0x81, 0x81, 0xFF, 0x93, 0x40, 0x40, 0xFF, 0x9E, 0x06, 0x05, 0xFF, 0x99, 0x46, 0x3D, 0xFF, + 0x9E, 0x62, 0x4D, 0xFF, 0xA6, 0xA1, 0x4C, 0xFF, 0x9F, 0x9B, 0x4A, 0xFF, 0x94, 0x88, 0x45, 0xFF, + 0x4B, 0x3C, 0x43, 0xFF, 0x9A, 0x05, 0x05, 0xFF, 0x77, 0x1F, 0x1F, 0xBA, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x16, 0x16, 0x55, 0x7F, 0x17, 0x17, 0xD3, + 0x9C, 0x0B, 0x0B, 0xF9, 0xA3, 0x02, 0x01, 0xFF, 0xA2, 0x01, 0x01, 0xFF, 0x9C, 0x0B, 0x0B, 0xF9, + 0x88, 0x1E, 0x1E, 0xD3, 0x41, 0x22, 0x22, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t dat3[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x30, 0x30, 0x30, 0x52, 0x91, 0x91, 0x91, 0xF2, 0x99, 0x99, 0x99, 0xFF, 0x9C, 0x9C, 0x9C, 0xFF, + 0x9E, 0x9E, 0x9E, 0xFF, 0x9C, 0x9C, 0x9C, 0xFF, 0x99, 0x99, 0x99, 0xFF, 0x96, 0x96, 0x96, 0xFF, + 0x93, 0x93, 0x93, 0xFF, 0x8F, 0x8F, 0x8F, 0xFF, 0x8C, 0x8C, 0x8C, 0xFF, 0x88, 0x88, 0x88, 0xFF, + 0x84, 0x84, 0x84, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x7D, 0x7D, 0x7D, 0xFF, 0x7A, 0x7A, 0x7A, 0xFF, + 0x76, 0x76, 0x76, 0xFF, 0x72, 0x72, 0x72, 0xFF, 0x6F, 0x6F, 0x6F, 0xFF, 0x6B, 0x6B, 0x6B, 0xFF, + 0x68, 0x68, 0x68, 0xFF, 0x64, 0x64, 0x64, 0xFF, 0x5F, 0x5F, 0x5F, 0xF2, 0x1E, 0x1E, 0x1E, 0x52, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x8D, 0x8D, 0x8D, 0xEF, 0xFD, 0xFD, 0xFD, 0xFF, 0xFD, 0xFD, 0xFD, 0xFF, 0xFE, 0xFE, 0xFE, 0xFF, + 0xFE, 0xFE, 0xFE, 0xFF, 0xFE, 0xFE, 0xFE, 0xFF, 0xFD, 0xFD, 0xFD, 0xFF, 0xFD, 0xFD, 0xFD, 0xFF, + 0xFD, 0xFD, 0xFD, 0xFF, 0xFD, 0xFD, 0xFD, 0xFF, 0xFC, 0xFC, 0xFC, 0xFF, 0xFC, 0xFC, 0xFC, 0xFF, + 0xFC, 0xFC, 0xFC, 0xFF, 0xFB, 0xFB, 0xFB, 0xFF, 0xFB, 0xFB, 0xFB, 0xFF, 0xFB, 0xFB, 0xFB, 0xFF, + 0xFA, 0xFA, 0xFA, 0xFF, 0xFA, 0xFA, 0xFA, 0xFF, 0xFA, 0xFA, 0xFA, 0xFF, 0xF9, 0xF9, 0xF9, 0xFF, + 0xF9, 0xF9, 0xF9, 0xFF, 0xF9, 0xF9, 0xF9, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x5D, 0x5D, 0x5D, 0xEF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x93, 0x93, 0x93, 0xFF, 0xFD, 0xFD, 0xFD, 0xFF, 0xDC, 0xDC, 0xDC, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, + 0xDE, 0xDE, 0xDE, 0xFF, 0xDE, 0xDE, 0xDE, 0xFF, 0xDF, 0xDF, 0xDF, 0xFF, 0xDF, 0xDF, 0xDF, 0xFF, + 0xE0, 0xE0, 0xE0, 0xFF, 0xE1, 0xE1, 0xE1, 0xFF, 0xE1, 0xE1, 0xE1, 0xFF, 0xE1, 0xE1, 0xE1, 0xFF, + 0xE2, 0xE2, 0xE2, 0xFF, 0xE2, 0xE2, 0xE2, 0xFF, 0xE2, 0xE2, 0xE2, 0xFF, 0xE2, 0xE2, 0xE2, 0xFF, + 0xE3, 0xE3, 0xE3, 0xFF, 0xE3, 0xE3, 0xE3, 0xFF, 0xE3, 0xE3, 0xE3, 0xFF, 0xE3, 0xE3, 0xE3, 0xFF, + 0xE3, 0xE3, 0xE3, 0xFF, 0xE2, 0xE2, 0xE2, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x5D, 0x5D, 0x5D, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x92, 0x92, 0x92, 0xFF, 0xFE, 0xFE, 0xFE, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0x78, 0x78, 0x78, 0xFF, + 0x8D, 0x8D, 0x8D, 0xFF, 0x8E, 0x8E, 0x8E, 0xFF, 0x8F, 0x8F, 0x8F, 0xFF, 0x8F, 0x8F, 0x8F, 0xFF, + 0x78, 0x78, 0x78, 0xFF, 0x8F, 0x8F, 0x8F, 0xFF, 0x90, 0x90, 0x90, 0xFF, 0x90, 0x90, 0x90, 0xFF, + 0x7A, 0x7A, 0x7A, 0xFF, 0x91, 0x91, 0x91, 0xFF, 0x91, 0x91, 0x91, 0xFF, 0x91, 0x91, 0x91, 0xFF, + 0x7B, 0x7B, 0x7B, 0xFF, 0x91, 0x91, 0x91, 0xFF, 0x91, 0x91, 0x91, 0xFF, 0x91, 0x91, 0x91, 0xFF, + 0x7D, 0x7D, 0x7D, 0xFF, 0xE3, 0xE3, 0xE3, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x5D, 0x5D, 0x5D, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x90, 0x90, 0x90, 0xFF, 0xFE, 0xFE, 0xFE, 0xFF, 0xDE, 0xDE, 0xDE, 0xFF, 0x94, 0x94, 0x94, 0xFF, + 0xB0, 0xB0, 0xB0, 0xFF, 0xB1, 0xB1, 0xB1, 0xFF, 0xB1, 0xB1, 0xB1, 0xFF, 0xB1, 0xB1, 0xB1, 0xFF, + 0x96, 0x96, 0x96, 0xFF, 0xB2, 0xB2, 0xB2, 0xFF, 0xB3, 0xB3, 0xB3, 0xFF, 0xB3, 0xB3, 0xB3, 0xFF, + 0x98, 0x98, 0x98, 0xFF, 0xB4, 0xB4, 0xB4, 0xFF, 0xB4, 0xB4, 0xB4, 0xFF, 0xB5, 0xB5, 0xB5, 0xFF, + 0x99, 0x99, 0x99, 0xFF, 0xB5, 0xB5, 0xB5, 0xFF, 0xB5, 0xB5, 0xB5, 0xFF, 0xB5, 0xB5, 0xB5, 0xFF, + 0x99, 0x99, 0x99, 0xFF, 0xE4, 0xE4, 0xE4, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x5C, 0x5C, 0x5C, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x8E, 0x8E, 0x8E, 0xFF, 0xFD, 0xFD, 0xFD, 0xFF, 0xDF, 0xDF, 0xDF, 0xFF, 0x94, 0x94, 0x94, 0xFF, + 0xB1, 0xB1, 0xB1, 0xFF, 0xB1, 0xB1, 0xB1, 0xFF, 0xB2, 0xB2, 0xB2, 0xFF, 0xB2, 0xB2, 0xB2, 0xFF, + 0x97, 0x97, 0x97, 0xFF, 0xB3, 0xB3, 0xB3, 0xFF, 0xB4, 0xB4, 0xB4, 0xFF, 0xB4, 0xB4, 0xB4, 0xFF, + 0x99, 0x99, 0x99, 0xFF, 0xB5, 0xB5, 0xB5, 0xFF, 0xB5, 0xB5, 0xB5, 0xFF, 0xB5, 0xB5, 0xB5, 0xFF, + 0x99, 0x99, 0x99, 0xFF, 0xB5, 0xB5, 0xB5, 0xFF, 0xB5, 0xB5, 0xB5, 0xFF, 0xB5, 0xB5, 0xB5, 0xFF, + 0x99, 0x99, 0x99, 0xFF, 0xE5, 0xE5, 0xE5, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x5C, 0x5C, 0x5C, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x8B, 0x8B, 0x8B, 0xFF, 0xFD, 0xFD, 0xFD, 0xFF, 0xDF, 0xDF, 0xDF, 0xFF, 0x7A, 0x7A, 0x7A, 0xFF, + 0x91, 0x91, 0x91, 0xFF, 0x92, 0x92, 0x92, 0xFF, 0x92, 0x92, 0x92, 0xFF, 0x93, 0x93, 0x93, 0xFF, + 0x7D, 0x7D, 0x7D, 0xFF, 0x94, 0x94, 0x94, 0xFF, 0x94, 0x94, 0x94, 0xFF, 0x94, 0x94, 0x94, 0xFF, + 0x7D, 0x7D, 0x7D, 0xFF, 0x94, 0x94, 0x94, 0xFF, 0x94, 0x94, 0x94, 0xFF, 0x95, 0x95, 0x95, 0xFF, + 0x7E, 0x7E, 0x7E, 0xFF, 0x95, 0x95, 0x95, 0xFF, 0x95, 0x95, 0x95, 0xFF, 0x95, 0x95, 0x95, 0xFF, + 0x7E, 0x7E, 0x7E, 0xFF, 0xE6, 0xE6, 0xE6, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x5B, 0x5B, 0x5B, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x89, 0x89, 0x89, 0xFF, 0xFD, 0xFD, 0xFD, 0xFF, 0xE0, 0xE0, 0xE0, 0xFF, 0x94, 0x94, 0x94, 0xFF, + 0xB0, 0xB0, 0xB0, 0xFF, 0xB0, 0xB0, 0xB0, 0xFF, 0xB1, 0xB1, 0xB1, 0xFF, 0xB2, 0xB2, 0xB2, 0xFF, + 0x97, 0x97, 0x97, 0xFF, 0xE2, 0xE2, 0xE2, 0xFF, 0xE3, 0xE3, 0xE3, 0xFF, 0xE3, 0xE3, 0xE3, 0xFF, + 0xC0, 0xC0, 0xC0, 0xFF, 0xE4, 0xE4, 0xE4, 0xFF, 0xE4, 0xE4, 0xE4, 0xFF, 0xE5, 0xE5, 0xE5, 0xFF, + 0xC1, 0xC1, 0xC1, 0xFF, 0xE5, 0xE5, 0xE5, 0xFF, 0xE5, 0xE5, 0xE5, 0xFF, 0xE5, 0xE5, 0xE5, 0xFF, + 0xC1, 0xC1, 0xC1, 0xFF, 0xE8, 0xE8, 0xE8, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x5A, 0x5A, 0x5A, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x86, 0x86, 0x86, 0xFF, 0xFD, 0xFD, 0xFD, 0xFF, 0xE1, 0xE1, 0xE1, 0xFF, 0x7B, 0x7B, 0x7B, 0xFF, + 0x92, 0x92, 0x92, 0xFF, 0x93, 0x93, 0x93, 0xFF, 0x94, 0x94, 0x94, 0xFF, 0x94, 0x94, 0x94, 0xFF, + 0x7D, 0x7D, 0x7D, 0xFF, 0xBD, 0xBD, 0xBD, 0xFF, 0xBD, 0xBD, 0xBD, 0xFF, 0xBD, 0xBD, 0xBD, 0xFF, + 0xA0, 0xA0, 0xA0, 0xFF, 0xBE, 0xBE, 0xBE, 0xFF, 0xBE, 0xBE, 0xBE, 0xFF, 0xBF, 0xBF, 0xBF, 0xFF, + 0xA1, 0xA1, 0xA1, 0xFF, 0xBF, 0xBF, 0xBF, 0xFF, 0xBF, 0xBF, 0xBF, 0xFF, 0xBF, 0xBF, 0xBF, 0xFF, + 0xA1, 0xA1, 0xA1, 0xFF, 0xE9, 0xE9, 0xE9, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x59, 0x59, 0x59, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x83, 0x83, 0x83, 0xFF, 0xFD, 0xFD, 0xFD, 0xFF, 0xE1, 0xE1, 0xE1, 0xFF, 0x94, 0x94, 0x94, 0xFF, + 0xB1, 0xB1, 0xB1, 0xFF, 0xB2, 0xB2, 0xB2, 0xFF, 0xB3, 0xB3, 0xB3, 0xFF, 0xB3, 0xB3, 0xB3, 0xFF, + 0x97, 0x97, 0x97, 0xFF, 0xE4, 0xE4, 0xE4, 0xFF, 0xE5, 0xE5, 0xE5, 0xFF, 0xE5, 0xE5, 0xE5, 0xFF, + 0xC2, 0xC2, 0xC2, 0xFF, 0xE6, 0xE6, 0xE6, 0xFF, 0xE6, 0xE6, 0xE6, 0xFF, 0xE7, 0xE7, 0xE7, 0xFF, + 0xC3, 0xC3, 0xC3, 0xFF, 0xE7, 0xE7, 0xE7, 0xFF, 0xE7, 0xE7, 0xE7, 0xFF, 0xE7, 0xE7, 0xE7, 0xFF, + 0xC3, 0xC3, 0xC3, 0xFF, 0xEA, 0xEA, 0xEA, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x58, 0x58, 0x58, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x80, 0x80, 0xFF, 0xFC, 0xFC, 0xFC, 0xFF, 0xE2, 0xE2, 0xE2, 0xFF, 0x7C, 0x7C, 0x7C, 0xFF, + 0x94, 0x94, 0x94, 0xFF, 0x94, 0x94, 0x94, 0xFF, 0x94, 0x94, 0x94, 0xFF, 0x94, 0x94, 0x94, 0xFF, + 0x7E, 0x7E, 0x7E, 0xFF, 0xBE, 0xBE, 0xBE, 0xFF, 0xBE, 0xBE, 0xBE, 0xFF, 0xBF, 0xBF, 0xBF, 0xFF, + 0xA2, 0xA2, 0xA2, 0xFF, 0xC0, 0xC0, 0xC0, 0xFF, 0xC0, 0xC0, 0xC0, 0xFF, 0xC1, 0xC1, 0xC1, 0xFF, + 0xA3, 0xA3, 0xA3, 0xFF, 0xC1, 0xC1, 0xC1, 0xFF, 0xC1, 0xC1, 0xC1, 0xFF, 0xC1, 0xC1, 0xC1, 0xFF, + 0xA3, 0xA3, 0xA3, 0xFF, 0xEB, 0xEB, 0xEB, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x57, 0x57, 0x57, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7D, 0x7D, 0x7D, 0xFF, 0xFC, 0xFC, 0xFC, 0xFF, 0xE3, 0xE3, 0xE3, 0xFF, 0x96, 0x96, 0x96, 0xFF, + 0xB3, 0xB3, 0xB3, 0xFF, 0xB3, 0xB3, 0xB3, 0xFF, 0xB3, 0xB3, 0xB3, 0xFF, 0xB4, 0xB4, 0xB4, 0xFF, + 0x99, 0x99, 0x99, 0xFF, 0xE6, 0xE6, 0xE6, 0xFF, 0xE6, 0xE6, 0xE6, 0xFF, 0xE7, 0xE7, 0xE7, 0xFF, + 0xC4, 0xC4, 0xC4, 0xFF, 0xE8, 0xE8, 0xE8, 0xFF, 0xE8, 0xE8, 0xE8, 0xFF, 0xE9, 0xE9, 0xE9, 0xFF, + 0xC4, 0xC4, 0xC4, 0xFF, 0xE9, 0xE9, 0xE9, 0xFF, 0xE9, 0xE9, 0xE9, 0xFF, 0xE9, 0xE9, 0xE9, 0xFF, + 0xC4, 0xC4, 0xC4, 0xFF, 0xEC, 0xEC, 0xEC, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x55, 0x55, 0x55, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7A, 0x7A, 0x7A, 0xFF, 0xFC, 0xFC, 0xFC, 0xFF, 0xE3, 0xE3, 0xE3, 0xFF, 0x7D, 0x7D, 0x7D, 0xFF, + 0x94, 0x94, 0x94, 0xFF, 0x94, 0x94, 0x94, 0xFF, 0x95, 0x95, 0x95, 0xFF, 0x96, 0x96, 0x96, 0xFF, + 0x7F, 0x7F, 0x7F, 0xFF, 0xBF, 0xBF, 0xBF, 0xFF, 0xC0, 0xC0, 0xC0, 0xFF, 0xC1, 0xC1, 0xC1, 0xFF, + 0xA3, 0xA3, 0xA3, 0xFF, 0xC1, 0xC1, 0xC1, 0xFF, 0xC1, 0xC1, 0xC1, 0xFF, 0xC2, 0xC2, 0xC2, 0xFF, + 0xA4, 0xA4, 0xA4, 0xFF, 0xC2, 0xC2, 0xC2, 0xFF, 0xC2, 0xC2, 0xC2, 0xFF, 0xC2, 0xC2, 0xC2, 0xFF, + 0xA4, 0xA4, 0xA4, 0xFF, 0xED, 0xED, 0xED, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x54, 0x54, 0x54, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x77, 0x77, 0x77, 0xFF, 0xFB, 0xFB, 0xFB, 0xFF, 0xE4, 0xE4, 0xE4, 0xFF, 0x97, 0x97, 0x97, 0xFF, + 0xB3, 0xB3, 0xB3, 0xFF, 0xB4, 0xB4, 0xB4, 0xFF, 0xB5, 0xB5, 0xB5, 0xFF, 0xB6, 0xB6, 0xB6, 0xFF, + 0x99, 0x99, 0x99, 0xFF, 0xE7, 0xE7, 0xE7, 0xFF, 0xE8, 0xE8, 0xE8, 0xFF, 0xE9, 0xE9, 0xE9, 0xFF, + 0xC4, 0xC4, 0xC4, 0xFF, 0xEA, 0xEA, 0xEA, 0xFF, 0xEA, 0xEA, 0xEA, 0xFF, 0xEB, 0xEB, 0xEB, 0xFF, + 0xC6, 0xC6, 0xC6, 0xFF, 0xEB, 0xEB, 0xEB, 0xFF, 0xEB, 0xEB, 0xEB, 0xFF, 0xEB, 0xEB, 0xEB, 0xFF, + 0xC6, 0xC6, 0xC6, 0xFF, 0xEE, 0xEE, 0xEE, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x52, 0x52, 0x52, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x73, 0x73, 0x73, 0xFF, 0xFB, 0xFB, 0xFB, 0xFF, 0xE4, 0xE4, 0xE4, 0xFF, 0x7D, 0x7D, 0x7D, 0xFF, + 0x94, 0x94, 0x94, 0xFF, 0x95, 0x95, 0x95, 0xFF, 0x96, 0x96, 0x96, 0xFF, 0x97, 0x97, 0x97, 0xFF, + 0x7F, 0x7F, 0x7F, 0xFF, 0xC1, 0xC1, 0xC1, 0xFF, 0xC1, 0xC1, 0xC1, 0xFF, 0xC2, 0xC2, 0xC2, 0xFF, + 0xA3, 0xA2, 0xA2, 0xFF, 0xAB, 0x91, 0x91, 0xFF, 0x94, 0x60, 0x60, 0xFF, 0x84, 0x3D, 0x3D, 0xFF, + 0x78, 0x25, 0x25, 0xFF, 0x80, 0x27, 0x27, 0xFF, 0x7E, 0x2B, 0x2B, 0xFF, 0x84, 0x3D, 0x3D, 0xFF, + 0x86, 0x52, 0x52, 0xFF, 0xCC, 0xB2, 0xB2, 0xFF, 0xF6, 0xF5, 0xF5, 0xFF, 0x50, 0x50, 0x50, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x70, 0x70, 0x70, 0xFF, 0xFB, 0xFB, 0xFB, 0xFF, 0xE5, 0xE5, 0xE5, 0xFF, 0x97, 0x97, 0x97, 0xFF, + 0xB4, 0xB4, 0xB4, 0xFF, 0xB5, 0xB5, 0xB5, 0xFF, 0xB6, 0xB6, 0xB6, 0xFF, 0xB6, 0xB6, 0xB6, 0xFF, + 0x9A, 0x9A, 0x9A, 0xFF, 0xE9, 0xE9, 0xE9, 0xFF, 0xE6, 0xE4, 0xE4, 0xFF, 0xB1, 0x85, 0x85, 0xFF, + 0x7A, 0x1B, 0x1B, 0xFF, 0xA6, 0x2F, 0x2F, 0xFF, 0xD3, 0x50, 0x50, 0xFF, 0xF2, 0x67, 0x67, 0xFF, + 0xFF, 0x70, 0x70, 0xFF, 0xFE, 0x6E, 0x6E, 0xFF, 0xFC, 0x69, 0x69, 0xFF, 0xED, 0x5B, 0x5B, 0xFF, + 0xCD, 0x43, 0x43, 0xFF, 0xA2, 0x25, 0x25, 0xFF, 0x7E, 0x1D, 0x1D, 0xFF, 0x58, 0x2C, 0x2C, 0xFF, + 0x02, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x6D, 0x6D, 0x6D, 0xFF, 0xFB, 0xFB, 0xFB, 0xFF, 0xE5, 0xE5, 0xE5, 0xFF, 0x7D, 0x7D, 0x7D, 0xFF, + 0x95, 0x95, 0x95, 0xFF, 0x96, 0x96, 0x96, 0xFF, 0x97, 0x97, 0x97, 0xFF, 0x97, 0x97, 0x97, 0xFF, + 0x80, 0x80, 0x80, 0xFF, 0xB7, 0xAB, 0xAB, 0xFF, 0x7B, 0x28, 0x28, 0xFF, 0xAC, 0x34, 0x34, 0xFF, + 0xF5, 0x6A, 0x6A, 0xFF, 0xFF, 0x70, 0x70, 0xFF, 0xFF, 0x70, 0x70, 0xFF, 0xFE, 0x6F, 0x6F, 0xFF, + 0xFC, 0x6A, 0x6A, 0xFF, 0xFA, 0x65, 0x65, 0xFF, 0xF8, 0x60, 0x60, 0xFF, 0xF5, 0x5C, 0x5C, 0xFF, + 0xF3, 0x57, 0x57, 0xFF, 0xF1, 0x52, 0x52, 0xFF, 0xE6, 0x48, 0x48, 0xFF, 0xA1, 0x1F, 0x1F, 0xFF, + 0x53, 0x00, 0x00, 0xCB, 0x0C, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x6A, 0x6A, 0x6A, 0xFF, 0xFA, 0xFA, 0xFA, 0xFF, 0xE6, 0xE6, 0xE6, 0xFF, 0x98, 0x98, 0x98, 0xFF, + 0xB5, 0xB5, 0xB5, 0xFF, 0xB6, 0xB6, 0xB6, 0xFF, 0xB6, 0xB6, 0xB6, 0xFF, 0xB7, 0xB7, 0xB7, 0xFF, + 0x98, 0x92, 0x92, 0xFF, 0x7E, 0x26, 0x26, 0xFF, 0xCB, 0x50, 0x50, 0xFF, 0xFF, 0x74, 0x74, 0xFF, + 0xFF, 0x70, 0x70, 0xFF, 0xFF, 0x70, 0x70, 0xFF, 0xFD, 0x6B, 0x6B, 0xFF, 0xFA, 0x67, 0x67, 0xFF, + 0xF8, 0x62, 0x62, 0xFF, 0xF6, 0x5D, 0x5D, 0xFF, 0xF4, 0x58, 0x58, 0xFF, 0xF1, 0x53, 0x53, 0xFF, + 0xEF, 0x4E, 0x4E, 0xFF, 0xED, 0x49, 0x49, 0xFF, 0xEB, 0x44, 0x44, 0xFF, 0xE9, 0x3F, 0x3F, 0xFF, + 0xB6, 0x24, 0x24, 0xFF, 0x57, 0x00, 0x00, 0xD7, 0x06, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x66, 0x66, 0x66, 0xFF, 0xFA, 0xFA, 0xFA, 0xFF, 0xE6, 0xE6, 0xE6, 0xFF, 0x7E, 0x7E, 0x7E, 0xFF, + 0x96, 0x96, 0x96, 0xFF, 0x97, 0x97, 0x97, 0xFF, 0x97, 0x97, 0x97, 0xFF, 0x98, 0x98, 0x98, 0xFF, + 0x73, 0x3D, 0x3D, 0xFF, 0xA6, 0x37, 0x37, 0xFF, 0xFF, 0x79, 0x79, 0xFF, 0xFF, 0x72, 0x72, 0xFF, + 0xFD, 0x6D, 0x6D, 0xFF, 0xFB, 0x68, 0x68, 0xFF, 0xF9, 0x63, 0x63, 0xFF, 0xF6, 0x5E, 0x5E, 0xFF, + 0xF4, 0x59, 0x59, 0xFF, 0xF2, 0x54, 0x54, 0xFF, 0xF0, 0x4F, 0x4F, 0xFF, 0xEE, 0x4A, 0x4A, 0xFF, + 0xEB, 0x45, 0x45, 0xFF, 0xE9, 0x41, 0x41, 0xFF, 0xE7, 0x3C, 0x3C, 0xFF, 0xE5, 0x37, 0x37, 0xFF, + 0xE2, 0x35, 0x35, 0xFF, 0x91, 0x13, 0x13, 0xFF, 0x36, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x63, 0x63, 0x63, 0xFF, 0xFA, 0xFA, 0xFA, 0xFF, 0xE6, 0xE6, 0xE6, 0xFF, 0x98, 0x98, 0x98, 0xFF, + 0xB5, 0xB5, 0xB5, 0xFF, 0xB6, 0xB6, 0xB6, 0xFF, 0xB7, 0xB7, 0xB7, 0xFF, 0xB8, 0xB8, 0xB8, 0xFF, + 0x6E, 0x15, 0x15, 0xFF, 0xDF, 0x6D, 0x6D, 0xFF, 0xFE, 0x75, 0x75, 0xFF, 0xFB, 0x69, 0x69, 0xFF, + 0xF9, 0x64, 0x64, 0xFF, 0xF7, 0x5F, 0x5F, 0xFF, 0xF5, 0x5A, 0x5A, 0xFF, 0xF3, 0x55, 0x55, 0xFF, + 0xF0, 0x50, 0x50, 0xFF, 0xEE, 0x4C, 0x4C, 0xFF, 0xEC, 0x47, 0x47, 0xFF, 0xEA, 0x42, 0x42, 0xFF, + 0xE7, 0x3D, 0x3D, 0xFF, 0xE5, 0x38, 0x38, 0xFF, 0xE3, 0x33, 0x33, 0xFF, 0xE1, 0x2E, 0x2E, 0xFF, + 0xDF, 0x2D, 0x2D, 0xFF, 0xC2, 0x28, 0x28, 0xFF, 0x59, 0x00, 0x00, 0xDE, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x60, 0x60, 0x60, 0xFF, 0xF9, 0xF9, 0xF9, 0xFF, 0xE6, 0xE6, 0xE6, 0xFF, 0x7E, 0x7E, 0x7E, 0xFF, + 0x97, 0x97, 0x97, 0xFF, 0x97, 0x97, 0x97, 0xFF, 0x98, 0x98, 0x98, 0xFF, 0x98, 0x98, 0x98, 0xFF, + 0x67, 0x01, 0x01, 0xFF, 0xED, 0x7B, 0x7B, 0xFF, 0xFA, 0x6A, 0x6A, 0xFF, 0xF8, 0x60, 0x60, 0xFF, + 0xF5, 0x5B, 0x5B, 0xFF, 0xF3, 0x56, 0x56, 0xFF, 0xF1, 0x52, 0x52, 0xFF, 0xEF, 0x4D, 0x4D, 0xFF, + 0xEC, 0x48, 0x48, 0xFF, 0xCD, 0x84, 0x78, 0xFF, 0x77, 0x7C, 0xAE, 0xFF, 0x77, 0x7B, 0xAD, 0xFF, + 0x72, 0x75, 0xA7, 0xFF, 0x6B, 0x6F, 0xA1, 0xFF, 0x67, 0x69, 0x9B, 0xFF, 0x60, 0x63, 0x96, 0xFF, + 0x5E, 0x60, 0x91, 0xFF, 0x7E, 0x5E, 0x82, 0xFF, 0x66, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x5D, 0x5D, 0x5D, 0xFF, 0xF9, 0xF9, 0xF9, 0xFF, 0xE6, 0xE6, 0xE6, 0xFF, 0x99, 0x99, 0x99, 0xFF, + 0xB6, 0xB6, 0xB6, 0xFF, 0xB6, 0xB6, 0xB6, 0xFF, 0xB7, 0xB7, 0xB7, 0xFF, 0xB8, 0xB8, 0xB8, 0xFF, + 0x67, 0x00, 0x00, 0xFF, 0xD8, 0x62, 0x62, 0xFF, 0xF7, 0x6D, 0x6D, 0xFF, 0xF4, 0x58, 0x58, 0xFF, + 0xF1, 0x53, 0x53, 0xFF, 0xEF, 0x4E, 0x4E, 0xFF, 0xED, 0x49, 0x49, 0xFF, 0xEB, 0x44, 0x44, 0xFF, + 0xE8, 0x43, 0x41, 0xFF, 0xC5, 0xE1, 0x8C, 0xFF, 0x8A, 0x93, 0x9F, 0xFF, 0x58, 0x89, 0xC7, 0xFF, + 0x51, 0x82, 0xC1, 0xFF, 0x4B, 0x7C, 0xBA, 0xFF, 0x44, 0x75, 0xB4, 0xFF, 0x3D, 0x6E, 0xAE, 0xFF, + 0x45, 0x72, 0xAD, 0xFF, 0x6A, 0x60, 0x87, 0xFF, 0x67, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x0E, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x59, 0x59, 0x59, 0xFF, 0xF9, 0xF9, 0xF9, 0xFF, 0xE7, 0xE7, 0xE7, 0xFF, 0x7E, 0x7E, 0x7E, 0xFF, + 0x97, 0x97, 0x97, 0xFF, 0x97, 0x97, 0x97, 0xFF, 0x98, 0x98, 0x98, 0xFF, 0x98, 0x98, 0x98, 0xFF, + 0x69, 0x0D, 0x0D, 0xFF, 0xA3, 0x24, 0x24, 0xFF, 0xEF, 0x7B, 0x7B, 0xFF, 0xF1, 0x5A, 0x5A, 0xFF, + 0xEE, 0x4A, 0x4A, 0xFF, 0xEB, 0x45, 0x45, 0xFF, 0xE9, 0x40, 0x40, 0xFF, 0xE7, 0x3C, 0x3C, 0xFF, + 0xD8, 0x6B, 0x4D, 0xFF, 0xAD, 0xE7, 0x73, 0xFF, 0xAB, 0xC3, 0x5F, 0xFF, 0x55, 0x85, 0xC0, 0xFF, + 0x4D, 0x7E, 0xBD, 0xFF, 0x47, 0x78, 0xB7, 0xFF, 0x40, 0x71, 0xB0, 0xFF, 0x43, 0x72, 0xAE, 0xFF, + 0x6B, 0x7A, 0xA6, 0xFF, 0x54, 0x42, 0x67, 0xFF, 0x5C, 0x00, 0x00, 0xE8, 0x00, 0x00, 0x00, 0x16, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x56, 0x56, 0x56, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0xE7, 0xE7, 0xE7, 0xFF, 0x99, 0x99, 0x99, 0xFF, + 0xB6, 0xB6, 0xB6, 0xFF, 0xB6, 0xB6, 0xB6, 0xFF, 0xB7, 0xB7, 0xB7, 0xFF, 0xB8, 0xB8, 0xB8, 0xFF, + 0x77, 0x39, 0x39, 0xFF, 0x7A, 0x09, 0x09, 0xFF, 0xBB, 0x32, 0x32, 0xFF, 0xE8, 0x71, 0x71, 0xFF, + 0xED, 0x61, 0x61, 0xFF, 0xE8, 0x42, 0x42, 0xFF, 0xE5, 0x38, 0x38, 0xFF, 0xE3, 0x33, 0x33, 0xFF, + 0xBA, 0x9F, 0x4E, 0xFF, 0x99, 0xE0, 0x53, 0xFF, 0x8F, 0xDC, 0x43, 0xFF, 0x84, 0x8A, 0x66, 0xFF, + 0x49, 0x7A, 0xB9, 0xFF, 0x47, 0x77, 0xB4, 0xFF, 0x58, 0x82, 0xB9, 0xFF, 0x79, 0x7F, 0xA6, 0xFF, + 0x45, 0x5E, 0x90, 0xFF, 0x65, 0x17, 0x23, 0xFF, 0x3E, 0x00, 0x00, 0xAC, 0x00, 0x00, 0x00, 0x13, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x53, 0x53, 0x53, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0xE6, 0xE6, 0xE6, 0xFF, 0x7E, 0x7E, 0x7E, 0xFF, + 0x97, 0x97, 0x97, 0xFF, 0x97, 0x97, 0x97, 0xFF, 0x98, 0x98, 0x98, 0xFF, 0x98, 0x98, 0x98, 0xFF, + 0x76, 0x6B, 0x6B, 0xFF, 0x6D, 0x0E, 0x0E, 0xFF, 0x88, 0x11, 0x11, 0xFF, 0xB0, 0x23, 0x23, 0xFF, + 0xCA, 0x45, 0x45, 0xFF, 0xE2, 0x66, 0x66, 0xFF, 0xE8, 0x5D, 0x5D, 0xFF, 0xE2, 0x4F, 0x4B, 0xFF, + 0xA1, 0xD6, 0x56, 0xFF, 0x91, 0xDC, 0x47, 0xFF, 0x8B, 0xD9, 0x3C, 0xFF, 0x90, 0xC9, 0x3A, 0xFF, + 0x74, 0x94, 0xBB, 0xFF, 0x84, 0x8E, 0xB4, 0xFF, 0x70, 0x68, 0x8E, 0xFF, 0x3B, 0x5E, 0x92, 0xFF, + 0x60, 0x29, 0x42, 0xFF, 0x5F, 0x00, 0x00, 0xEF, 0x0B, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x4F, 0x4F, 0x4F, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0xE6, 0xE6, 0xE6, 0xFF, 0xE5, 0xE5, 0xE5, 0xFF, + 0xE6, 0xE6, 0xE6, 0xFF, 0xE7, 0xE7, 0xE7, 0xFF, 0xE8, 0xE8, 0xE8, 0xFF, 0xE9, 0xE9, 0xE9, 0xFF, + 0xE6, 0xE6, 0xE6, 0xFF, 0xBD, 0xA9, 0xA9, 0xFF, 0x71, 0x15, 0x15, 0xFF, 0x7B, 0x0C, 0x0C, 0xFF, + 0xA2, 0x1D, 0x1D, 0xFF, 0xAD, 0x1B, 0x1B, 0xFF, 0xB6, 0x27, 0x27, 0xFF, 0xB2, 0x4F, 0x33, 0xFF, + 0x93, 0xA6, 0x40, 0xFF, 0x96, 0xA8, 0x40, 0xFF, 0x91, 0xA7, 0x3C, 0xFF, 0x86, 0x9F, 0x33, 0xFF, + 0x6E, 0x67, 0x5B, 0xFF, 0x37, 0x5C, 0x93, 0xFF, 0x54, 0x4D, 0x75, 0xFF, 0x6B, 0x18, 0x23, 0xFF, + 0x5C, 0x00, 0x00, 0xEB, 0x13, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, + 0x4C, 0x4C, 0x4C, 0xF1, 0xF7, 0xF7, 0xF7, 0xFF, 0xF6, 0xF6, 0xF6, 0xFF, 0xF6, 0xF6, 0xF6, 0xFF, + 0xF6, 0xF6, 0xF6, 0xFF, 0xF7, 0xF7, 0xF7, 0xFF, 0xF7, 0xF7, 0xF7, 0xFF, 0xF7, 0xF7, 0xF7, 0xFF, + 0xF7, 0xF7, 0xF7, 0xFF, 0xF0, 0xF0, 0xF0, 0xFF, 0xD1, 0xC9, 0xC9, 0xFF, 0x90, 0x53, 0x53, 0xFF, + 0x6A, 0x05, 0x05, 0xFF, 0x77, 0x0A, 0x0A, 0xFF, 0x8E, 0x14, 0x14, 0xFF, 0x91, 0x33, 0x1C, 0xFF, + 0x72, 0x87, 0x23, 0xFF, 0x70, 0x90, 0x24, 0xFF, 0x6E, 0x8F, 0x23, 0xFF, 0x73, 0x7C, 0x1F, 0xFF, + 0x70, 0x3C, 0x31, 0xFF, 0x6C, 0x14, 0x1B, 0xFF, 0x6A, 0x05, 0x05, 0xFF, 0x53, 0x16, 0x16, 0xFB, + 0x08, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x22, + 0x19, 0x19, 0x19, 0x78, 0x53, 0x53, 0x53, 0xF6, 0x56, 0x56, 0x56, 0xFF, 0x56, 0x56, 0x56, 0xFF, + 0x56, 0x56, 0x56, 0xFF, 0x56, 0x56, 0x56, 0xFF, 0x56, 0x56, 0x56, 0xFF, 0x56, 0x56, 0x56, 0xFF, + 0x56, 0x56, 0x56, 0xFF, 0x56, 0x56, 0x56, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x4F, 0x4F, 0x4F, 0xFF, + 0x4B, 0x43, 0x43, 0xFF, 0x52, 0x28, 0x28, 0xFF, 0x5A, 0x16, 0x16, 0xFF, 0x61, 0x0A, 0x0A, 0xFF, + 0x65, 0x04, 0x04, 0xFF, 0x67, 0x00, 0x00, 0xFF, 0x65, 0x04, 0x04, 0xFF, 0x60, 0x09, 0x09, 0xFF, + 0x59, 0x15, 0x15, 0xFF, 0x50, 0x26, 0x26, 0xFF, 0x45, 0x3C, 0x3C, 0xF9, 0x15, 0x15, 0x15, 0x93, + 0x00, 0x00, 0x00, 0x3A, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1D, + 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x4A, + 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x4A, + 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x4A, + 0x00, 0x00, 0x00, 0x4C, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x5E, 0x00, 0x00, 0x00, 0x65, + 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x6B, 0x00, 0x00, 0x00, 0x6B, 0x00, 0x00, 0x00, 0x69, + 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x4B, 0x00, 0x00, 0x00, 0x3B, + 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, + 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x16, + 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x16, + 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x16, + 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x17, + 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const struct { + const char *name; + void *data; + int width; + int height; + int stride; +} files[] = { + { "andlabs_16x16test_24june2016.png", dat0, 16, 16, 64 }, + { "andlabs_32x32test_24june2016.png", dat1, 32, 32, 128 }, + { "tango-icon-theme-0.8.90_16x16_x-office-spreadsheet.png", dat2, 16, 16, 64 }, + { "tango-icon-theme-0.8.90_32x32_x-office-spreadsheet.png", dat3, 32, 32, 128 }, +}; + +void appendImageNamed(uiImage *img, const char *name) +{ + int i; + + i = 0; + for (;;) { + if (strcmp(name, files[i].name) == 0) { + uiImageAppend(img, files[i].data, files[i].width, files[i].height, files[i].stride); + return; + } + i++; + } +} + diff --git a/test/images/andlabs_16x16test_24june2016.png b/test/images/andlabs_16x16test_24june2016.png new file mode 100644 index 00000000..a4c27d9a Binary files /dev/null and b/test/images/andlabs_16x16test_24june2016.png differ diff --git a/test/images/andlabs_32x32test_24june2016.png b/test/images/andlabs_32x32test_24june2016.png new file mode 100644 index 00000000..e1c33fcf Binary files /dev/null and b/test/images/andlabs_32x32test_24june2016.png differ diff --git a/test/images/gen.go b/test/images/gen.go new file mode 100644 index 00000000..7ded83e6 --- /dev/null +++ b/test/images/gen.go @@ -0,0 +1,94 @@ +// 25 june 2016 +package main + +import ( + "fmt" + "os" + "image" + "image/draw" + _ "image/png" +) + +type img struct { + filename string + data []byte + width int + height int + stride int +} + +func main() { + if len(os.Args[1:]) == 0 { + panic("no files specified") + } + + images := make([]*img, 0, len(os.Args[1:])) + for _, fn := range os.Args[1:] { + f, err := os.Open(fn) + if err != nil { + panic(err) + } + ii, _, err := image.Decode(f) + if err != nil { + panic(err) + } + f.Close() + + i := image.NewRGBA(ii.Bounds()) + draw.Draw(i, i.Rect, ii, ii.Bounds().Min, draw.Src) + + im := &img{ + filename: fn, + data: i.Pix, + width: i.Rect.Dx(), + height: i.Rect.Dy(), + stride: i.Stride, + } + images = append(images, im) + } + + fmt.Println("// auto-generated by images/gen.go") + fmt.Println("#include \"test.h\"") + fmt.Println() + for i, im := range images { + fmt.Printf("static const uint8_t dat%d[] = {", i) + for j := 0; j < len(im.data); j++ { + if (j % 16) == 0 { + fmt.Printf("\n\t") + } else { + fmt.Printf(" ") + } + fmt.Printf("0x%02X,", im.data[j]) + + } + fmt.Println("\n};") + fmt.Println() + } + fmt.Println("static const struct {") + fmt.Println(" const char *name;") + fmt.Println(" void *data;") + fmt.Println(" int width;") + fmt.Println(" int height;") + fmt.Println(" int stride;") + fmt.Println("} files[] = {") + for i, im := range images { + fmt.Printf(" { %q, dat%d, %d, %d, %d },\n", + im.filename, i, im.width, im.height, im.stride) + } + fmt.Println("};") + fmt.Println() + fmt.Println("void appendImageNamed(uiImage *img, const char *name)") + fmt.Println("{") + fmt.Println(" int i;") + fmt.Println("") + fmt.Println(" i = 0;") + fmt.Println(" for (;;) {") + fmt.Println(" if (strcmp(name, files[i].name) == 0) {") + fmt.Println(" uiImageAppend(img, files[i].data, files[i].width, files[i].height, files[i].stride);") + fmt.Println(" return;") + fmt.Println(" }") + fmt.Println(" i++;") + fmt.Println(" }") + fmt.Println("}") + fmt.Println() +} diff --git a/test/images/tango-icon-theme-0.8.90_16x16_x-office-spreadsheet.png b/test/images/tango-icon-theme-0.8.90_16x16_x-office-spreadsheet.png new file mode 100644 index 00000000..a6b1268d Binary files /dev/null and b/test/images/tango-icon-theme-0.8.90_16x16_x-office-spreadsheet.png differ diff --git a/test/images/tango-icon-theme-0.8.90_32x32_x-office-spreadsheet.png b/test/images/tango-icon-theme-0.8.90_32x32_x-office-spreadsheet.png new file mode 100644 index 00000000..c0ccb7ab Binary files /dev/null and b/test/images/tango-icon-theme-0.8.90_32x32_x-office-spreadsheet.png differ diff --git a/test/main.c b/test/main.c index cc52e8e2..2f66826f 100644 --- a/test/main.c +++ b/test/main.c @@ -45,11 +45,17 @@ int main(int argc, char *argv[]) int i; const char *err; uiWindow *w; - uiBox *page2, *page3, *page4, *page5, *page6, *page7, *page8, *page9, *page10; + uiBox *page2, *page3, *page4, *page5; + uiBox *page6, *page7, *page8, *page9, *page10; + uiBox *page11, *page12, *page13; + uiTab *page14; + uiBox *page15; + uiBox *page16; uiTab *outerTab; uiTab *innerTab; int nomenus = 0; int startspaced = 0; + int steps = 0; newhbox = uiNewHorizontalBox; newvbox = uiNewVerticalBox; @@ -63,7 +69,9 @@ int main(int argc, char *argv[]) else if (strcmp(argv[i], "swaphv") == 0) { newhbox = uiNewVerticalBox; newvbox = uiNewHorizontalBox; - } else { + } else if (strcmp(argv[i], "steps") == 0) + steps = 1; + else { fprintf(stderr, "%s: unrecognized option %s\n", argv[0], argv[i]); return 1; } @@ -80,7 +88,7 @@ int main(int argc, char *argv[]) w = newWindow("Main Window", 320, 240, 1); uiWindowOnClosing(w, onClosing, NULL); - printf("main window %p\n", w); + printf("main window %p\n", (void *) w); uiOnShouldQuit(onShouldQuit, w); @@ -121,7 +129,7 @@ int main(int argc, char *argv[]) page7 = makePage7(); uiTabAppend(innerTab, "Page 7", uiControl(page7)); - page8 = makePage8(); +/* page8 = makePage8(); uiTabAppend(innerTab, "Page 8", uiControl(page8)); page9 = makePage9(); @@ -129,13 +137,44 @@ int main(int argc, char *argv[]) page10 = makePage10(); uiTabAppend(innerTab, "Page 10", uiControl(page10)); +*/ + innerTab = newTab(); + uiTabAppend(outerTab, "Pages 11-15", uiControl(innerTab)); + +// page11 = makePage11(); +// uiTabAppend(innerTab, "Page 11", uiControl(page11)); + + page12 = makePage12(); + uiTabAppend(innerTab, "Page 12", uiControl(page12)); + + page13 = makePage13(); + uiTabAppend(innerTab, "Page 13", uiControl(page13)); + + page14 = makePage14(); + uiTabAppend(innerTab, "Page 14", uiControl(page14)); + + page15 = makePage15(w); + uiTabAppend(innerTab, "Page 15", uiControl(page15)); + + innerTab = newTab(); + uiTabAppend(outerTab, "Pages 16-?", uiControl(innerTab)); + + page16 = makePage16(); + uiTabAppend(innerTab, "Page 16", uiControl(page16)); if (startspaced) setSpaced(1); uiControlShow(uiControl(w)); - uiMain(); + if (!steps) + uiMain(); + else { + uiMainSteps(); + while (uiMainStep(1)) + ; + } printf("after uiMain()\n"); + freePage16(); uiUninit(); printf("after uiUninit()\n"); return 0; diff --git a/test/menus.c b/test/menus.c index 8c7e0d02..87ff80a3 100644 --- a/test/menus.c +++ b/test/menus.c @@ -47,7 +47,7 @@ static void forceOff(uiMenuItem *item, uiWindow *w, void *data) static void whatWindow(uiMenuItem *item, uiWindow *w, void *data) { - printf("menu item clicked on window %p\n", w); + printf("menu item clicked on window %p\n", (void *) w); } void initMenus(void) @@ -94,10 +94,10 @@ void initMenus(void) multiMenu = uiNewMenu("Multi"); uiMenuAppendSeparator(multiMenu); uiMenuAppendSeparator(multiMenu); - uiMenuAppendItem(multiMenu, "Item"); + uiMenuAppendItem(multiMenu, "Item && Item && Item"); uiMenuAppendSeparator(multiMenu); uiMenuAppendSeparator(multiMenu); - uiMenuAppendItem(multiMenu, "Item"); + uiMenuAppendItem(multiMenu, "Item __ Item __ Item"); uiMenuAppendSeparator(multiMenu); uiMenuAppendSeparator(multiMenu); diff --git a/test/page10.c b/test/page10.c index 4af59cbc..d7f26a73 100644 --- a/test/page10.c +++ b/test/page10.c @@ -1,14 +1,12 @@ // 22 december 2015 #include "test.h" -// TODO figure out how the various backends handle non-BMP characters/surrogate pairs -// use: F0 90 8C 88 (surrogates D800 DF08) - static uiEntry *textString; static uiFontButton *textFontButton; +static uiColorButton *textColorButton; static uiEntry *textWidth; static uiButton *textApply; -static uiCheckbox *addLeading; +static uiCheckbox *noZ; static uiArea *textArea; static uiAreaHandler textAreaHandler; @@ -27,6 +25,10 @@ static void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *dp) { uiDrawTextFont *font; uiDrawTextLayout *layout; + double r, g, b, al; + char surrogates[1 + 4 + 1 + 1]; + char composed[2 + 2 + 2 + 3 + 2 + 1]; + double width, height; font = uiFontButtonFont(textFontButton); @@ -37,7 +39,54 @@ static void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *dp) uiDrawTextLayoutSetColor(layout, 8, 14, 1, 0, 0.5, 0.5); + uiColorButtonColor(textColorButton, &r, &g, &b, &al); + uiDrawTextLayoutSetColor(layout, + 14, 18, + r, g, b, al); uiDrawText(dp->Context, 10, 10, layout); + uiDrawTextLayoutExtents(layout, &width, &height); + uiDrawFreeTextLayout(layout); + + surrogates[0] = 'x'; + surrogates[1] = 0xF0; // surrogates D800 DF08 + surrogates[2] = 0x90; + surrogates[3] = 0x8C; + surrogates[4] = 0x88; + surrogates[5] = 'y'; + surrogates[6] = '\0'; + + layout = uiDrawNewTextLayout(surrogates, font, -1); + uiDrawTextLayoutSetColor(layout, + 1, 2, + 1, 0, 0.5, 0.5); + uiDrawText(dp->Context, 10, 10 + height, layout); + uiDrawFreeTextLayout(layout); + + composed[0] = 'z'; + composed[1] = 'z'; + composed[2] = 0xC3; // 2 + composed[3] = 0xA9; + composed[4] = 'z'; + composed[5] = 'z'; + composed[6] = 0x65; // 5 + composed[7] = 0xCC; + composed[8] = 0x81; + composed[9] = 'z'; + composed[10] = 'z'; + composed[11] = '\0'; + + layout = uiDrawNewTextLayout(composed, font, -1); + uiDrawTextLayoutSetColor(layout, + 2, 3, + 1, 0, 0.5, 0.5); + uiDrawTextLayoutSetColor(layout, + 5, 6, + 1, 0, 0.5, 0.5); + if (!uiCheckboxChecked(noZ)) + uiDrawTextLayoutSetColor(layout, + 6, 7, + 0.5, 0, 1, 0.5); + uiDrawText(dp->Context, 10, 10 + height + height, layout); uiDrawFreeTextLayout(layout); uiDrawFreeTextFont(font); @@ -69,7 +118,12 @@ static void onFontChanged(uiFontButton *b, void *data) uiAreaQueueRedrawAll(textArea); } -static void onTextApply(uiButton *b, void *data) +static void onColorChanged(uiColorButton *b, void *data) +{ + uiAreaQueueRedrawAll(textArea); +} + +static void onNoZ(uiCheckbox *b, void *data) { uiAreaQueueRedrawAll(textArea); } @@ -95,20 +149,23 @@ uiBox *makePage10(void) uiFontButtonOnChanged(textFontButton, onFontChanged, NULL); uiBoxAppend(hbox, uiControl(textFontButton), 1); + textColorButton = uiNewColorButton(); + uiColorButtonOnChanged(textColorButton, onColorChanged, NULL); + uiBoxAppend(hbox, uiControl(textColorButton), 1); + hbox = newHorizontalBox(); uiBoxAppend(vbox, uiControl(hbox), 0); textApply = uiNewButton("Apply"); - uiButtonOnClicked(textApply, onTextApply, NULL); uiBoxAppend(hbox, uiControl(textApply), 1); textWidth = uiNewEntry(); uiEntrySetText(textWidth, "-1"); uiBoxAppend(hbox, uiControl(textWidth), 1); - addLeading = uiNewCheckbox("Add Leading"); - uiCheckboxSetChecked(addLeading, 1); - uiBoxAppend(hbox, uiControl(addLeading), 0); + noZ = uiNewCheckbox("No Z Color"); + uiCheckboxOnToggled(noZ, onNoZ, NULL); + uiBoxAppend(hbox, uiControl(noZ), 0); textAreaHandler.Draw = handlerDraw; textAreaHandler.MouseEvent = handlerMouseEvent; @@ -122,6 +179,7 @@ uiBox *makePage10(void) hbox = newHorizontalBox(); uiBoxAppend(vbox, uiControl(hbox), 0); uiBoxAppend(hbox, uiControl(uiNewFontButton()), 1); + uiBoxAppend(hbox, uiControl(uiNewColorButton()), 1); return page10; } diff --git a/test/page11.c b/test/page11.c new file mode 100644 index 00000000..02ad213a --- /dev/null +++ b/test/page11.c @@ -0,0 +1,54 @@ +// 14 may 2016 +#include "test.h" + +// TODO add a test for childless windows +// TODO add tests for contianers with all controls hidden + +static uiGroup *newg(const char *n, int s) +{ + uiGroup *g; + + g = uiNewGroup(n); + if (s) + uiGroupSetChild(g, NULL); + return g; +} + +static uiTab *newt(int tt) +{ + uiTab *t; + + t = uiNewTab(); + if (tt) + uiTabAppend(t, "Test", NULL); + return t; +} + +uiBox *makePage11(void) +{ + uiBox *page11; + uiBox *ns; + uiBox *s; + + page11 = newHorizontalBox(); + + ns = newVerticalBox(); + uiBoxAppend(ns, uiControl(newg("", 0)), 0); + uiBoxAppend(ns, uiControl(newg("", 1)), 0); + uiBoxAppend(ns, uiControl(newg("Group", 0)), 0); + uiBoxAppend(ns, uiControl(newg("Group", 1)), 0); + uiBoxAppend(ns, uiControl(newt(0)), 0); + uiBoxAppend(ns, uiControl(newt(1)), 0); + uiBoxAppend(page11, uiControl(ns), 1); + + s = newVerticalBox(); + uiBoxAppend(s, uiControl(newg("", 0)), 1); + uiBoxAppend(s, uiControl(newg("", 1)), 1); + uiBoxAppend(s, uiControl(newg("Group", 0)), 1); + uiBoxAppend(s, uiControl(newg("Group", 1)), 1); + uiBoxAppend(s, uiControl(newt(0)), 1); + uiBoxAppend(s, uiControl(newt(1)), 1); + uiBoxAppend(page11, uiControl(s), 1); + + return page11; +} diff --git a/test/page12.c b/test/page12.c new file mode 100644 index 00000000..5a8e963f --- /dev/null +++ b/test/page12.c @@ -0,0 +1,60 @@ +// 22 may 2016 +#include "test.h" + +// TODO OS X: if the hboxes are empty, the text views don't show up + +static void meChanged(uiMultilineEntry *e, void *data) +{ + printf("%s changed\n", (char *) data); +} + +static void setClicked(uiButton *b, void *data) +{ + uiMultilineEntrySetText(uiMultilineEntry(data), "set"); +} + +static void appendClicked(uiButton *b, void *data) +{ + uiMultilineEntryAppend(uiMultilineEntry(data), "append\n"); +} + +static uiBox *half(uiMultilineEntry *(*mk)(void), const char *which) +{ + uiBox *vbox, *hbox; + uiMultilineEntry *me; + uiButton *button; + + vbox = newVerticalBox(); + + me = (*mk)(); + uiMultilineEntryOnChanged(me, meChanged, (void *) which); + uiBoxAppend(vbox, uiControl(me), 1); + + hbox = newHorizontalBox(); + uiBoxAppend(vbox, uiControl(hbox), 0); + + button = uiNewButton("Set"); + uiButtonOnClicked(button, setClicked, me); + uiBoxAppend(hbox, uiControl(button), 0); + + button = uiNewButton("Append"); + uiButtonOnClicked(button, appendClicked, me); + uiBoxAppend(hbox, uiControl(button), 0); + + return vbox; +} + +uiBox *makePage12(void) +{ + uiBox *page12; + uiBox *b; + + page12 = newHorizontalBox(); + + b = half(uiNewMultilineEntry, "wrap"); + uiBoxAppend(page12, uiControl(b), 1); + b = half(uiNewNonWrappingMultilineEntry, "no wrap"); + uiBoxAppend(page12, uiControl(b), 1); + + return page12; +} diff --git a/test/page13.c b/test/page13.c new file mode 100644 index 00000000..5e6fd52c --- /dev/null +++ b/test/page13.c @@ -0,0 +1,157 @@ +// 28 may 2016 +#include "test.h" + +static int winClose(uiWindow *w, void *data) +{ + return 1; +} + +static void openTestWindow(uiBox *(*mkf)(void)) +{ + uiWindow *w; + uiBox *b; + uiCombobox *c; + uiEditableCombobox *e; + uiRadioButtons *r; + + w = uiNewWindow("Test", 100, 100, 0); + uiWindowOnClosing(w, winClose, NULL); + uiWindowSetMargined(w, 1); + b = (*mkf)(); + uiWindowSetChild(w, uiControl(b)); + +#define BA(x) uiBoxAppend(b, uiControl(x), 0) + BA(uiNewButton("")); + BA(uiNewCheckbox("")); + BA(uiNewEntry()); + BA(uiNewLabel("")); + BA(uiNewSpinbox(0, 100)); + BA(uiNewProgressBar()); + BA(uiNewSlider(0, 100)); + BA(uiNewHorizontalSeparator()); + c = uiNewCombobox(); + uiComboboxAppend(c, ""); + BA(c); + e = uiNewEditableCombobox(); + uiEditableComboboxAppend(e, ""); + BA(e); + r = uiNewRadioButtons(); + uiRadioButtonsAppend(r, ""); + BA(r); + BA(uiNewDateTimePicker()); + BA(uiNewDatePicker()); + BA(uiNewTimePicker()); + BA(uiNewMultilineEntry()); + // TODO nonscrolling and scrolling areas? + BA(uiNewFontButton()); + BA(uiNewColorButton()); + BA(uiNewPasswordEntry()); + BA(uiNewSearchEntry()); + BA(uiNewVerticalSeparator()); + + uiControlShow(uiControl(w)); +} + +static void buttonClicked(uiButton *b, void *data) +{ + openTestWindow((uiBox *(*)(void)) data); +} + +static void entryChanged(uiEntry *e, void *data) +{ + char *text; + + text = uiEntryText(e); + printf("%s entry changed: %s\n", (const char *) data, text); + uiFreeText(text); +} + +static void showHide(uiButton *b, void *data) +{ + uiControl *c = uiControl(data); + + if (uiControlVisible(c)) + uiControlHide(c); + else + uiControlShow(c); +} + +static void setIndeterminate(uiButton *b, void *data) +{ + uiProgressBar *p = uiProgressBar(data); + int value; + + value = uiProgressBarValue(p); + if (value == -1) + value = 50; + else + value = -1; + uiProgressBarSetValue(p, value); +} + +static void deleteFirst(uiButton *b, void *data) +{ + uiForm *f = uiForm(data); + + uiFormDelete(f, 0); +} + +uiBox *makePage13(void) +{ + uiBox *page13; + uiRadioButtons *rb; + uiButton *b; + uiForm *f; + uiEntry *e; + uiProgressBar *p; + + page13 = newVerticalBox(); + + rb = uiNewRadioButtons(); + uiRadioButtonsAppend(rb, "Item 1"); + uiRadioButtonsAppend(rb, "Item 2"); + uiRadioButtonsAppend(rb, "Item 3"); + uiBoxAppend(page13, uiControl(rb), 0); + + rb = uiNewRadioButtons(); + uiRadioButtonsAppend(rb, "Item A"); + uiRadioButtonsAppend(rb, "Item B"); + uiBoxAppend(page13, uiControl(rb), 0); + + b = uiNewButton("Horizontal"); + uiButtonOnClicked(b, buttonClicked, uiNewHorizontalBox); + uiBoxAppend(page13, uiControl(b), 0); + + b = uiNewButton("Vertical"); + uiButtonOnClicked(b, buttonClicked, uiNewVerticalBox); + uiBoxAppend(page13, uiControl(b), 0); + + f = newForm(); + + e = uiNewPasswordEntry(); + uiEntryOnChanged(e, entryChanged, "password"); + uiFormAppend(f, "Password Entry", uiControl(e), 0); + + e = uiNewSearchEntry(); + uiEntryOnChanged(e, entryChanged, "search"); + uiFormAppend(f, "Search Box", uiControl(e), 0); + + uiFormAppend(f, "MLE", uiControl(uiNewMultilineEntry()), 1); + + p = uiNewProgressBar(); + uiProgressBarSetValue(p, 50); + uiBoxAppend(page13, uiControl(p), 0); + b = uiNewButton("Toggle Indeterminate"); + uiButtonOnClicked(b, setIndeterminate, p); + uiBoxAppend(page13, uiControl(b), 0); + + b = uiNewButton("Show/Hide"); + uiButtonOnClicked(b, showHide, e); + uiBoxAppend(page13, uiControl(b), 0); + b = uiNewButton("Delete First"); + uiButtonOnClicked(b, deleteFirst, f); + uiBoxAppend(page13, uiControl(b), 0); + uiBoxAppend(page13, uiControl(f), 1); + + return page13; +} diff --git a/test/page14.c b/test/page14.c new file mode 100644 index 00000000..880534ce --- /dev/null +++ b/test/page14.c @@ -0,0 +1,350 @@ +// 9 june 2016 +#include "test.h" + +// TODOs: +// - GTK+ - make all expanding controls the same size, to match the other OSs? will they match the other OSs? + +enum { + red, + green, + blue, + yellow, + white, + magenta, + orange, + purple, + cyan, +}; + +static const struct { + double r; + double g; + double b; +} colors[] = { + { 1, 0, 0 }, + { 0, 0.5, 0 }, + { 0, 0, 1 }, + { 1, 1, 0 }, + { 1, 1, 1 }, + { 1, 0, 1 }, + { 1, 0.65, 0 }, + { 0.5, 0, 0.5 }, + { 0, 1, 1 }, +}; + +static uiControl *testControl(const char *label, int color) +{ + uiColorButton *b; + + b = uiNewColorButton(); + uiColorButtonSetColor(b, colors[color].r, colors[color].g, colors[color].b, 1.0); + return uiControl(b); +} + +static uiControl *simpleGrid(void) +{ + uiGrid *g; + uiControl *t4; + + g = newGrid(); + uiGridAppend(g, testControl("1", red), + 0, 0, 1, 1, + 0, uiAlignFill, 0, uiAlignFill); + uiGridAppend(g, testControl("2", green), + 1, 0, 1, 1, + 0, uiAlignFill, 0, uiAlignFill); + uiGridAppend(g, testControl("3", blue), + 2, 0, 1, 1, + 0, uiAlignFill, 0, uiAlignFill); + t4 = testControl("4", green); + uiGridAppend(g, t4, + 0, 1, 1, 1, + 0, uiAlignFill, 1, uiAlignFill); + uiGridInsertAt(g, testControl("5", blue), + t4, uiAtTrailing, 2, 1, + 0, uiAlignFill, 0, uiAlignFill); + uiGridAppend(g, testControl("6", yellow), + -1, 0, 1, 2, + 1, uiAlignFill, 0, uiAlignFill); + return uiControl(g); +} + +static uiControl *boxComparison(void) +{ + uiBox *vbox; + uiGrid *g; + uiBox *hbox; + + vbox = newVerticalBox(); + uiBoxAppend(vbox, uiControl(uiNewLabel("Above")), 0); + uiBoxAppend(vbox, uiControl(uiNewHorizontalSeparator()), 0); + + hbox = newHorizontalBox(); + uiBoxAppend(vbox, uiControl(hbox), 0); + uiBoxAppend(hbox, testControl("1", white), 0); + uiBoxAppend(hbox, uiControl(uiNewLabel("A label")), 1); + uiBoxAppend(hbox, testControl("2", green), 0); + uiBoxAppend(hbox, uiControl(uiNewLabel("Another label")), 1); + uiBoxAppend(hbox, testControl("3", red), 0); + + uiBoxAppend(vbox, uiControl(uiNewHorizontalSeparator()), 0); + + g = newGrid(); + uiBoxAppend(vbox, uiControl(g), 0); + uiGridAppend(g, testControl("1", white), + 0, 0, 1, 1, + 0, uiAlignFill, 0, uiAlignFill); + uiGridAppend(g, uiControl(uiNewLabel("A label")), + 1, 0, 1, 1, + 1, uiAlignFill, 0, uiAlignFill); + uiGridAppend(g, testControl("2", green), + 2, 0, 1, 1, + 0, uiAlignFill, 0, uiAlignFill); + uiGridAppend(g, uiControl(uiNewLabel("Another label")), + 3, 0, 1, 1, + 1, uiAlignFill, 0, uiAlignFill); + uiGridAppend(g, testControl("3", red), + 4, 0, 1, 1, + 0, uiAlignFill, 0, uiAlignFill); + + uiBoxAppend(vbox, uiControl(uiNewHorizontalSeparator()), 0); + uiBoxAppend(vbox, uiControl(uiNewLabel("Below")), 0); + return uiControl(vbox); +} + +static uiControl *emptyLine(void) +{ + uiGrid *g; + + g = newGrid(); + uiGridAppend(g, testControl("(0, 0)", red), + 0, 0, 1, 1, + 1, uiAlignFill, 1, uiAlignFill); + uiGridAppend(g, testControl("(0, 1)", blue), + 0, 1, 1, 1, + 0, uiAlignFill, 0, uiAlignFill); + uiGridAppend(g, testControl("(10, 0)", green), + 10, 0, 1, 1, + 0, uiAlignFill, 0, uiAlignFill); + uiGridAppend(g, testControl("(10, 1)", magenta), + 10, 1, 1, 1, + 0, uiAlignFill, 0, uiAlignFill); + return uiControl(g); +} + +static uiControl *emptyGrid(void) +{ + uiGrid *g; + uiControl *t; + + g = newGrid(); + t = testControl("(0, 0)", red); + uiGridAppend(g, t, + 0, 0, 1, 1, + 1, uiAlignFill, 1, uiAlignFill); + uiControlHide(t); + return uiControl(g); +} + +// TODO insert (need specialized insert/delete) + +static uiControl *spanningGrid(void) +{ + uiGrid *g; + + g = newGrid(); + uiGridAppend(g, testControl("0", blue), + 0, 4, 4, 1, + 1, uiAlignFill, 0, uiAlignFill); + uiGridAppend(g, testControl("1", green), + 4, 0, 1, 4, + 0, uiAlignFill, 1, uiAlignFill); + uiGridAppend(g, testControl("2", red), + 3, 3, 1, 1, + 1, uiAlignFill, 1, uiAlignFill); + uiGridAppend(g, testControl("3", yellow), + 0, 3, 2, 1, + 0, uiAlignFill, 0, uiAlignFill); + uiGridAppend(g, testControl("4", orange), + 3, 0, 1, 2, + 0, uiAlignFill, 0, uiAlignFill); + uiGridAppend(g, testControl("5", purple), + 1, 1, 1, 1, + 0, uiAlignFill, 0, uiAlignFill); + uiGridAppend(g, testControl("6", white), + 0, 1, 1, 1, + 0, uiAlignFill, 0, uiAlignFill); + uiGridAppend(g, testControl("7", cyan), + 1, 0, 1, 1, + 0, uiAlignFill, 0, uiAlignFill); + return uiControl(g); +} + +// TODO make non-global +static uiButton *hideOne, *one, *showOne; + +static void onHideOne(uiButton *b, void *data) +{ + uiControlHide(uiControl(one)); +} + +static void onShowOne(uiButton *b, void *data) +{ + uiControlShow(uiControl(one)); +} + +static void onHideAll(uiButton *b, void *data) +{ + uiControlHide(uiControl(hideOne)); + uiControlHide(uiControl(one)); + uiControlHide(uiControl(showOne)); +} + +static void onShowAll(uiButton *b, void *data) +{ + uiControlShow(uiControl(hideOne)); + uiControlShow(uiControl(one)); + uiControlShow(uiControl(showOne)); +} + +#define AT(x) static void onInsert ## x(uiButton *b, void *data) \ + { \ + uiGrid *g = uiGrid(data); \ + uiGridInsertAt(g, uiControl(uiNewButton("Button")), \ + uiControl(b), uiAt ## x, 1, 1, \ + 0, uiAlignFill, 0, uiAlignFill); \ + } +AT(Leading) +AT(Top) +AT(Trailing) +AT(Bottom) + +static uiControl *assorted(void) +{ + uiGrid *outergrid; + uiGrid *innergrid; + uiButton *b; + + outergrid = newGrid(); + + innergrid = newGrid(); + one = uiNewButton("Test"); + uiGridAppend(innergrid, uiControl(one), + 1, 1, 1, 1, + 0, uiAlignFill, 0, uiAlignFill); + hideOne = uiNewButton("Hide One"); + uiButtonOnClicked(hideOne, onHideOne, NULL); + uiGridAppend(innergrid, uiControl(hideOne), + 0, 1, 1, 1, + 0, uiAlignFill, 0, uiAlignFill); + showOne = uiNewButton("Show One"); + uiButtonOnClicked(showOne, onShowOne, NULL); + uiGridAppend(innergrid, uiControl(showOne), + 2, 1, 1, 1, + 0, uiAlignFill, 0, uiAlignFill); + b = uiNewButton("Hide All"); + uiButtonOnClicked(b, onHideAll, NULL); + uiGridAppend(innergrid, uiControl(b), + 1, 0, 1, 1, + 0, uiAlignFill, 0, uiAlignFill); + b = uiNewButton("Show All"); + uiButtonOnClicked(b, onShowAll, NULL); + uiGridAppend(innergrid, uiControl(b), + 1, 2, 1, 1, + 0, uiAlignFill, 0, uiAlignFill); + uiGridAppend(outergrid, uiControl(innergrid), + 0, 0, 1, 1, + 1, uiAlignFill, 1, uiAlignFill); + + innergrid = newGrid(); + b = uiNewButton("Insert Trailing"); + uiButtonOnClicked(b, onInsertTrailing, innergrid); + uiGridAppend(innergrid, uiControl(b), + 0, 0, 1, 1, + 1, uiAlignFill, 0, uiAlignFill); + b = uiNewButton("Insert Bottom"); + uiButtonOnClicked(b, onInsertBottom, innergrid); + uiGridAppend(innergrid, uiControl(b), + 1, 0, 1, 1, + 1, uiAlignFill, 0, uiAlignFill); + b = uiNewButton("Insert Leading"); + uiButtonOnClicked(b, onInsertLeading, innergrid); + uiGridAppend(innergrid, uiControl(b), + 1, 1, 1, 1, + 1, uiAlignFill, 0, uiAlignFill); + b = uiNewButton("Insert Top"); + uiButtonOnClicked(b, onInsertTop, innergrid); + uiGridAppend(innergrid, uiControl(b), + 0, 1, 1, 1, + 1, uiAlignFill, 0, uiAlignFill); + uiGridAppend(outergrid, uiControl(innergrid), + 1, 0, 1, 1, + 1, uiAlignFill, 1, uiAlignFill); + + innergrid = newGrid(); + uiGridAppend(innergrid, uiControl(uiNewColorButton()), + 0, 0, 1, 1, + 1, uiAlignFill, 0, uiAlignFill); + uiGridAppend(innergrid, uiControl(uiNewColorButton()), + 0, 1, 1, 1, + 1, uiAlignStart, 0, uiAlignFill); + uiGridAppend(innergrid, uiControl(uiNewColorButton()), + 0, 2, 1, 1, + 1, uiAlignCenter, 0, uiAlignFill); + uiGridAppend(innergrid, uiControl(uiNewColorButton()), + 0, 3, 1, 1, + 1, uiAlignEnd, 0, uiAlignFill); + uiGridAppend(outergrid, uiControl(innergrid), + 0, 1, 1, 1, + 1, uiAlignFill, 1, uiAlignFill); + + // TODO with only this, wrong size on OS X — expand sizing thing? + innergrid = newGrid(); + uiGridAppend(innergrid, uiControl(uiNewColorButton()), + 0, 0, 1, 1, + 0, uiAlignFill, 1, uiAlignFill); + uiGridAppend(innergrid, uiControl(uiNewColorButton()), + 1, 0, 1, 1, + 0, uiAlignFill, 1, uiAlignStart); + uiGridAppend(innergrid, uiControl(uiNewColorButton()), + 2, 0, 1, 1, + 0, uiAlignFill, 1, uiAlignCenter); + uiGridAppend(innergrid, uiControl(uiNewColorButton()), + 3, 0, 1, 1, + 0, uiAlignFill, 1, uiAlignEnd); + uiGridAppend(outergrid, uiControl(innergrid), + 1, 1, 1, 1, + 1, uiAlignFill, 1, uiAlignFill); + + return uiControl(outergrid); +} + +static const struct { + const char *name; + uiControl *(*f)(void); +} pages[] = { + // based on GTK+ test/testgrid.c + { "Simple Grid", simpleGrid }, + { "Box Comparison", boxComparison }, + { "Empty Line", emptyLine }, + { "Empty Grid", emptyGrid }, + { "Spanning Grid", spanningGrid }, + // my own + { "Assorted", assorted }, + { NULL, NULL }, +}; + +uiTab *makePage14(void) +{ + uiTab *page14; + int i; + + page14 = newTab(); + + for (i = 0; pages[i].name != NULL; i++) + uiTabAppend(page14, + pages[i].name, + (*(pages[i].f))()); + + return page14; +} diff --git a/test/page15.c b/test/page15.c new file mode 100644 index 00000000..e703bee2 --- /dev/null +++ b/test/page15.c @@ -0,0 +1,260 @@ +// 15 june 2016 +#include "test.h" + +static uiAreaHandler borderAH; +static int borderAHInit = 0; +static double lastx = -1, lasty = -1; + +struct trect { + double left; + double top; + double right; + double bottom; + int in; +}; + +#define tsetrect(re, l, t, r, b) re.left = l; re.top = t; re.right = r; re.bottom = b; re.in = lastx >= re.left && lastx < re.right && lasty >= re.top && lasty < re.bottom + +struct tareas { + struct trect move; + struct trect alsomove; + struct trect leftresize; + struct trect topresize; + struct trect rightresize; + struct trect bottomresize; + struct trect topleftresize; + struct trect toprightresize; + struct trect bottomleftresize; + struct trect bottomrightresize; + struct trect close; +}; + +static void filltareas(double awid, double aht, struct tareas *ta) +{ + tsetrect(ta->move, 20, 20, awid - 20, 20 + 30); + tsetrect(ta->alsomove, 30, 200, 100, 270); + tsetrect(ta->leftresize, 5, 20, 15, aht - 20); + tsetrect(ta->topresize, 20, 5, awid - 20, 15); + tsetrect(ta->rightresize, awid - 15, 20, awid - 5, aht - 20); + tsetrect(ta->bottomresize, 20, aht - 15, awid - 20, aht - 5); + tsetrect(ta->topleftresize, 5, 5, 15, 15); + tsetrect(ta->toprightresize, awid - 15, 5, awid - 5, 15); + tsetrect(ta->bottomleftresize, 5, aht - 15, 15, aht - 5); + tsetrect(ta->bottomrightresize, awid - 15, aht - 15, awid - 5, aht - 5); + tsetrect(ta->close, 130, 200, 200, 270); +} + +static void drawtrect(uiDrawContext *c, struct trect tr, double r, double g, double bl) +{ + uiDrawPath *p; + uiDrawBrush b; + + memset(&b, 0, sizeof (uiDrawBrush)); + b.Type = uiDrawBrushTypeSolid; + b.R = r; + b.G = g; + b.B = bl; + b.A = 1.0; + if (tr.in) { + b.R += b.R * 0.75; + b.G += b.G * 0.75; + b.B += b.B * 0.75; + } + p = uiDrawNewPath(uiDrawFillModeWinding); + uiDrawPathAddRectangle(p, + tr.left, + tr.top, + tr.right - tr.left, + tr.bottom - tr.top); + uiDrawPathEnd(p); + uiDrawFill(c, p, &b); + uiDrawFreePath(p); +} + +static void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *p) +{ + struct tareas ta; + + filltareas(p->AreaWidth, p->AreaHeight, &ta); + drawtrect(p->Context, ta.move, 0, 0.5, 0); + drawtrect(p->Context, ta.alsomove, 0, 0.5, 0); + drawtrect(p->Context, ta.leftresize, 0, 0, 0.5); + drawtrect(p->Context, ta.topresize, 0, 0, 0.5); + drawtrect(p->Context, ta.rightresize, 0, 0, 0.5); + drawtrect(p->Context, ta.bottomresize, 0, 0, 0.5); + drawtrect(p->Context, ta.topleftresize, 0, 0.5, 0.5); + drawtrect(p->Context, ta.toprightresize, 0, 0.5, 0.5); + drawtrect(p->Context, ta.bottomleftresize, 0, 0.5, 0.5); + drawtrect(p->Context, ta.bottomrightresize, 0, 0.5, 0.5); + drawtrect(p->Context, ta.close, 0.5, 0, 0); + + // TODO add current position prints here +} + +static void handlerMouseEvent(uiAreaHandler *a, uiArea *area, uiAreaMouseEvent *e) +{ + struct tareas ta; + + lastx = e->X; + lasty = e->Y; + filltareas(e->AreaWidth, e->AreaHeight, &ta); + // redraw our highlighted rect + uiAreaQueueRedrawAll(area); + if (e->Down != 1) + return; + if (ta.move.in || ta.alsomove.in) { + uiAreaBeginUserWindowMove(area); + return; + } +#define resize(cond, edge) if (cond) { uiAreaBeginUserWindowResize(area, edge); return; } + resize(ta.leftresize.in, uiWindowResizeEdgeLeft) + resize(ta.topresize.in, uiWindowResizeEdgeTop) + resize(ta.rightresize.in, uiWindowResizeEdgeRight) + resize(ta.bottomresize.in, uiWindowResizeEdgeBottom) + resize(ta.topleftresize.in, uiWindowResizeEdgeTopLeft) + resize(ta.toprightresize.in, uiWindowResizeEdgeTopRight) + resize(ta.bottomleftresize.in, uiWindowResizeEdgeBottomLeft) + resize(ta.bottomrightresize.in, uiWindowResizeEdgeBottomRight) + if (ta.close.in) { + // TODO + return; + } +} + +static void handlerMouseCrossed(uiAreaHandler *ah, uiArea *a, int left) +{ +} + +static void handlerDragBroken(uiAreaHandler *ah, uiArea *a) +{ +} + +static int handlerKeyEvent(uiAreaHandler *ah, uiArea *a, uiAreaKeyEvent *e) +{ + return 0; +} + +static void borderWindowOpen(uiButton *b, void *data) +{ + uiWindow *w; + uiArea *a; + + if (!borderAHInit) { + borderAH.Draw = handlerDraw; + borderAH.MouseEvent = handlerMouseEvent; + borderAH.MouseCrossed = handlerMouseCrossed; + borderAH.DragBroken = handlerDragBroken; + borderAH.KeyEvent = handlerKeyEvent; + borderAHInit = 1; + } + + w = uiNewWindow("Border Resize Test", 300, 500, 0); + uiWindowSetBorderless(w, 1); + + a = uiNewArea(&borderAH); +// uiWindowSetChild(w, uiControl(a)); +{uiBox *b; +b=uiNewHorizontalBox(); +uiBoxAppend(b,uiControl(a),1); +uiWindowSetChild(w,uiControl(b));} +//TODO why is this hack needed? GTK+ issue + + uiControlShow(uiControl(w)); +} + +static uiSpinbox *width, *height; +static uiCheckbox *fullscreen; + +static void sizeWidth(uiSpinbox *s, void *data) +{ + uiWindow *w = uiWindow(data); + int xp, yp; + + uiWindowContentSize(w, &xp, &yp); + xp = uiSpinboxValue(width); + uiWindowSetContentSize(w, xp, yp); +} + +static void sizeHeight(uiSpinbox *s, void *data) +{ + uiWindow *w = uiWindow(data); + int xp, yp; + + uiWindowContentSize(w, &xp, &yp); + yp = uiSpinboxValue(height); + uiWindowSetContentSize(w, xp, yp); +} + +static void updatesize(uiWindow *w) +{ + int xp, yp; + + uiWindowContentSize(w, &xp, &yp); + uiSpinboxSetValue(width, xp); + uiSpinboxSetValue(height, yp); + // TODO on OS X this is updated AFTER sending the size change, not before + uiCheckboxSetChecked(fullscreen, uiWindowFullscreen(w)); +} + +void onSize(uiWindow *w, void *data) +{ + printf("size\n"); + updatesize(w); +} + +void setFullscreen(uiCheckbox *cb, void *data) +{ + uiWindow *w = uiWindow(data); + + uiWindowSetFullscreen(w, uiCheckboxChecked(fullscreen)); + updatesize(w); +} + +static void borderless(uiCheckbox *c, void *data) +{ + uiWindow *w = uiWindow(data); + + uiWindowSetBorderless(w, uiCheckboxChecked(c)); +} + +uiBox *makePage15(uiWindow *w) +{ + uiBox *page15; + uiBox *hbox; + uiButton *button; + uiCheckbox *checkbox; + + page15 = newVerticalBox(); + + hbox = newHorizontalBox(); + uiBoxAppend(page15, uiControl(hbox), 0); + + uiBoxAppend(hbox, uiControl(uiNewLabel("Size")), 0); + width = uiNewSpinbox(INT_MIN, INT_MAX); + uiBoxAppend(hbox, uiControl(width), 1); + height = uiNewSpinbox(INT_MIN, INT_MAX); + uiBoxAppend(hbox, uiControl(height), 1); + fullscreen = uiNewCheckbox("Fullscreen"); + uiBoxAppend(hbox, uiControl(fullscreen), 0); + + uiSpinboxOnChanged(width, sizeWidth, w); + uiSpinboxOnChanged(height, sizeHeight, w); + uiCheckboxOnToggled(fullscreen, setFullscreen, w); + uiWindowOnContentSizeChanged(w, onSize, NULL); + updatesize(w); + + checkbox = uiNewCheckbox("Borderless"); + uiCheckboxOnToggled(checkbox, borderless, w); + uiBoxAppend(page15, uiControl(checkbox), 0); + + button = uiNewButton("Borderless Resizes"); + uiButtonOnClicked(button, borderWindowOpen, NULL); + uiBoxAppend(page15, uiControl(button), 0); + + hbox = newHorizontalBox(); + uiBoxAppend(page15, uiControl(hbox), 1); + + uiBoxAppend(hbox, uiControl(uiNewVerticalSeparator()), 0); + + return page15; +} diff --git a/test/page16.c b/test/page16.c new file mode 100644 index 00000000..f28ba3c7 --- /dev/null +++ b/test/page16.c @@ -0,0 +1,163 @@ +// 21 june 2016 +#include "test.h" + +static uiTableModelHandler mh; + +static int modelNumColumns(uiTableModelHandler *mh, uiTableModel *m) +{ + return 9; +} + +static uiTableValueType modelColumnType(uiTableModelHandler *mh, uiTableModel *m, int column) +{ + if (column == 3 || column == 4) + return uiTableValueTypeColor; + if (column == 5) + return uiTableValueTypeImage; + if (column == 7 || column == 8) + return uiTableValueTypeInt; + return uiTableValueTypeString; +} + +static int modelNumRows(uiTableModelHandler *mh, uiTableModel *m) +{ + return 15; +} + +static uiImage *img[2]; +static char row9text[1024]; +static int yellowRow = -1; +static int checkStates[15]; + +static uiTableValue *modelCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, int col) +{ + char buf[256]; + + if (col == 3) { + if (row == yellowRow) + return uiNewTableValueColor(1, 1, 0, 1); + if (row == 3) + return uiNewTableValueColor(1, 0, 0, 1); + if (row == 11) + return uiNewTableValueColor(0, 0.5, 1, 0.5); + return NULL; + } + if (col == 4) { + if ((row % 2) == 1) + return uiNewTableValueColor(0.5, 0, 0.75, 1); + return NULL; + } + if (col == 5) { + if (row < 8) + return uiNewTableValueImage(img[0]); + return uiNewTableValueImage(img[1]); + } + if (col == 7) + return uiNewTableValueInt(checkStates[row]); + if (col == 8) { + if (row == 0) + return uiNewTableValueInt(0); + if (row == 13) + return uiNewTableValueInt(100); + if (row == 14) + return uiNewTableValueInt(-1); + return uiNewTableValueInt(50); + } + switch (col) { + case 0: + sprintf(buf, "Row %d", row); + break; + case 2: + if (row == 9) + return uiNewTableValueString(row9text); + // fall through + case 1: + strcpy(buf, "Part"); + break; + case 6: + strcpy(buf, "Make Yellow"); + break; + } + return uiNewTableValueString(buf); +} + +static void modelSetCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, int col, const uiTableValue *val) +{ + if (row == 9 && col == 2) + strcpy(row9text, uiTableValueString(val)); + if (col == 6) { + int prevYellowRow; + + prevYellowRow = yellowRow; + yellowRow = row; + if (prevYellowRow != -1) + uiTableModelRowChanged(m, prevYellowRow); + uiTableModelRowChanged(m, yellowRow); + } + if (col == 7) + checkStates[row] = uiTableValueInt(val); +} + +static uiTableModel *m; + +uiBox *makePage16(void) +{ + uiBox *page16; + uiTable *t; + uiTableParams p; + uiTableTextColumnOptionalParams tp; + + img[0] = uiNewImage(16, 16); + appendImageNamed(img[0], "andlabs_16x16test_24june2016.png"); + appendImageNamed(img[0], "andlabs_32x32test_24june2016.png"); + img[1] = uiNewImage(16, 16); + appendImageNamed(img[1], "tango-icon-theme-0.8.90_16x16_x-office-spreadsheet.png"); + appendImageNamed(img[1], "tango-icon-theme-0.8.90_32x32_x-office-spreadsheet.png"); + + strcpy(row9text, "Part"); + + memset(checkStates, 0, 15 * sizeof (int)); + + page16 = newVerticalBox(); + + mh.NumColumns = modelNumColumns; + mh.ColumnType = modelColumnType; + mh.NumRows = modelNumRows; + mh.CellValue = modelCellValue; + mh.SetCellValue = modelSetCellValue; + m = uiNewTableModel(&mh); + + memset(&p, 0, sizeof (uiTableParams)); + p.Model = m; + p.RowBackgroundColorModelColumn = 3; + t = uiNewTable(&p); + uiBoxAppend(page16, uiControl(t), 1); + + uiTableAppendTextColumn(t, "Column 1", + 0, uiTableModelColumnNeverEditable, NULL); + + memset(&tp, 0, sizeof (uiTableTextColumnOptionalParams)); + tp.ColorModelColumn = 4; + uiTableAppendImageTextColumn(t, "Column 2", + 5, + 1, uiTableModelColumnNeverEditable, &tp); + uiTableAppendTextColumn(t, "Editable", + 2, uiTableModelColumnAlwaysEditable, NULL); + + uiTableAppendCheckboxColumn(t, "Checkboxes", + 7, uiTableModelColumnAlwaysEditable); + uiTableAppendButtonColumn(t, "Buttons", + 6, uiTableModelColumnAlwaysEditable); + + uiTableAppendProgressBarColumn(t, "Progress Bar", + 8); + + return page16; +} + +void freePage16(void) +{ + uiFreeTableModel(m); + uiFreeImage(img[1]); + uiFreeImage(img[0]); +} diff --git a/test/page4.c b/test/page4.c index 27c68880..ce4a6afb 100644 --- a/test/page4.c +++ b/test/page4.c @@ -8,7 +8,7 @@ static uiProgressBar *pbar; #define CHANGED(what) \ static void on ## what ## Changed(ui ## what *this, void *data) \ { \ - uintmax_t value; \ + int value; \ printf("on %s changed\n", #what); \ value = ui ## what ## Value(this); \ uiSpinboxSetValue(spinbox, value); \ @@ -29,13 +29,13 @@ SETTOO(Slider, Low, -80) SETTOO(Slider, High, 80) static uiCombobox *cbox; -static uiCombobox *editable; +static uiEditableCombobox *editable; static uiRadioButtons *rb; static void appendCBRB(uiButton *b, void *data) { uiComboboxAppend(cbox, "New Item"); - uiComboboxAppend(editable, "New Item"); + uiEditableComboboxAppend(editable, "New Item"); uiRadioButtonsAppend(rb, "New Item"); } @@ -44,6 +44,35 @@ static void onCBChanged(uiCombobox *c, void *data) printf("%s combobox changed to %d\n", (char *) data, (int) uiComboboxSelected(c)); + uiEditableComboboxSetText(editable, "changed"); +} + +static void onECBChanged(uiEditableCombobox *c, void *data) +{ + char *t; + + t = uiEditableComboboxText(c); + printf("%s combobox changed to %s\n", + (char *) data, + t); + uiFreeText(t); +} + +static void onRBSelected(uiRadioButtons *r, void *data) +{ + printf("radio buttons %d\n", uiRadioButtonsSelected(r)); +} + +static void selectSecond(uiButton *b, void *data) +{ + // TODO combobox, editable + uiRadioButtonsSetSelected(rb, 1); +} + +static void selectNone(uiButton *b, void *data) +{ + // TODO combobox, editable + uiRadioButtonsSetSelected(rb, -1); } uiBox *makePage4(void) @@ -101,22 +130,29 @@ uiBox *makePage4(void) uiBoxAppend(page4, uiControl(cbox), 0); editable = uiNewEditableCombobox(); - uiComboboxAppend(editable, "Editable Item 1"); - uiComboboxAppend(editable, "Editable Item 2"); - uiComboboxAppend(editable, "Editable Item 3"); - uiComboboxOnSelected(editable, onCBChanged, "editable"); + uiEditableComboboxAppend(editable, "Editable Item 1"); + uiEditableComboboxAppend(editable, "Editable Item 2"); + uiEditableComboboxAppend(editable, "Editable Item 3"); + uiEditableComboboxOnChanged(editable, onECBChanged, "editable"); uiBoxAppend(page4, uiControl(editable), 0); rb = uiNewRadioButtons(); uiRadioButtonsAppend(rb, "Item 1"); uiRadioButtonsAppend(rb, "Item 2"); uiRadioButtonsAppend(rb, "Item 3"); + uiRadioButtonsOnSelected(rb, onRBSelected, NULL); uiBoxAppend(page4, uiControl(rb), 0); hbox = newHorizontalBox(); b = uiNewButton("Append"); uiButtonOnClicked(b, appendCBRB, NULL); uiBoxAppend(hbox, uiControl(b), 0); + b = uiNewButton("Second"); + uiButtonOnClicked(b, selectSecond, NULL); + uiBoxAppend(hbox, uiControl(b), 0); + b = uiNewButton("None"); + uiButtonOnClicked(b, selectNone, NULL); + uiBoxAppend(hbox, uiControl(b), 0); uiBoxAppend(page4, uiControl(hbox), 0); uiBoxAppend(page4, uiControl(uiNewHorizontalSeparator()), 0); diff --git a/test/page6.c b/test/page6.c index 332b2e66..896b3d50 100644 --- a/test/page6.c +++ b/test/page6.c @@ -72,10 +72,19 @@ static void redraw(uiCombobox *c, void *data) uiAreaQueueRedrawAll(area); } +static void enableArea(uiButton *b, void *data) +{ + if (data != NULL) + uiControlEnable(uiControl(area)); + else + uiControlDisable(uiControl(area)); +} + uiBox *makePage6(void) { uiBox *page6; uiBox *hbox; + uiButton *button; handler.ah.Draw = handlerDraw; handler.ah.MouseEvent = handlerMouseEvent; @@ -99,8 +108,19 @@ uiBox *makePage6(void) area = uiNewArea((uiAreaHandler *) (&handler)); uiBoxAppend(page6, uiControl(area), 1); + hbox = newHorizontalBox(); + uiBoxAppend(page6, uiControl(hbox), 0); + swallowKeys = uiNewCheckbox("Consider key events handled"); - uiBoxAppend(page6, uiControl(swallowKeys), 0); + uiBoxAppend(hbox, uiControl(swallowKeys), 1); + + button = uiNewButton("Enable"); + uiButtonOnClicked(button, enableArea, button); + uiBoxAppend(hbox, uiControl(button), 0); + + button = uiNewButton("Disable"); + uiButtonOnClicked(button, enableArea, NULL); + uiBoxAppend(hbox, uiControl(button), 0); return page6; } diff --git a/test/page7a.c b/test/page7a.c index ff0db201..72e03216 100644 --- a/test/page7a.c +++ b/test/page7a.c @@ -41,7 +41,7 @@ static void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *p) startText = uiEntryText(startAngle); sweepText = uiEntryText(sweep); - factor = M_PI / 180; + factor = uiPi / 180; if (uiCheckboxChecked(radians)) factor = 1; diff --git a/test/page7c.c b/test/page7c.c index 1ebddad1..ac6a316c 100644 --- a/test/page7c.c +++ b/test/page7c.c @@ -65,7 +65,7 @@ static void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *dp) uiDrawPathNewFigureWithArc(path, areaSize / 2, areaSize / 2, circleRadius, - 0, 2 * M_PI, + 0, 2 * uiPi, 0); uiDrawPathEnd(path); stops[0].Pos =0.0; diff --git a/test/page8.c b/test/page8.c index 3819257d..7d855560 100644 --- a/test/page8.c +++ b/test/page8.c @@ -5,7 +5,7 @@ static void onListFonts(uiButton *b, void *data) { uiDrawFontFamilies *ff; char *this; - uintmax_t i, n; + int i, n; uiMultilineEntrySetText(uiMultilineEntry(data), ""); ff = uiDrawListFontFamilies(); diff --git a/test/page9.c b/test/page9.c index 3c5e2073..65b2d3a1 100644 --- a/test/page9.c +++ b/test/page9.c @@ -25,12 +25,17 @@ static double entryDouble(uiEntry *e) return d; } -// TODO this should be altered not to restore all state on exit so default text colors can be checked static void drawGuides(uiDrawContext *c, uiDrawTextFontMetrics *m) { uiDrawPath *p; uiDrawBrush b; uiDrawStrokeParams sp; + double leading; + double y; + + leading = 0; + if (uiCheckboxChecked(addLeading)) + leading = m->Leading; memset(&b, 0, sizeof (uiDrawBrush)); b.Type = uiDrawBrushTypeSolid; @@ -43,8 +48,10 @@ static void drawGuides(uiDrawContext *c, uiDrawTextFontMetrics *m) uiDrawSave(c); p = uiDrawNewPath(uiDrawFillModeWinding); - uiDrawPathNewFigure(p, 8, 10); - uiDrawPathLineTo(p, 8, 10 + m->Ascent); + y = 10; + uiDrawPathNewFigure(p, 8, y); + y += m->Ascent; + uiDrawPathLineTo(p, 8, y); uiDrawPathEnd(p); b.R = 0.94; b.G = 0.5; @@ -54,8 +61,9 @@ static void drawGuides(uiDrawContext *c, uiDrawTextFontMetrics *m) uiDrawFreePath(p); p = uiDrawNewPath(uiDrawFillModeWinding); - uiDrawPathNewFigure(p, 8, 10 + m->Ascent); - uiDrawPathLineTo(p, 8, 10 + m->Ascent + m->Descent); + uiDrawPathNewFigure(p, 8, y); + y += m->Descent; + uiDrawPathLineTo(p, 8, y); uiDrawPathEnd(p); b.R = 0.12; b.G = 0.56; @@ -64,6 +72,33 @@ static void drawGuides(uiDrawContext *c, uiDrawTextFontMetrics *m) uiDrawStroke(c, p, &b, &sp); uiDrawFreePath(p); + // and again for the second line + p = uiDrawNewPath(uiDrawFillModeWinding); + y += leading; + uiDrawPathNewFigure(p, 8, y); + y += m->Ascent; + uiDrawPathLineTo(p, 8, y); + uiDrawPathEnd(p); + b.R = 0.94; + b.G = 0.5; + b.B = 0.5; + b.A = 0.75; + uiDrawStroke(c, p, &b, &sp); + uiDrawFreePath(p); + + p = uiDrawNewPath(uiDrawFillModeWinding); + uiDrawPathNewFigure(p, 8, y); + y += m->Descent; + uiDrawPathLineTo(p, 8, y); + uiDrawPathEnd(p); + b.R = 0.12; + b.G = 0.56; + b.B = 1.0; + b.A = 0.75; + uiDrawStroke(c, p, &b, &sp); + uiDrawFreePath(p); + + // and a box to text layout top-left corners p = uiDrawNewPath(uiDrawFillModeWinding); uiDrawPathAddRectangle(p, 0, 0, 10, 10); uiDrawPathEnd(p); @@ -195,7 +230,7 @@ uiBox *makePage9(void) uiComboboxAppend(textWeight, "Medium"); uiComboboxAppend(textWeight, "Semi Bold"); uiComboboxAppend(textWeight, "Bold"); - uiComboboxAppend(textWeight, "Utra Bold"); + uiComboboxAppend(textWeight, "Ultra Bold"); uiComboboxAppend(textWeight, "Heavy"); uiComboboxAppend(textWeight, "Ultra Heavy"); uiComboboxSetSelected(textWeight, uiDrawTextWeightNormal); diff --git a/test/resources.rc b/test/resources.rc index 11c78a76..ebc5d6e6 100644 --- a/test/resources.rc +++ b/test/resources.rc @@ -6,4 +6,8 @@ // this is the Common Controls 6 manifest // TODO set up the string values here // 1 is the value of CREATEPROCESS_MANIFEST_RESOURCE_ID and 24 is the value of RT_MANIFEST; we use it directly to avoid needing to share winapi.h with the tests and examples +#ifndef _UI_STATIC 1 24 "test.manifest" +#else +1 24 "test.static.manifest" +#endif diff --git a/test/spaced.c b/test/spaced.c index 8bd4f465..02db99ae 100644 --- a/test/spaced.c +++ b/test/spaced.c @@ -7,8 +7,8 @@ struct thing { }; static struct thing *things = NULL; -static uintmax_t len = 0; -static uintmax_t cap = 0; +static size_t len = 0; +static size_t cap = 0; #define grow 32 @@ -31,13 +31,15 @@ enum types { box, tab, group, + form, + grid, }; void setSpaced(int spaced) { - uintmax_t i; + size_t i; void *p; - uintmax_t j, n; + size_t j, n; for (i = 0; i < len; i++) { p = things[i].ptr; @@ -56,6 +58,12 @@ void setSpaced(int spaced) case group: uiGroupSetMargined(uiGroup(p), spaced); break; + case form: + uiFormSetPadded(uiForm(p), spaced); + break; + case grid: + uiGridSetPadded(uiGrid(p), spaced); + break; } } } @@ -64,9 +72,9 @@ void querySpaced(char out[12]) // more than enough { int m = 0; int p = 0; - uintmax_t i; + size_t i; void *pp; - uintmax_t j, n; + size_t j, n; for (i = 0; i < len; i++) { pp = things[i].ptr; @@ -88,6 +96,8 @@ void querySpaced(char out[12]) // more than enough if (uiGroupMargined(uiGroup(pp))) m++; break; + // TODO form + // TODO grid } } @@ -147,3 +157,21 @@ uiGroup *newGroup(const char *text) append(g, group); return g; } + +uiForm *newForm(void) +{ + uiForm *f; + + f = uiNewForm(); + append(f, form); + return f; +} + +uiGrid *newGrid(void) +{ + uiGrid *g; + + g = uiNewGrid(); + append(g, grid); + return g; +} diff --git a/test/test.h b/test/test.h index 89d41695..42ec9314 100644 --- a/test/test.h +++ b/test/test.h @@ -1,13 +1,11 @@ // 22 april 2015 -// TODO -#define _GNU_SOURCE -#define _USE_MATH_DEFINES #include #include #include #include #include #include +#include #include "../ui.h" // main.c @@ -25,6 +23,8 @@ extern uiBox *newHorizontalBox(void); extern uiBox *newVerticalBox(void); extern uiTab *newTab(void); extern uiGroup *newGroup(const char *); +extern uiForm *newForm(void); +extern uiGrid *newGrid(void); // menus.c extern uiMenuItem *shouldQuitItem; @@ -51,7 +51,7 @@ extern uiBox *makePage5(uiWindow *); extern uiBox *makePage6(void); // drawtests.c -extern void runDrawTest(intmax_t, uiAreaDrawParams *); +extern void runDrawTest(int, uiAreaDrawParams *); extern void populateComboboxWithTests(uiCombobox *); // page7.c @@ -74,3 +74,25 @@ extern uiBox *makePage9(void); // page10.c extern uiBox *makePage10(void); + +// page11.c +extern uiBox *makePage11(void); + +// page12.c +extern uiBox *makePage12(void); + +// page13.c +extern uiBox *makePage13(void); + +// page14.c +extern uiTab *makePage14(void); + +// page15.c +extern uiBox *makePage15(uiWindow *); + +// page16.c +extern uiBox *makePage16(void); +extern void freePage16(void); + +// images.c +extern void appendImageNamed(uiImage *img, const char *name); diff --git a/test/test.static.manifest b/test/test.static.manifest new file mode 100644 index 00000000..d8e83a83 --- /dev/null +++ b/test/test.static.manifest @@ -0,0 +1,32 @@ + + + +Your application description here. + + + + + + + + + + + + + + + + diff --git a/ui.h b/ui.h index 26da72df..40aea949 100644 --- a/ui.h +++ b/ui.h @@ -2,6 +2,11 @@ // TODO add a uiVerifyControlType() function that can be used by control implementations to verify controls +// TODOs +// - make getters that return whether something exists accept a NULL pointer to discard the value (and thus only return that the thing exists?) +// - const-correct everything +// - normalize documentation between typedefs and structs + #ifndef __LIBUI_UI_H__ #define __LIBUI_UI_H__ @@ -12,8 +17,15 @@ extern "C" { #endif -// TODO add __declspec(dllimport) on windows -#ifndef _UI_EXTERN +// this macro is generated by cmake +#ifdef libui_EXPORTS +#ifdef _WIN32 +#define _UI_EXTERN __declspec(dllexport) extern +#else +#define _UI_EXTERN __attribute__((visibility("default"))) extern +#endif +#else +// TODO add __declspec(dllimport) on windows, but only if not static #define _UI_EXTERN extern #endif @@ -21,6 +33,18 @@ extern "C" { // This has the advantage of being ABI-able should we ever need an ABI... #define _UI_ENUM(s) typedef unsigned int s; enum +// This constant is provided because M_PI is nonstandard. +// This comes from Go's math.Pi, which in turn comes from http://oeis.org/A000796. +#define uiPi 3.14159265358979323846264338327950288419716939937510582097494459 + +// TODO uiBool? + +// uiForEach represents the return value from one of libui's various ForEach functions. +_UI_ENUM(uiForEach) { + uiForEachContinue, + uiForEachStop, +}; + typedef struct uiInitOptions uiInitOptions; struct uiInitOptions { @@ -32,11 +56,20 @@ _UI_EXTERN void uiUninit(void); _UI_EXTERN void uiFreeInitError(const char *err); _UI_EXTERN void uiMain(void); +_UI_EXTERN void uiMainSteps(void); +_UI_EXTERN int uiMainStep(int wait); _UI_EXTERN void uiQuit(void); -// TODO write a test for this after adding multiline entries _UI_EXTERN void uiQueueMain(void (*f)(void *data), void *data); +// TODO standardize the looping behavior return type, either with some enum or something, and the test expressions throughout the code +// TODO figure out what to do about looping and the exact point that the timer is rescheduled so we can document it; see https://github.com/andlabs/libui/pull/277 +// TODO (also in the above link) document that this cannot be called from any thread, unlike uiQueueMain() +// TODO document that the minimum exact timing, either accuracy (timer burst, etc.) or granularity (15ms on Windows, etc.), is OS-defined +// TODO also figure out how long until the initial tick is registered on all platforms to document +// TODO also add a comment about how useful this could be in bindings, depending on the language being bound to +_UI_EXTERN void uiTimer(int milliseconds, int (*f)(void *data), void *data); + _UI_EXTERN void uiOnShouldQuit(int (*f)(void *data), void *data); _UI_EXTERN void uiFreeText(char *text); @@ -77,15 +110,23 @@ _UI_EXTERN uiControl *uiAllocControl(size_t n, uint32_t OSsig, uint32_t typesig, _UI_EXTERN void uiFreeControl(uiControl *); // TODO make sure all controls have these -_UI_EXTERN void uiControlVerifyDestroy(uiControl *); _UI_EXTERN void uiControlVerifySetParent(uiControl *, uiControl *); _UI_EXTERN int uiControlEnabledToUser(uiControl *); +_UI_EXTERN void uiUserBugCannotSetParentOnToplevel(const char *type); + typedef struct uiWindow uiWindow; #define uiWindow(this) ((uiWindow *) (this)) _UI_EXTERN char *uiWindowTitle(uiWindow *w); _UI_EXTERN void uiWindowSetTitle(uiWindow *w, const char *title); +_UI_EXTERN void uiWindowContentSize(uiWindow *w, int *width, int *height); +_UI_EXTERN void uiWindowSetContentSize(uiWindow *w, int width, int height); +_UI_EXTERN int uiWindowFullscreen(uiWindow *w); +_UI_EXTERN void uiWindowSetFullscreen(uiWindow *w, int fullscreen); +_UI_EXTERN void uiWindowOnContentSizeChanged(uiWindow *w, void (*f)(uiWindow *, void *), void *data); _UI_EXTERN void uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *w, void *data), void *data); +_UI_EXTERN int uiWindowBorderless(uiWindow *w); +_UI_EXTERN void uiWindowSetBorderless(uiWindow *w, int borderless); _UI_EXTERN void uiWindowSetChild(uiWindow *w, uiControl *child); _UI_EXTERN int uiWindowMargined(uiWindow *w); _UI_EXTERN void uiWindowSetMargined(uiWindow *w, int margined); @@ -101,21 +142,12 @@ _UI_EXTERN uiButton *uiNewButton(const char *text); typedef struct uiBox uiBox; #define uiBox(this) ((uiBox *) (this)) _UI_EXTERN void uiBoxAppend(uiBox *b, uiControl *child, int stretchy); -_UI_EXTERN void uiBoxDelete(uiBox *b, uintmax_t index); +_UI_EXTERN void uiBoxDelete(uiBox *b, int index); _UI_EXTERN int uiBoxPadded(uiBox *b); _UI_EXTERN void uiBoxSetPadded(uiBox *b, int padded); _UI_EXTERN uiBox *uiNewHorizontalBox(void); _UI_EXTERN uiBox *uiNewVerticalBox(void); -typedef struct uiEntry uiEntry; -#define uiEntry(this) ((uiEntry *) (this)) -_UI_EXTERN char *uiEntryText(uiEntry *e); -_UI_EXTERN void uiEntrySetText(uiEntry *e, const char *text); -_UI_EXTERN void uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *e, void *data), void *data); -_UI_EXTERN int uiEntryReadOnly(uiEntry *e); -_UI_EXTERN void uiEntrySetReadOnly(uiEntry *e, int readonly); -_UI_EXTERN uiEntry *uiNewEntry(void); - typedef struct uiCheckbox uiCheckbox; #define uiCheckbox(this) ((uiCheckbox *) (this)) _UI_EXTERN char *uiCheckboxText(uiCheckbox *c); @@ -125,6 +157,17 @@ _UI_EXTERN int uiCheckboxChecked(uiCheckbox *c); _UI_EXTERN void uiCheckboxSetChecked(uiCheckbox *c, int checked); _UI_EXTERN uiCheckbox *uiNewCheckbox(const char *text); +typedef struct uiEntry uiEntry; +#define uiEntry(this) ((uiEntry *) (this)) +_UI_EXTERN char *uiEntryText(uiEntry *e); +_UI_EXTERN void uiEntrySetText(uiEntry *e, const char *text); +_UI_EXTERN void uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *e, void *data), void *data); +_UI_EXTERN int uiEntryReadOnly(uiEntry *e); +_UI_EXTERN void uiEntrySetReadOnly(uiEntry *e, int readonly); +_UI_EXTERN uiEntry *uiNewEntry(void); +_UI_EXTERN uiEntry *uiNewPasswordEntry(void); +_UI_EXTERN uiEntry *uiNewSearchEntry(void); + typedef struct uiLabel uiLabel; #define uiLabel(this) ((uiLabel *) (this)) _UI_EXTERN char *uiLabelText(uiLabel *l); @@ -134,11 +177,11 @@ _UI_EXTERN uiLabel *uiNewLabel(const char *text); typedef struct uiTab uiTab; #define uiTab(this) ((uiTab *) (this)) _UI_EXTERN void uiTabAppend(uiTab *t, const char *name, uiControl *c); -_UI_EXTERN void uiTabInsertAt(uiTab *t, const char *name, uintmax_t before, uiControl *c); -_UI_EXTERN void uiTabDelete(uiTab *t, uintmax_t index); -_UI_EXTERN uintmax_t uiTabNumPages(uiTab *t); -_UI_EXTERN int uiTabMargined(uiTab *t, uintmax_t page); -_UI_EXTERN void uiTabSetMargined(uiTab *t, uintmax_t page, int margined); +_UI_EXTERN void uiTabInsertAt(uiTab *t, const char *name, int before, uiControl *c); +_UI_EXTERN void uiTabDelete(uiTab *t, int index); +_UI_EXTERN int uiTabNumPages(uiTab *t); +_UI_EXTERN int uiTabMargined(uiTab *t, int page); +_UI_EXTERN void uiTabSetMargined(uiTab *t, int page, int margined); _UI_EXTERN uiTab *uiNewTab(void); typedef struct uiGroup uiGroup; @@ -157,51 +200,68 @@ _UI_EXTERN uiGroup *uiNewGroup(const char *title); typedef struct uiSpinbox uiSpinbox; #define uiSpinbox(this) ((uiSpinbox *) (this)) -_UI_EXTERN intmax_t uiSpinboxValue(uiSpinbox *s); -_UI_EXTERN void uiSpinboxSetValue(uiSpinbox *s, intmax_t value); +_UI_EXTERN int uiSpinboxValue(uiSpinbox *s); +_UI_EXTERN void uiSpinboxSetValue(uiSpinbox *s, int value); _UI_EXTERN void uiSpinboxOnChanged(uiSpinbox *s, void (*f)(uiSpinbox *s, void *data), void *data); -_UI_EXTERN uiSpinbox *uiNewSpinbox(intmax_t min, intmax_t max); - -typedef struct uiProgressBar uiProgressBar; -#define uiProgressBar(this) ((uiProgressBar *) (this)) -// TODO uiProgressBarValue() -_UI_EXTERN void uiProgressBarSetValue(uiProgressBar *p, int n); -_UI_EXTERN uiProgressBar *uiNewProgressBar(void); +_UI_EXTERN uiSpinbox *uiNewSpinbox(int min, int max); typedef struct uiSlider uiSlider; #define uiSlider(this) ((uiSlider *) (this)) -_UI_EXTERN intmax_t uiSliderValue(uiSlider *s); -_UI_EXTERN void uiSliderSetValue(uiSlider *s, intmax_t value); +_UI_EXTERN int uiSliderValue(uiSlider *s); +_UI_EXTERN void uiSliderSetValue(uiSlider *s, int value); _UI_EXTERN void uiSliderOnChanged(uiSlider *s, void (*f)(uiSlider *s, void *data), void *data); -_UI_EXTERN uiSlider *uiNewSlider(intmax_t min, intmax_t max); +_UI_EXTERN uiSlider *uiNewSlider(int min, int max); + +typedef struct uiProgressBar uiProgressBar; +#define uiProgressBar(this) ((uiProgressBar *) (this)) +_UI_EXTERN int uiProgressBarValue(uiProgressBar *p); +_UI_EXTERN void uiProgressBarSetValue(uiProgressBar *p, int n); +_UI_EXTERN uiProgressBar *uiNewProgressBar(void); typedef struct uiSeparator uiSeparator; #define uiSeparator(this) ((uiSeparator *) (this)) _UI_EXTERN uiSeparator *uiNewHorizontalSeparator(void); +_UI_EXTERN uiSeparator *uiNewVerticalSeparator(void); typedef struct uiCombobox uiCombobox; #define uiCombobox(this) ((uiCombobox *) (this)) _UI_EXTERN void uiComboboxAppend(uiCombobox *c, const char *text); -_UI_EXTERN intmax_t uiComboboxSelected(uiCombobox *c); -_UI_EXTERN void uiComboboxSetSelected(uiCombobox *c, intmax_t n); +_UI_EXTERN int uiComboboxSelected(uiCombobox *c); +_UI_EXTERN void uiComboboxSetSelected(uiCombobox *c, int n); _UI_EXTERN void uiComboboxOnSelected(uiCombobox *c, void (*f)(uiCombobox *c, void *data), void *data); _UI_EXTERN uiCombobox *uiNewCombobox(void); -_UI_EXTERN uiCombobox *uiNewEditableCombobox(void); + +typedef struct uiEditableCombobox uiEditableCombobox; +#define uiEditableCombobox(this) ((uiEditableCombobox *) (this)) +_UI_EXTERN void uiEditableComboboxAppend(uiEditableCombobox *c, const char *text); +_UI_EXTERN char *uiEditableComboboxText(uiEditableCombobox *c); +_UI_EXTERN void uiEditableComboboxSetText(uiEditableCombobox *c, const char *text); +// TODO what do we call a function that sets the currently selected item and fills the text field with it? editable comboboxes have no consistent concept of selected item +_UI_EXTERN void uiEditableComboboxOnChanged(uiEditableCombobox *c, void (*f)(uiEditableCombobox *c, void *data), void *data); +_UI_EXTERN uiEditableCombobox *uiNewEditableCombobox(void); typedef struct uiRadioButtons uiRadioButtons; #define uiRadioButtons(this) ((uiRadioButtons *) (this)) _UI_EXTERN void uiRadioButtonsAppend(uiRadioButtons *r, const char *text); +_UI_EXTERN int uiRadioButtonsSelected(uiRadioButtons *r); +_UI_EXTERN void uiRadioButtonsSetSelected(uiRadioButtons *r, int n); +_UI_EXTERN void uiRadioButtonsOnSelected(uiRadioButtons *r, void (*f)(uiRadioButtons *, void *), void *data); _UI_EXTERN uiRadioButtons *uiNewRadioButtons(void); +struct tm; typedef struct uiDateTimePicker uiDateTimePicker; #define uiDateTimePicker(this) ((uiDateTimePicker *) (this)) +// TODO document that tm_wday and tm_yday are undefined, and tm_isdst should be -1 +// TODO document that for both sides +// TODO document time zone conversions or lack thereof +// TODO for Time: define what values are returned when a part is missing +_UI_EXTERN void uiDateTimePickerTime(uiDateTimePicker *d, struct tm *time); +_UI_EXTERN void uiDateTimePickerSetTime(uiDateTimePicker *d, const struct tm *time); +_UI_EXTERN void uiDateTimePickerOnChanged(uiDateTimePicker *d, void (*f)(uiDateTimePicker *, void *), void *data); _UI_EXTERN uiDateTimePicker *uiNewDateTimePicker(void); _UI_EXTERN uiDateTimePicker *uiNewDatePicker(void); _UI_EXTERN uiDateTimePicker *uiNewTimePicker(void); -// TODO merge with uiEntry? some things can't be shared (for instance, the future Invalid() -// TODO how are line endings converted? -// TODO provide a facility for allowing horizontal scrolling // TODO provide a facility for entering tab stops? typedef struct uiMultilineEntry uiMultilineEntry; #define uiMultilineEntry(this) ((uiMultilineEntry *) (this)) @@ -212,6 +272,7 @@ _UI_EXTERN void uiMultilineEntryOnChanged(uiMultilineEntry *e, void (*f)(uiMulti _UI_EXTERN int uiMultilineEntryReadOnly(uiMultilineEntry *e); _UI_EXTERN void uiMultilineEntrySetReadOnly(uiMultilineEntry *e, int readonly); _UI_EXTERN uiMultilineEntry *uiNewMultilineEntry(void); +_UI_EXTERN uiMultilineEntry *uiNewNonWrappingMultilineEntry(void); typedef struct uiMenuItem uiMenuItem; #define uiMenuItem(this) ((uiMenuItem *) (this)) @@ -255,15 +316,38 @@ struct uiAreaHandler { int (*KeyEvent)(uiAreaHandler *, uiArea *, uiAreaKeyEvent *); }; +// TODO RTL layouts? +// TODO reconcile edge and corner naming +_UI_ENUM(uiWindowResizeEdge) { + uiWindowResizeEdgeLeft, + uiWindowResizeEdgeTop, + uiWindowResizeEdgeRight, + uiWindowResizeEdgeBottom, + uiWindowResizeEdgeTopLeft, + uiWindowResizeEdgeTopRight, + uiWindowResizeEdgeBottomLeft, + uiWindowResizeEdgeBottomRight, + // TODO have one for keyboard resizes? + // TODO GDK doesn't seem to have any others, including for keyboards... + // TODO way to bring up the system menu instead? +}; + #define uiArea(this) ((uiArea *) (this)) // TODO give a better name // TODO document the types of width and height -_UI_EXTERN void uiAreaSetSize(uiArea *a, intmax_t width, intmax_t height); +_UI_EXTERN void uiAreaSetSize(uiArea *a, int width, int height); // TODO uiAreaQueueRedraw() _UI_EXTERN void uiAreaQueueRedrawAll(uiArea *a); _UI_EXTERN void uiAreaScrollTo(uiArea *a, double x, double y, double width, double height); +// TODO document these can only be called within Mouse() handlers +// TODO should these be allowed on scrolling areas? +// TODO decide which mouse events should be accepted; Down is the only one guaranteed to work right now +// TODO what happens to events after calling this up to and including the next mouse up? +// TODO release capture? +_UI_EXTERN void uiAreaBeginUserWindowMove(uiArea *a); +_UI_EXTERN void uiAreaBeginUserWindowResize(uiArea *a, uiWindowResizeEdge edge); _UI_EXTERN uiArea *uiNewArea(uiAreaHandler *ah); -_UI_EXTERN uiArea *uiNewScrollingArea(uiAreaHandler *ah, intmax_t width, intmax_t height); +_UI_EXTERN uiArea *uiNewScrollingArea(uiAreaHandler *ah, int width, int height); struct uiAreaDrawParams { uiDrawContext *Context; @@ -418,90 +502,514 @@ _UI_EXTERN void uiDrawClip(uiDrawContext *c, uiDrawPath *path); _UI_EXTERN void uiDrawSave(uiDrawContext *c); _UI_EXTERN void uiDrawRestore(uiDrawContext *c); -// TODO manage the use of Text, Font, and TextFont, and of the uiDrawText prefix in general +// uiAttribute stores information about an attribute in a +// uiAttributedString. +// +// You do not create uiAttributes directly; instead, you create a +// uiAttribute of a given type using the specialized constructor +// functions. For every Unicode codepoint in the uiAttributedString, +// at most one value of each attribute type can be applied. +// +// uiAttributes are immutable and the uiAttributedString takes +// ownership of the uiAttribute object once assigned, copying its +// contents as necessary. +typedef struct uiAttribute uiAttribute; -///// TODO -typedef struct uiDrawFontFamilies uiDrawFontFamilies; +// @role uiAttribute destructor +// uiFreeAttribute() frees a uiAttribute. You generally do not need to +// call this yourself, as uiAttributedString does this for you. In fact, +// it is an error to call this function on a uiAttribute that has been +// given to a uiAttributedString. You can call this, however, if you +// created a uiAttribute that you aren't going to use later. +_UI_EXTERN void uiFreeAttribute(uiAttribute *a); -_UI_EXTERN uiDrawFontFamilies *uiDrawListFontFamilies(void); -_UI_EXTERN uintmax_t uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff); -_UI_EXTERN char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, uintmax_t n); -_UI_EXTERN void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff); -///// END TODO - -typedef struct uiDrawTextLayout uiDrawTextLayout; -typedef struct uiDrawTextFont uiDrawTextFont; -typedef struct uiDrawTextFontDescriptor uiDrawTextFontDescriptor; -typedef struct uiDrawTextFontMetrics uiDrawTextFontMetrics; - -_UI_ENUM(uiDrawTextWeight) { - uiDrawTextWeightThin, - uiDrawTextWeightUltraLight, - uiDrawTextWeightLight, - uiDrawTextWeightBook, - uiDrawTextWeightNormal, - uiDrawTextWeightMedium, - uiDrawTextWeightSemiBold, - uiDrawTextWeightBold, - uiDrawTextWeightUtraBold, - uiDrawTextWeightHeavy, - uiDrawTextWeightUltraHeavy, +// uiAttributeType holds the possible uiAttribute types that may be +// returned by uiAttributeGetType(). Refer to the documentation for +// each type's constructor function for details on each type. +_UI_ENUM(uiAttributeType) { + uiAttributeTypeFamily, + uiAttributeTypeSize, + uiAttributeTypeWeight, + uiAttributeTypeItalic, + uiAttributeTypeStretch, + uiAttributeTypeColor, + uiAttributeTypeBackground, + uiAttributeTypeUnderline, + uiAttributeTypeUnderlineColor, + uiAttributeTypeFeatures, }; -_UI_ENUM(uiDrawTextItalic) { - uiDrawTextItalicNormal, - uiDrawTextItalicOblique, - uiDrawTextItalicItalic, +// uiAttributeGetType() returns the type of a. +// TODO I don't like this name +_UI_EXTERN uiAttributeType uiAttributeGetType(const uiAttribute *a); + +// uiNewFamilyAttribute() creates a new uiAttribute that changes the +// font family of the text it is applied to. family is copied; you do not +// need to keep it alive after uiNewFamilyAttribute() returns. Font +// family names are case-insensitive. +_UI_EXTERN uiAttribute *uiNewFamilyAttribute(const char *family); + +// uiAttributeFamily() returns the font family stored in a. The +// returned string is owned by a. It is an error to call this on a +// uiAttribute that does not hold a font family. +_UI_EXTERN const char *uiAttributeFamily(const uiAttribute *a); + +// uiNewSizeAttribute() creates a new uiAttribute that changes the +// size of the text it is applied to, in typographical points. +_UI_EXTERN uiAttribute *uiNewSizeAttribute(double size); + +// uiAttributeSize() returns the font size stored in a. It is an error to +// call this on a uiAttribute that does not hold a font size. +_UI_EXTERN double uiAttributeSize(const uiAttribute *a); + +// uiTextWeight represents possible text weights. These roughly +// map to the OS/2 text weight field of TrueType and OpenType +// fonts, or to CSS weight numbers. The named constants are +// nominal values; the actual values may vary by font and by OS, +// though this isn't particularly likely. Any value between +// uiTextWeightMinimum and uiTextWeightMaximum, inclusive, +// is allowed. +// +// Note that due to restrictions in early versions of Windows, some +// fonts have "special" weights be exposed in many programs as +// separate font families. This is perhaps most notable with +// Arial Black. libui does not do this, even on Windows (because the +// DirectWrite API libui uses on Windows does not do this); to +// specify Arial Black, use family Arial and weight uiTextWeightBlack. +_UI_ENUM(uiTextWeight) { + uiTextWeightMinimum = 0, + uiTextWeightThin = 100, + uiTextWeightUltraLight = 200, + uiTextWeightLight = 300, + uiTextWeightBook = 350, + uiTextWeightNormal = 400, + uiTextWeightMedium = 500, + uiTextWeightSemiBold = 600, + uiTextWeightBold = 700, + uiTextWeightUltraBold = 800, + uiTextWeightHeavy = 900, + uiTextWeightUltraHeavy = 950, + uiTextWeightMaximum = 1000, }; -_UI_ENUM(uiDrawTextStretch) { - uiDrawTextStretchUltraCondensed, - uiDrawTextStretchExtraCondensed, - uiDrawTextStretchCondensed, - uiDrawTextStretchSemiCondensed, - uiDrawTextStretchNormal, - uiDrawTextStretchSemiExpanded, - uiDrawTextStretchExpanded, - uiDrawTextStretchExtraExpanded, - uiDrawTextStretchUltraExpanded, +// uiNewWeightAttribute() creates a new uiAttribute that changes the +// weight of the text it is applied to. It is an error to specify a weight +// outside the range [uiTextWeightMinimum, +// uiTextWeightMaximum]. +_UI_EXTERN uiAttribute *uiNewWeightAttribute(uiTextWeight weight); + +// uiAttributeWeight() returns the font weight stored in a. It is an error +// to call this on a uiAttribute that does not hold a font weight. +_UI_EXTERN uiTextWeight uiAttributeWeight(const uiAttribute *a); + +// uiTextItalic represents possible italic modes for a font. Italic +// represents "true" italics where the slanted glyphs have custom +// shapes, whereas oblique represents italics that are merely slanted +// versions of the normal glyphs. Most fonts usually have one or the +// other. +_UI_ENUM(uiTextItalic) { + uiTextItalicNormal, + uiTextItalicOblique, + uiTextItalicItalic, }; -struct uiDrawTextFontDescriptor { - const char *Family; +// uiNewItalicAttribute() creates a new uiAttribute that changes the +// italic mode of the text it is applied to. It is an error to specify an +// italic mode not specified in uiTextItalic. +_UI_EXTERN uiAttribute *uiNewItalicAttribute(uiTextItalic italic); + +// uiAttributeItalic() returns the font italic mode stored in a. It is an +// error to call this on a uiAttribute that does not hold a font italic +// mode. +_UI_EXTERN uiTextItalic uiAttributeItalic(const uiAttribute *a); + +// uiTextStretch represents possible stretches (also called "widths") +// of a font. +// +// Note that due to restrictions in early versions of Windows, some +// fonts have "special" stretches be exposed in many programs as +// separate font families. This is perhaps most notable with +// Arial Condensed. libui does not do this, even on Windows (because +// the DirectWrite API libui uses on Windows does not do this); to +// specify Arial Condensed, use family Arial and stretch +// uiTextStretchCondensed. +_UI_ENUM(uiTextStretch) { + uiTextStretchUltraCondensed, + uiTextStretchExtraCondensed, + uiTextStretchCondensed, + uiTextStretchSemiCondensed, + uiTextStretchNormal, + uiTextStretchSemiExpanded, + uiTextStretchExpanded, + uiTextStretchExtraExpanded, + uiTextStretchUltraExpanded, +}; + +// uiNewStretchAttribute() creates a new uiAttribute that changes the +// stretch of the text it is applied to. It is an error to specify a strech +// not specified in uiTextStretch. +_UI_EXTERN uiAttribute *uiNewStretchAttribute(uiTextStretch stretch); + +// uiAttributeStretch() returns the font stretch stored in a. It is an +// error to call this on a uiAttribute that does not hold a font stretch. +_UI_EXTERN uiTextStretch uiAttributeStretch(const uiAttribute *a); + +// uiNewColorAttribute() creates a new uiAttribute that changes the +// color of the text it is applied to. It is an error to specify an invalid +// color. +_UI_EXTERN uiAttribute *uiNewColorAttribute(double r, double g, double b, double a); + +// uiAttributeColor() returns the text color stored in a. It is an +// error to call this on a uiAttribute that does not hold a text color. +_UI_EXTERN void uiAttributeColor(const uiAttribute *a, double *r, double *g, double *b, double *alpha); + +// uiNewBackgroundAttribute() creates a new uiAttribute that +// changes the background color of the text it is applied to. It is an +// error to specify an invalid color. +_UI_EXTERN uiAttribute *uiNewBackgroundAttribute(double r, double g, double b, double a); + +// TODO reuse uiAttributeColor() for background colors, or make a new function... + +// uiUnderline specifies a type of underline to use on text. +_UI_ENUM(uiUnderline) { + uiUnderlineNone, + uiUnderlineSingle, + uiUnderlineDouble, + uiUnderlineSuggestion, // wavy or dotted underlines used for spelling/grammar checkers +}; + +// uiNewUnderlineAttribute() creates a new uiAttribute that changes +// the type of underline on the text it is applied to. It is an error to +// specify an underline type not specified in uiUnderline. +_UI_EXTERN uiAttribute *uiNewUnderlineAttribute(uiUnderline u); + +// uiAttributeUnderline() returns the underline type stored in a. It is +// an error to call this on a uiAttribute that does not hold an underline +// style. +_UI_EXTERN uiUnderline uiAttributeUnderline(const uiAttribute *a); + +// uiUnderlineColor specifies the color of any underline on the text it +// is applied to, regardless of the type of underline. In addition to +// being able to specify a custom color, you can explicitly specify +// platform-specific colors for suggestion underlines; to use them +// correctly, pair them with uiUnderlineSuggestion (though they can +// be used on other types of underline as well). +// +// If an underline type is applied but no underline color is +// specified, the text color is used instead. If an underline color +// is specified without an underline type, the underline color +// attribute is ignored, but not removed from the uiAttributedString. +_UI_ENUM(uiUnderlineColor) { + uiUnderlineColorCustom, + uiUnderlineColorSpelling, + uiUnderlineColorGrammar, + uiUnderlineColorAuxiliary, // for instance, the color used by smart replacements on macOS or in Microsoft Office +}; + +// uiNewUnderlineColorAttribute() creates a new uiAttribute that +// changes the color of the underline on the text it is applied to. +// It is an error to specify an underline color not specified in +// uiUnderlineColor. +// +// If the specified color type is uiUnderlineColorCustom, it is an +// error to specify an invalid color value. Otherwise, the color values +// are ignored and should be specified as zero. +_UI_EXTERN uiAttribute *uiNewUnderlineColorAttribute(uiUnderlineColor u, double r, double g, double b, double a); + +// uiAttributeUnderlineColor() returns the underline color stored in +// a. It is an error to call this on a uiAttribute that does not hold an +// underline color. +_UI_EXTERN void uiAttributeUnderlineColor(const uiAttribute *a, uiUnderlineColor *u, double *r, double *g, double *b, double *alpha); + +// uiOpenTypeFeatures represents a set of OpenType feature +// tag-value pairs, for applying OpenType features to text. +// OpenType feature tags are four-character codes defined by +// OpenType that cover things from design features like small +// caps and swashes to language-specific glyph shapes and +// beyond. Each tag may only appear once in any given +// uiOpenTypeFeatures instance. Each value is a 32-bit integer, +// often used as a Boolean flag, but sometimes as an index to choose +// a glyph shape to use. +// +// If a font does not support a certain feature, that feature will be +// ignored. (TODO verify this on all OSs) +// +// See the OpenType specification at +// https://www.microsoft.com/typography/otspec/featuretags.htm +// for the complete list of available features, information on specific +// features, and how to use them. +// TODO invalid features +typedef struct uiOpenTypeFeatures uiOpenTypeFeatures; + +// uiOpenTypeFeaturesForEachFunc is the type of the function +// invoked by uiOpenTypeFeaturesForEach() for every OpenType +// feature in otf. Refer to that function's documentation for more +// details. +typedef uiForEach (*uiOpenTypeFeaturesForEachFunc)(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value, void *data); + +// @role uiOpenTypeFeatures constructor +// uiNewOpenTypeFeatures() returns a new uiOpenTypeFeatures +// instance, with no tags yet added. +_UI_EXTERN uiOpenTypeFeatures *uiNewOpenTypeFeatures(void); + +// @role uiOpenTypeFeatures destructor +// uiFreeOpenTypeFeatures() frees otf. +_UI_EXTERN void uiFreeOpenTypeFeatures(uiOpenTypeFeatures *otf); + +// uiOpenTypeFeaturesClone() makes a copy of otf and returns it. +// Changing one will not affect the other. +_UI_EXTERN uiOpenTypeFeatures *uiOpenTypeFeaturesClone(const uiOpenTypeFeatures *otf); + +// uiOpenTypeFeaturesAdd() adds the given feature tag and value +// to otf. The feature tag is specified by a, b, c, and d. If there is +// already a value associated with the specified tag in otf, the old +// value is removed. +_UI_EXTERN void uiOpenTypeFeaturesAdd(uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value); + +// uiOpenTypeFeaturesRemove() removes the given feature tag +// and value from otf. If the tag is not present in otf, +// uiOpenTypeFeaturesRemove() does nothing. +_UI_EXTERN void uiOpenTypeFeaturesRemove(uiOpenTypeFeatures *otf, char a, char b, char c, char d); + +// uiOpenTypeFeaturesGet() determines whether the given feature +// tag is present in otf. If it is, *value is set to the tag's value and +// nonzero is returned. Otherwise, zero is returned. +// +// Note that if uiOpenTypeFeaturesGet() returns zero, value isn't +// changed. This is important: if a feature is not present in a +// uiOpenTypeFeatures, the feature is NOT treated as if its +// value was zero anyway. Script-specific font shaping rules and +// font-specific feature settings may use a different default value +// for a feature. You should likewise not treat a missing feature as +// having a value of zero either. Instead, a missing feature should +// be treated as having some unspecified default value. +_UI_EXTERN int uiOpenTypeFeaturesGet(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t *value); + +// uiOpenTypeFeaturesForEach() executes f for every tag-value +// pair in otf. The enumeration order is unspecified. You cannot +// modify otf while uiOpenTypeFeaturesForEach() is running. +_UI_EXTERN void uiOpenTypeFeaturesForEach(const uiOpenTypeFeatures *otf, uiOpenTypeFeaturesForEachFunc f, void *data); + +// uiNewFeaturesAttribute() creates a new uiAttribute that changes +// the font family of the text it is applied to. otf is copied; you may +// free it after uiNewFeaturesAttribute() returns. +_UI_EXTERN uiAttribute *uiNewFeaturesAttribute(const uiOpenTypeFeatures *otf); + +// uiAttributeFeatures() returns the OpenType features stored in a. +// The returned uiOpenTypeFeatures object is owned by a. It is an +// error to call this on a uiAttribute that does not hold OpenType +// features. +_UI_EXTERN const uiOpenTypeFeatures *uiAttributeFeatures(const uiAttribute *a); + +// uiAttributedString represents a string of UTF-8 text that can +// optionally be embellished with formatting attributes. libui +// provides the list of formatting attributes, which cover common +// formatting traits like boldface and color as well as advanced +// typographical features provided by OpenType like superscripts +// and small caps. These attributes can be combined in a variety of +// ways. +// +// Attributes are applied to runs of Unicode codepoints in the string. +// Zero-length runs are elided. Consecutive runs that have the same +// attribute type and value are merged. Each attribute is independent +// of each other attribute; overlapping attributes of different types +// do not split each other apart, but different values of the same +// attribute type do. +// +// The empty string can also be represented by uiAttributedString, +// but because of the no-zero-length-attribute rule, it will not have +// attributes. +// +// A uiAttributedString takes ownership of all attributes given to +// it, as it may need to duplicate or delete uiAttribute objects at +// any time. By extension, when you free a uiAttributedString, +// all uiAttributes within will also be freed. Each method will +// describe its own rules in more details. +// +// In addition, uiAttributedString provides facilities for moving +// between grapheme clusters, which represent a character +// from the point of view of the end user. The cursor of a text editor +// is always placed on a grapheme boundary, so you can use these +// features to move the cursor left or right by one "character". +// TODO does uiAttributedString itself need this +// +// uiAttributedString does not provide enough information to be able +// to draw itself onto a uiDrawContext or respond to user actions. +// In order to do that, you'll need to use a uiDrawTextLayout, which +// is built from the combination of a uiAttributedString and a set of +// layout-specific properties. +typedef struct uiAttributedString uiAttributedString; + +// uiAttributedStringForEachAttributeFunc is the type of the function +// invoked by uiAttributedStringForEachAttribute() for every +// attribute in s. Refer to that function's documentation for more +// details. +typedef uiForEach (*uiAttributedStringForEachAttributeFunc)(const uiAttributedString *s, const uiAttribute *a, size_t start, size_t end, void *data); + +// @role uiAttributedString constructor +// uiNewAttributedString() creates a new uiAttributedString from +// initialString. The string will be entirely unattributed. +_UI_EXTERN uiAttributedString *uiNewAttributedString(const char *initialString); + +// @role uiAttributedString destructor +// uiFreeAttributedString() destroys the uiAttributedString s. +// It will also free all uiAttributes within. +_UI_EXTERN void uiFreeAttributedString(uiAttributedString *s); + +// uiAttributedStringString() returns the textual content of s as a +// '\0'-terminated UTF-8 string. The returned pointer is valid until +// the next change to the textual content of s. +_UI_EXTERN const char *uiAttributedStringString(const uiAttributedString *s); + +// uiAttributedStringLength() returns the number of UTF-8 bytes in +// the textual content of s, excluding the terminating '\0'. +_UI_EXTERN size_t uiAttributedStringLen(const uiAttributedString *s); + +// uiAttributedStringAppendUnattributed() adds the '\0'-terminated +// UTF-8 string str to the end of s. The new substring will be +// unattributed. +_UI_EXTERN void uiAttributedStringAppendUnattributed(uiAttributedString *s, const char *str); + +// uiAttributedStringInsertAtUnattributed() adds the '\0'-terminated +// UTF-8 string str to s at the byte position specified by at. The new +// substring will be unattributed; existing attributes will be moved +// along with their text. +_UI_EXTERN void uiAttributedStringInsertAtUnattributed(uiAttributedString *s, const char *str, size_t at); + +// TODO add the Append and InsertAtExtendingAttributes functions +// TODO and add functions that take a string + length + +// uiAttributedStringDelete() deletes the characters and attributes of +// s in the byte range [start, end). +_UI_EXTERN void uiAttributedStringDelete(uiAttributedString *s, size_t start, size_t end); + +// TODO add a function to uiAttributedString to get an attribute's value at a specific index or in a specific range, so we can edit + +// uiAttributedStringSetAttribute() sets a in the byte range [start, end) +// of s. Any existing attributes in that byte range of the same type are +// removed. s takes ownership of a; you should not use it after +// uiAttributedStringSetAttribute() returns. +_UI_EXTERN void uiAttributedStringSetAttribute(uiAttributedString *s, uiAttribute *a, size_t start, size_t end); + +// uiAttributedStringForEachAttribute() enumerates all the +// uiAttributes in s. It is an error to modify s in f. Within f, s still +// owns the attribute; you can neither free it nor save it for later +// use. +// TODO reword the above for consistency (TODO and find out what I meant by that) +// TODO define an enumeration order (or mark it as undefined); also define how consecutive runs of identical attributes are handled here and sync with the definition of uiAttributedString itself +_UI_EXTERN void uiAttributedStringForEachAttribute(const uiAttributedString *s, uiAttributedStringForEachAttributeFunc f, void *data); + +// TODO const correct this somehow (the implementation needs to mutate the structure) +_UI_EXTERN size_t uiAttributedStringNumGraphemes(uiAttributedString *s); + +// TODO const correct this somehow (the implementation needs to mutate the structure) +_UI_EXTERN size_t uiAttributedStringByteIndexToGrapheme(uiAttributedString *s, size_t pos); + +// TODO const correct this somehow (the implementation needs to mutate the structure) +_UI_EXTERN size_t uiAttributedStringGraphemeToByteIndex(uiAttributedString *s, size_t pos); + +// uiFontDescriptor provides a complete description of a font where +// one is needed. Currently, this means as the default font of a +// uiDrawTextLayout and as the data returned by uiFontButton. +// All the members operate like the respective uiAttributes. +typedef struct uiFontDescriptor uiFontDescriptor; + +struct uiFontDescriptor { + // TODO const-correct this or figure out how to deal with this when getting a value + char *Family; double Size; - uiDrawTextWeight Weight; - uiDrawTextItalic Italic; - uiDrawTextStretch Stretch; + uiTextWeight Weight; + uiTextItalic Italic; + uiTextStretch Stretch; }; -struct uiDrawTextFontMetrics { - double Ascent; - double Descent; - double Leading; - // TODO do these two mean the same across all platforms? - double UnderlinePos; - double UnderlineThickness; +// uiDrawTextLayout is a concrete representation of a +// uiAttributedString that can be displayed in a uiDrawContext. +// It includes information important for the drawing of a block of +// text, including the bounding box to wrap the text within, the +// alignment of lines of text within that box, areas to mark as +// being selected, and other things. +// +// Unlike uiAttributedString, the content of a uiDrawTextLayout is +// immutable once it has been created. +// +// TODO talk about OS-specific differences with text drawing that libui can't account for... +typedef struct uiDrawTextLayout uiDrawTextLayout; + +// uiDrawTextAlign specifies the alignment of lines of text in a +// uiDrawTextLayout. +// TODO should this really have Draw in the name? +_UI_ENUM(uiDrawTextAlign) { + uiDrawTextAlignLeft, + uiDrawTextAlignCenter, + uiDrawTextAlignRight, }; -_UI_EXTERN uiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc); -_UI_EXTERN void uiDrawFreeTextFont(uiDrawTextFont *font); -_UI_EXTERN uintptr_t uiDrawTextFontHandle(uiDrawTextFont *font); -_UI_EXTERN void uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc); -// TODO make copy with given attributes methods? -// TODO yuck this name -_UI_EXTERN void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics); +// uiDrawTextLayoutParams describes a uiDrawTextLayout. +// DefaultFont is used to render any text that is not attributed +// sufficiently in String. Width determines the width of the bounding +// box of the text; the height is determined automatically. +typedef struct uiDrawTextLayoutParams uiDrawTextLayoutParams; -// TODO initial line spacing? and what about leading? -_UI_EXTERN uiDrawTextLayout *uiDrawNewTextLayout(const char *text, uiDrawTextFont *defaultFont, double width); -_UI_EXTERN void uiDrawFreeTextLayout(uiDrawTextLayout *layout); -// TODO get width -_UI_EXTERN void uiDrawTextLayoutSetWidth(uiDrawTextLayout *layout, double width); -_UI_EXTERN void uiDrawTextLayoutExtents(uiDrawTextLayout *layout, double *width, double *height); +// TODO const-correct this somehow +struct uiDrawTextLayoutParams { + uiAttributedString *String; + uiFontDescriptor *DefaultFont; + double Width; + uiDrawTextAlign Align; +}; -// and the attributes that you can set on a text layout -_UI_EXTERN void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, intmax_t startChar, intmax_t endChar, double r, double g, double b, double a); +// @role uiDrawTextLayout constructor +// uiDrawNewTextLayout() creates a new uiDrawTextLayout from +// the given parameters. +// +// TODO +// - allow creating a layout out of a substring +// - allow marking compositon strings +// - allow marking selections, even after creation +// - add the following functions: +// - uiDrawTextLayoutHeightForWidth() (returns the height that a layout would need to be to display the entire string at a given width) +// - uiDrawTextLayoutRangeForSize() (returns what substring would fit in a given size) +// - uiDrawTextLayoutNewWithHeight() (limits amount of string used by the height) +// - some function to fix up a range (for text editing) +_UI_EXTERN uiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *params); -_UI_EXTERN void uiDrawText(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout); +// @role uiDrawFreeTextLayout destructor +// uiDrawFreeTextLayout() frees tl. The underlying +// uiAttributedString is not freed. +_UI_EXTERN void uiDrawFreeTextLayout(uiDrawTextLayout *tl); + +// uiDrawText() draws tl in c with the top-left point of tl at (x, y). +_UI_EXTERN void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y); + +// uiDrawTextLayoutExtents() returns the width and height of tl +// in width and height. The returned width may be smaller than +// the width passed into uiDrawNewTextLayout() depending on +// how the text in tl is wrapped. Therefore, you can use this +// function to get the actual size of the text layout. +_UI_EXTERN void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height); + +// TODO metrics functions + +// TODO number of lines visible for clipping rect, range visible for clipping rect? + +// uiFontButton is a button that allows users to choose a font when they click on it. +typedef struct uiFontButton uiFontButton; +#define uiFontButton(this) ((uiFontButton *) (this)) +// uiFontButtonFont() returns the font currently selected in the uiFontButton in desc. +// uiFontButtonFont() allocates resources in desc; when you are done with the font, call uiFreeFontButtonFont() to release them. +// uiFontButtonFont() does not allocate desc itself; you must do so. +// TODO have a function that sets an entire font descriptor to a range in a uiAttributedString at once, for SetFont? +_UI_EXTERN void uiFontButtonFont(uiFontButton *b, uiFontDescriptor *desc); +// TOOD SetFont, mechanics +// uiFontButtonOnChanged() sets the function that is called when the font in the uiFontButton is changed. +_UI_EXTERN void uiFontButtonOnChanged(uiFontButton *b, void (*f)(uiFontButton *, void *), void *data); +// uiNewFontButton() creates a new uiFontButton. The default font selected into the uiFontButton is OS-defined. +_UI_EXTERN uiFontButton *uiNewFontButton(void); +// uiFreeFontButtonFont() frees resources allocated in desc by uiFontButtonFont(). +// After calling uiFreeFontButtonFont(), the contents of desc should be assumed to be undefined (though since you allocate desc itself, you can safely reuse desc for other font descriptors). +// Calling uiFreeFontButtonFont() on a uiFontDescriptor not returned by uiFontButtonFont() results in undefined behavior. +_UI_EXTERN void uiFreeFontButtonFont(uiFontDescriptor *desc); _UI_ENUM(uiModifiers) { uiModifierCtrl = 1 << 0, @@ -520,10 +1028,10 @@ struct uiAreaMouseEvent { double AreaWidth; double AreaHeight; - uintmax_t Down; - uintmax_t Up; + int Down; + int Up; - uintmax_t Count; + int Count; uiModifiers Modifiers; @@ -582,13 +1090,373 @@ struct uiAreaKeyEvent { int Up; }; -typedef struct uiFontButton uiFontButton; -#define uiFontButton(this) ((uiFontButton *) (this)) -// TODO document this returns a new font -_UI_EXTERN uiDrawTextFont *uiFontButtonFont(uiFontButton *b); -// TOOD SetFont, mechanics -_UI_EXTERN void uiFontButtonOnChanged(uiFontButton *b, void (*f)(uiFontButton *, void *), void *data); -_UI_EXTERN uiFontButton *uiNewFontButton(void); +typedef struct uiColorButton uiColorButton; +#define uiColorButton(this) ((uiColorButton *) (this)) +_UI_EXTERN void uiColorButtonColor(uiColorButton *b, double *r, double *g, double *bl, double *a); +_UI_EXTERN void uiColorButtonSetColor(uiColorButton *b, double r, double g, double bl, double a); +_UI_EXTERN void uiColorButtonOnChanged(uiColorButton *b, void (*f)(uiColorButton *, void *), void *data); +_UI_EXTERN uiColorButton *uiNewColorButton(void); + +typedef struct uiForm uiForm; +#define uiForm(this) ((uiForm *) (this)) +_UI_EXTERN void uiFormAppend(uiForm *f, const char *label, uiControl *c, int stretchy); +_UI_EXTERN void uiFormDelete(uiForm *f, int index); +_UI_EXTERN int uiFormPadded(uiForm *f); +_UI_EXTERN void uiFormSetPadded(uiForm *f, int padded); +_UI_EXTERN uiForm *uiNewForm(void); + +_UI_ENUM(uiAlign) { + uiAlignFill, + uiAlignStart, + uiAlignCenter, + uiAlignEnd, +}; + +_UI_ENUM(uiAt) { + uiAtLeading, + uiAtTop, + uiAtTrailing, + uiAtBottom, +}; + +typedef struct uiGrid uiGrid; +#define uiGrid(this) ((uiGrid *) (this)) +_UI_EXTERN void uiGridAppend(uiGrid *g, uiControl *c, int left, int top, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign); +_UI_EXTERN void uiGridInsertAt(uiGrid *g, uiControl *c, uiControl *existing, uiAt at, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign); +_UI_EXTERN int uiGridPadded(uiGrid *g); +_UI_EXTERN void uiGridSetPadded(uiGrid *g, int padded); +_UI_EXTERN uiGrid *uiNewGrid(void); + +// uiImage stores an image for display on screen. +// +// Images are built from one or more representations, each with the +// same aspect ratio but a different pixel size. libui automatically +// selects the most appropriate representation for drawing the image +// when it comes time to draw the image; what this means depends +// on the pixel density of the target context. Therefore, one can use +// uiImage to draw higher-detailed images on higher-density +// displays. The typical use cases are either: +// +// - have just a single representation, at which point all screens +// use the same image, and thus uiImage acts like a simple +// bitmap image, or +// - have two images, one at normal resolution and one at 2x +// resolution; this matches the current expectations of some +// desktop systems at the time of writing (mid-2018) +// +// uiImage is very simple: it only supports premultiplied 32-bit +// RGBA images, and libui does not provide any image file loading +// or image format conversion utilities on top of that. +typedef struct uiImage uiImage; + +// @role uiImage constructor +// uiNewImage creates a new uiImage with the given width and +// height. This width and height should be the size in points of the +// image in the device-independent case; typically this is the 1x size. +// TODO for all uiImage functions: use const void * for const correctness +_UI_EXTERN uiImage *uiNewImage(double width, double height); + +// @role uiImage destructor +// uiFreeImage frees the given image and all associated resources. +_UI_EXTERN void uiFreeImage(uiImage *i); + +// uiImageAppend adds a representation to the uiImage. +// pixels should point to a byte array of premultiplied pixels +// stored in [R G B A] order (so ((uint8_t *) pixels)[0] is the R of the +// first pixel and [3] is the A of the first pixel). pixelWidth and +// pixelHeight is the size *in pixels* of the image, and pixelStride is +// the number *of bytes* per row of the pixels array. Therefore, +// pixels itself must be at least byteStride * pixelHeight bytes long. +// TODO see if we either need the stride or can provide a way to get the OS-preferred stride (in cairo we do) +_UI_EXTERN void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int byteStride); + +// uiTableValue stores a value to be passed along uiTable and +// uiTableModel. +// +// You do not create uiTableValues directly; instead, you create a +// uiTableValue of a given type using the specialized constructor +// functions. +// +// uiTableValues are immutable and the uiTableModel and uiTable +// take ownership of the uiTableValue object once returned, copying +// its contents as necessary. +typedef struct uiTableValue uiTableValue; + +// @role uiTableValue destructor +// uiFreeTableValue() frees a uiTableValue. You generally do not +// need to call this yourself, as uiTable and uiTableModel do this +// for you. In fact, it is an error to call this function on a uiTableValue +// that has been given to a uiTable or uiTableModel. You can call this, +// however, if you created a uiTableValue that you aren't going to +// use later, or if you called a uiTableModelHandler method directly +// and thus never transferred ownership of the uiTableValue. +_UI_EXTERN void uiFreeTableValue(uiTableValue *v); + +// uiTableValueType holds the possible uiTableValue types that may +// be returned by uiTableValueGetType(). Refer to the documentation +// for each type's constructor function for details on each type. +// TODO actually validate these +_UI_ENUM(uiTableValueType) { + uiTableValueTypeString, + uiTableValueTypeImage, + uiTableValueTypeInt, + uiTableValueTypeColor, +}; + +// uiTableValueGetType() returns the type of v. +// TODO I don't like this name +_UI_EXTERN uiTableValueType uiTableValueGetType(const uiTableValue *v); + +// uiNewTableValueString() returns a new uiTableValue that contains +// str. str is copied; you do not need to keep it alive after +// uiNewTableValueString() returns. +_UI_EXTERN uiTableValue *uiNewTableValueString(const char *str); + +// uiTableValueString() returns the string stored in v. The returned +// string is owned by v. It is an error to call this on a uiTableValue +// that does not hold a string. +_UI_EXTERN const char *uiTableValueString(const uiTableValue *v); + +// uiNewTableValueImage() returns a new uiTableValue that contains +// the given uiImage. +// +// Unlike other similar constructors, uiNewTableValueImage() does +// NOT copy the image. This is because images are comparatively +// larger than the other objects in question. Therefore, you MUST +// keep the image alive as long as the returned uiTableValue is alive. +// As a general rule, if libui calls a uiTableModelHandler method, the +// uiImage is safe to free once any of your code is once again +// executed. +_UI_EXTERN uiTableValue *uiNewTableValueImage(uiImage *img); + +// uiTableValueImage() returns the uiImage stored in v. As these +// images are not owned by v, you should not assume anything +// about the lifetime of the image (unless you created the image, +// and thus control its lifetime). It is an error to call this on a +// uiTableValue that does not hold an image. +_UI_EXTERN uiImage *uiTableValueImage(const uiTableValue *v); + +// uiNewTableValueInt() returns a uiTableValue that stores the given +// int. This can be used both for boolean values (nonzero is true, as +// in C) or progresses (in which case the valid range is -1..100 +// inclusive). +_UI_EXTERN uiTableValue *uiNewTableValueInt(int i); + +// uiTableValueInt() returns the int stored in v. It is an error to call +// this on a uiTableValue that does not store an int. +_UI_EXTERN int uiTableValueInt(const uiTableValue *v); + +// uiNewTableValueColor() returns a uiTableValue that stores the +// given color. +_UI_EXTERN uiTableValue *uiNewTableValueColor(double r, double g, double b, double a); + +// uiTableValueColor() returns the color stored in v. It is an error to +// call this on a uiTableValue that does not store a color. +// TODO define whether all this, for both uiTableValue and uiAttribute, is undefined behavior or a caught error +_UI_EXTERN void uiTableValueColor(const uiTableValue *v, double *r, double *g, double *b, double *a); + +// uiTableModel is an object that provides the data for a uiTable. +// This data is returned via methods you provide in the +// uiTableModelHandler struct. +// +// uiTableModel represents data using a table, but this table does +// not map directly to uiTable itself. Instead, you can have data +// columns which provide instructions for how to render a given +// uiTable's column — for instance, one model column can be used +// to give certain rows of a uiTable a different background color. +// Row numbers DO match with uiTable row numbers. +// +// Once created, the number and data types of columns of a +// uiTableModel cannot change. +// +// Row and column numbers start at 0. A uiTableModel can be +// associated with more than one uiTable at a time. +typedef struct uiTableModel uiTableModel; + +// uiTableModelHandler defines the methods that uiTableModel +// calls when it needs data. Once a uiTableModel is created, these +// methods cannot change. +typedef struct uiTableModelHandler uiTableModelHandler; + +// TODO validate ranges; validate types on each getter/setter call (? table columns only?) +struct uiTableModelHandler { + // NumColumns returns the number of model columns in the + // uiTableModel. This value must remain constant through the + // lifetime of the uiTableModel. This method is not guaranteed + // to be called depending on the system. + // TODO strongly check column numbers and types on all platforms so these clauses can go away + int (*NumColumns)(uiTableModelHandler *, uiTableModel *); + // ColumnType returns the value type of the data stored in + // the given model column of the uiTableModel. The returned + // values must remain constant through the lifetime of the + // uiTableModel. This method is not guaranteed to be called + // depending on the system. + uiTableValueType (*ColumnType)(uiTableModelHandler *, uiTableModel *, int); + // NumRows returns the number or rows in the uiTableModel. + // This value must be non-negative. + int (*NumRows)(uiTableModelHandler *, uiTableModel *); + // CellValue returns a uiTableValue corresponding to the model + // cell at (row, column). The type of the returned uiTableValue + // must match column's value type. Under some circumstances, + // NULL may be returned; refer to the various methods that add + // columns to uiTable for details. Once returned, the uiTable + // that calls CellValue will free the uiTableValue returned. + uiTableValue *(*CellValue)(uiTableModelHandler *mh, uiTableModel *m, int row, int column); + // SetCellValue changes the model cell value at (row, column) + // in the uiTableModel. Within this function, either do nothing + // to keep the current cell value or save the new cell value as + // appropriate. After SetCellValue is called, the uiTable will + // itself reload the table cell. Under certain conditions, the + // uiTableValue passed in can be NULL; refer to the various + // methods that add columns to uiTable for details. Once + // returned, the uiTable that called SetCellValue will free the + // uiTableValue passed in. + void (*SetCellValue)(uiTableModelHandler *, uiTableModel *, int, int, const uiTableValue *); +}; + +// @role uiTableModel constructor +// uiNewTableModel() creates a new uiTableModel with the given +// handler methods. +_UI_EXTERN uiTableModel *uiNewTableModel(uiTableModelHandler *mh); + +// @role uiTableModel destructor +// uiFreeTableModel() frees the given table model. It is an error to +// free table models currently associated with a uiTable. +_UI_EXTERN void uiFreeTableModel(uiTableModel *m); + +// uiTableModelRowInserted() tells any uiTable associated with m +// that a new row has been added to m at index index. You call +// this function when the number of rows in your model has +// changed; after calling it, NumRows() should returm the new row +// count. +_UI_EXTERN void uiTableModelRowInserted(uiTableModel *m, int newIndex); + +// uiTableModelRowChanged() tells any uiTable associated with m +// that the data in the row at index has changed. You do not need to +// call this in your SetCellValue() handlers, but you do need to call +// this if your data changes at some other point. +_UI_EXTERN void uiTableModelRowChanged(uiTableModel *m, int index); + +// uiTableModelRowDeleted() tells any uiTable associated with m +// that the row at index index has been deleted. You call this +// function when the number of rows in your model has changed; +// after calling it, NumRows() should returm the new row +// count. +// TODO for this and Inserted: make sure the "after" part is right; clarify if it's after returning or after calling +_UI_EXTERN void uiTableModelRowDeleted(uiTableModel *m, int oldIndex); +// TODO reordering/moving + +// uiTableModelColumnNeverEditable and +// uiTableModelColumnAlwaysEditable are the value of an editable +// model column parameter to one of the uiTable create column +// functions; if used, that jparticular uiTable colum is not editable +// by the user and always editable by the user, respectively. +#define uiTableModelColumnNeverEditable (-1) +#define uiTableModelColumnAlwaysEditable (-2) + +// uiTableTextColumnOptionalParams are the optional parameters +// that control the appearance of the text column of a uiTable. +typedef struct uiTableTextColumnOptionalParams uiTableTextColumnOptionalParams; + +// uiTableParams defines the parameters passed to uiNewTable(). +typedef struct uiTableParams uiTableParams; + +struct uiTableTextColumnOptionalParams { + // ColorModelColumn is the model column containing the + // text color of this uiTable column's text, or -1 to use the + // default color. + // + // If CellValue() for this column for any cell returns NULL, that + // cell will also use the default text color. + int ColorModelColumn; +}; + +struct uiTableParams { + // Model is the uiTableModel to use for this uiTable. + // This parameter cannot be NULL. + uiTableModel *Model; + // RowBackgroundColorModelColumn is a model column + // number that defines the background color used for the + // entire row in the uiTable, or -1 to use the default color for + // all rows. + // + // If CellValue() for this column for any row returns NULL, that + // row will also use the default background color. + int RowBackgroundColorModelColumn; +}; + +// uiTable is a uiControl that shows tabular data, allowing users to +// manipulate rows of such data at a time. +typedef struct uiTable uiTable; +#define uiTable(this) ((uiTable *) (this)) + +// uiTableAppendTextColumn() appends a text column to t. +// name is displayed in the table header. +// textModelColumn is where the text comes from. +// If a row is editable according to textEditableModelColumn, +// SetCellValue() is called with textModelColumn as the column. +_UI_EXTERN void uiTableAppendTextColumn(uiTable *t, + const char *name, + int textModelColumn, + int textEditableModelColumn, + uiTableTextColumnOptionalParams *textParams); + +// uiTableAppendImageColumn() appends an image column to t. +// Images are drawn at icon size, appropriate to the pixel density +// of the screen showing the uiTable. +_UI_EXTERN void uiTableAppendImageColumn(uiTable *t, + const char *name, + int imageModelColumn); + +// uiTableAppendImageTextColumn() appends a column to t that +// shows both an image and text. +_UI_EXTERN void uiTableAppendImageTextColumn(uiTable *t, + const char *name, + int imageModelColumn, + int textModelColumn, + int textEditableModelColumn, + uiTableTextColumnOptionalParams *textParams); + +// uiTableAppendCheckboxColumn appends a column to t that +// contains a checkbox that the user can interact with (assuming the +// checkbox is editable). SetCellValue() will be called with +// checkboxModelColumn as the column in this case. +_UI_EXTERN void uiTableAppendCheckboxColumn(uiTable *t, + const char *name, + int checkboxModelColumn, + int checkboxEditableModelColumn); + +// uiTableAppendCheckboxTextColumn() appends a column to t +// that contains both a checkbox and text. +_UI_EXTERN void uiTableAppendCheckboxTextColumn(uiTable *t, + const char *name, + int checkboxModelColumn, + int checkboxEditableModelColumn, + int textModelColumn, + int textEditableModelColumn, + uiTableTextColumnOptionalParams *textParams); + +// uiTableAppendProgressBarColumn() appends a column to t +// that displays a progress bar. These columns work like +// uiProgressBar: a cell value of 0..100 displays that percentage, and +// a cell value of -1 displays an indeterminate progress bar. +_UI_EXTERN void uiTableAppendProgressBarColumn(uiTable *t, + const char *name, + int progressModelColumn); + +// uiTableAppendButtonColumn() appends a column to t +// that shows a button that the user can click on. When the user +// does click on the button, SetCellValue() is called with a NULL +// value and buttonModelColumn as the column. +// CellValue() on buttonModelColumn should return the text to show +// in the button. +_UI_EXTERN void uiTableAppendButtonColumn(uiTable *t, + const char *name, + int buttonModelColumn, + int buttonClickableModelColumn); + +// uiNewTable() creates a new uiTable with the specified parameters. +_UI_EXTERN uiTable *uiNewTable(uiTableParams *params); #ifdef __cplusplus } diff --git a/ui_darwin.h b/ui_darwin.h index fde55b42..c9c6ad54 100644 --- a/ui_darwin.h +++ b/ui_darwin.h @@ -24,6 +24,7 @@ struct uiDarwinControl { void (*ChildEdgeHuggingChanged)(uiDarwinControl *); NSLayoutPriority (*HuggingPriority)(uiDarwinControl *, NSLayoutConstraintOrientation); void (*SetHuggingPriority)(uiDarwinControl *, NSLayoutPriority, NSLayoutConstraintOrientation); + void (*ChildVisibilityChanged)(uiDarwinControl *); }; #define uiDarwinControl(this) ((uiDarwinControl *) (this)) // TODO document @@ -34,11 +35,11 @@ _UI_EXTERN BOOL uiDarwinControlHugsBottom(uiDarwinControl *); _UI_EXTERN void uiDarwinControlChildEdgeHuggingChanged(uiDarwinControl *); _UI_EXTERN NSLayoutPriority uiDarwinControlHuggingPriority(uiDarwinControl *, NSLayoutConstraintOrientation); _UI_EXTERN void uiDarwinControlSetHuggingPriority(uiDarwinControl *, NSLayoutPriority, NSLayoutConstraintOrientation); +_UI_EXTERN void uiDarwinControlChildVisibilityChanged(uiDarwinControl *); #define uiDarwinControlDefaultDestroy(type, handlefield) \ static void type ## Destroy(uiControl *c) \ { \ - uiControlVerifyDestroy(c); \ [type(c)->handlefield release]; \ uiFreeControl(c); \ } @@ -73,12 +74,14 @@ _UI_EXTERN void uiDarwinControlSetHuggingPriority(uiDarwinControl *, NSLayoutPri { \ uiDarwinControl(c)->visible = YES; \ [type(c)->handlefield setHidden:NO]; \ + uiDarwinNotifyVisibilityChanged(uiDarwinControl(c)); \ } #define uiDarwinControlDefaultHide(type, handlefield) \ static void type ## Hide(uiControl *c) \ { \ uiDarwinControl(c)->visible = NO; \ [type(c)->handlefield setHidden:YES]; \ + uiDarwinNotifyVisibilityChanged(uiDarwinControl(c)); \ } #define uiDarwinControlDefaultEnabled(type, handlefield) \ static int type ## Enabled(uiControl *c) \ @@ -103,7 +106,7 @@ _UI_EXTERN void uiDarwinControlSetHuggingPriority(uiDarwinControl *, NSLayoutPri if (uiDarwinShouldStopSyncEnableState(c, enabled)) \ return; \ if ([type(c)->handlefield respondsToSelector:@selector(setEnabled:)]) \ - [((id) type(c)->handlefield) setEnabled:enabled]; /* id cast to make compiler happy; thanks mikeash in irc.freenode.net/#macdev */ \ + [((id) (type(c)->handlefield)) setEnabled:enabled]; /* id cast to make compiler happy; thanks mikeash in irc.freenode.net/#macdev */ \ } #define uiDarwinControlDefaultSetSuperview(type, handlefield) \ static void type ## SetSuperview(uiDarwinControl *c, NSView *superview) \ @@ -139,6 +142,11 @@ _UI_EXTERN void uiDarwinControlSetHuggingPriority(uiDarwinControl *, NSLayoutPri { \ [type(c)->handlefield setContentHuggingPriority:priority forOrientation:orientation]; \ } +#define uiDarwinControlDefaultChildVisibilityChanged(type, handlefield) \ + static void type ## ChildVisibilityChanged(uiDarwinControl *c) \ + { \ + /* do nothing */ \ + } #define uiDarwinControlAllDefaultsExceptDestroy(type, handlefield) \ uiDarwinControlDefaultHandle(type, handlefield) \ @@ -157,7 +165,8 @@ _UI_EXTERN void uiDarwinControlSetHuggingPriority(uiDarwinControl *, NSLayoutPri uiDarwinControlDefaultHugsBottom(type, handlefield) \ uiDarwinControlDefaultChildEdgeHuggingChanged(type, handlefield) \ uiDarwinControlDefaultHuggingPriority(type, handlefield) \ - uiDarwinControlDefaultSetHuggingPriority(type, handlefield) + uiDarwinControlDefaultSetHuggingPriority(type, handlefield) \ + uiDarwinControlDefaultChildVisibilityChanged(type, handlefield) #define uiDarwinControlAllDefaults(type, handlefield) \ uiDarwinControlDefaultDestroy(type, handlefield) \ @@ -184,6 +193,7 @@ _UI_EXTERN void uiDarwinControlSetHuggingPriority(uiDarwinControl *, NSLayoutPri uiDarwinControl(var)->ChildEdgeHuggingChanged = type ## ChildEdgeHuggingChanged; \ uiDarwinControl(var)->HuggingPriority = type ## HuggingPriority; \ uiDarwinControl(var)->SetHuggingPriority = type ## SetHuggingPriority; \ + uiDarwinControl(var)->ChildVisibilityChanged = type ## ChildVisibilityChanged; \ uiDarwinControl(var)->visible = YES; \ uiDarwinControl(var)->enabled = YES; // TODO document @@ -200,6 +210,12 @@ _UI_EXTERN BOOL uiDarwinShouldStopSyncEnableState(uiDarwinControl *, BOOL); // TODO document _UI_EXTERN void uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl *); +_UI_EXTERN void uiDarwinNotifyVisibilityChanged(uiDarwinControl *c); + +// TODO document +// TODO document that values should not be cached +_UI_EXTERN CGFloat uiDarwinMarginAmount(void *reserved); +_UI_EXTERN CGFloat uiDarwinPaddingAmount(void *reserved); #ifdef __cplusplus } diff --git a/ui_unix.h b/ui_unix.h index 6ea475a6..ed019260 100644 --- a/ui_unix.h +++ b/ui_unix.h @@ -16,6 +16,7 @@ struct uiUnixControl { uiControl c; uiControl *parent; gboolean addedBefore; + gboolean explicitlyHidden; void (*SetContainer)(uiUnixControl *, GtkContainer *, gboolean); }; #define uiUnixControl(this) ((uiUnixControl *) (this)) @@ -25,7 +26,6 @@ _UI_EXTERN void uiUnixControlSetContainer(uiUnixControl *, GtkContainer *, gbool #define uiUnixControlDefaultDestroy(type) \ static void type ## Destroy(uiControl *c) \ { \ - uiControlVerifyDestroy(c); \ /* TODO is this safe on floating refs? */ \ g_object_unref(type(c)->widget); \ uiFreeControl(c); \ @@ -59,11 +59,13 @@ _UI_EXTERN void uiUnixControlSetContainer(uiUnixControl *, GtkContainer *, gbool #define uiUnixControlDefaultShow(type) \ static void type ## Show(uiControl *c) \ { \ + /*TODO part of massive hack about hidden before*/uiUnixControl(c)->explicitlyHidden=FALSE; \ gtk_widget_show(type(c)->widget); \ } #define uiUnixControlDefaultHide(type) \ static void type ## Hide(uiControl *c) \ { \ + /*TODO part of massive hack about hidden before*/uiUnixControl(c)->explicitlyHidden=TRUE; \ gtk_widget_hide(type(c)->widget); \ } #define uiUnixControlDefaultEnabled(type) \ @@ -81,12 +83,14 @@ _UI_EXTERN void uiUnixControlSetContainer(uiUnixControl *, GtkContainer *, gbool { \ gtk_widget_set_sensitive(type(c)->widget, FALSE); \ } +// TODO this whole addedBefore stuff is a MASSIVE HACK. #define uiUnixControlDefaultSetContainer(type) \ static void type ## SetContainer(uiUnixControl *c, GtkContainer *container, gboolean remove) \ { \ if (!uiUnixControl(c)->addedBefore) { \ g_object_ref_sink(type(c)->widget); /* our own reference, which we release in Destroy() */ \ - gtk_widget_show(type(c)->widget); \ + /* massive hack notes: without any of this, nothing gets shown when we show a window; without the if, all things get shown even if some were explicitly hidden (TODO why don't we just show everything except windows on create? */ \ + /*TODO*/if(!uiUnixControl(c)->explicitlyHidden) gtk_widget_show(type(c)->widget); \ uiUnixControl(c)->addedBefore = TRUE; \ } \ if (remove) \ diff --git a/ui_windows.h b/ui_windows.h index b1a6362f..69dda366 100644 --- a/ui_windows.h +++ b/ui_windows.h @@ -22,26 +22,26 @@ struct uiWindowsControl { BOOL visible; void (*SyncEnableState)(uiWindowsControl *, int); void (*SetParentHWND)(uiWindowsControl *, HWND); - // TODO consider changing these from intmax_t to int - void (*MinimumSize)(uiWindowsControl *, intmax_t *, intmax_t *); + void (*MinimumSize)(uiWindowsControl *, int *, int *); void (*MinimumSizeChanged)(uiWindowsControl *); void (*LayoutRect)(uiWindowsControl *c, RECT *r); void (*AssignControlIDZOrder)(uiWindowsControl *, LONG_PTR *, HWND *); + void (*ChildVisibilityChanged)(uiWindowsControl *); }; #define uiWindowsControl(this) ((uiWindowsControl *) (this)) // TODO document _UI_EXTERN void uiWindowsControlSyncEnableState(uiWindowsControl *, int); _UI_EXTERN void uiWindowsControlSetParentHWND(uiWindowsControl *, HWND); -_UI_EXTERN void uiWindowsControlMinimumSize(uiWindowsControl *, intmax_t *, intmax_t *); +_UI_EXTERN void uiWindowsControlMinimumSize(uiWindowsControl *, int *, int *); _UI_EXTERN void uiWindowsControlMinimumSizeChanged(uiWindowsControl *); _UI_EXTERN void uiWindowsControlLayoutRect(uiWindowsControl *, RECT *); _UI_EXTERN void uiWindowsControlAssignControlIDZOrder(uiWindowsControl *, LONG_PTR *, HWND *); +_UI_EXTERN void uiWindowsControlChildVisibilityChanged(uiWindowsControl *); // TODO document #define uiWindowsControlDefaultDestroy(type) \ static void type ## Destroy(uiControl *c) \ { \ - uiControlVerifyDestroy(c); \ uiWindowsEnsureDestroyWindow(type(c)->hwnd); \ uiFreeControl(c); \ } @@ -76,12 +76,14 @@ _UI_EXTERN void uiWindowsControlAssignControlIDZOrder(uiWindowsControl *, LONG_P { \ uiWindowsControl(c)->visible = 1; \ ShowWindow(type(c)->hwnd, SW_SHOW); \ + uiWindowsControlNotifyVisibilityChanged(uiWindowsControl(c)); \ } #define uiWindowsControlDefaultHide(type) \ static void type ## Hide(uiControl *c) \ { \ uiWindowsControl(c)->visible = 0; \ ShowWindow(type(c)->hwnd, SW_HIDE); \ + uiWindowsControlNotifyVisibilityChanged(uiWindowsControl(c)); \ } #define uiWindowsControlDefaultEnabled(type) \ static int type ## Enabled(uiControl *c) \ @@ -133,6 +135,11 @@ _UI_EXTERN void uiWindowsControlAssignControlIDZOrder(uiWindowsControl *, LONG_P { \ uiWindowsEnsureAssignControlIDZOrder(type(c)->hwnd, controlID, insertAfter); \ } +#define uiWindowsControlDefaultChildVisibilityChanged(type) \ + static void type ## ChildVisibilityChanged(uiWindowsControl *c) \ + { \ + /* do nothing */ \ + } #define uiWindowsControlAllDefaultsExceptDestroy(type) \ uiWindowsControlDefaultHandle(type) \ @@ -149,7 +156,8 @@ _UI_EXTERN void uiWindowsControlAssignControlIDZOrder(uiWindowsControl *, LONG_P uiWindowsControlDefaultSetParentHWND(type) \ uiWindowsControlDefaultMinimumSizeChanged(type) \ uiWindowsControlDefaultLayoutRect(type) \ - uiWindowsControlDefaultAssignControlIDZOrder(type) + uiWindowsControlDefaultAssignControlIDZOrder(type) \ + uiWindowsControlDefaultChildVisibilityChanged(type) #define uiWindowsControlAllDefaults(type) \ uiWindowsControlDefaultDestroy(type) \ @@ -175,6 +183,7 @@ _UI_EXTERN void uiWindowsControlAssignControlIDZOrder(uiWindowsControl *, LONG_P uiWindowsControl(var)->MinimumSizeChanged = type ## MinimumSizeChanged; \ uiWindowsControl(var)->LayoutRect = type ## LayoutRect; \ uiWindowsControl(var)->AssignControlIDZOrder = type ## AssignControlIDZOrder; \ + uiWindowsControl(var)->ChildVisibilityChanged = type ## ChildVisibilityChanged; \ uiWindowsControl(var)->visible = 1; \ uiWindowsControl(var)->enabled = 1; // TODO document @@ -202,11 +211,11 @@ _UI_EXTERN char *uiWindowsWindowText(HWND hwnd); _UI_EXTERN void uiWindowsSetWindowText(HWND hwnd, const char *text); // TODO document -_UI_EXTERN intmax_t uiWindowsWindowTextWidth(HWND hwnd); +_UI_EXTERN int uiWindowsWindowTextWidth(HWND hwnd); // TODO document // TODO point out this should only be used in a resize cycle -_UI_EXTERN void uiWindowsEnsureMoveWindowDuringResize(HWND hwnd, intmax_t x, intmax_t y, intmax_t width, intmax_t height); +_UI_EXTERN void uiWindowsEnsureMoveWindowDuringResize(HWND hwnd, int x, int y, int width, int height); // TODO document _UI_EXTERN void uiWindowsRegisterWM_COMMANDHandler(HWND hwnd, BOOL (*handler)(uiControl *, HWND, WORD, LRESULT *), uiControl *c); @@ -248,6 +257,9 @@ _UI_EXTERN void uiWindowsControlAssignSoleControlIDZOrder(uiWindowsControl *); // TODO document _UI_EXTERN BOOL uiWindowsShouldStopSyncEnableState(uiWindowsControl *c, int enabled); +// TODO document +_UI_EXTERN void uiWindowsControlNotifyVisibilityChanged(uiWindowsControl *c); + #ifdef __cplusplus } #endif diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt new file mode 100644 index 00000000..5c9425d2 --- /dev/null +++ b/unix/CMakeLists.txt @@ -0,0 +1,66 @@ +# 3 june 2016 + +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED gtk+-3.0) + +list(APPEND _LIBUI_SOURCES + unix/alloc.c + unix/area.c + unix/attrstr.c + unix/box.c + unix/button.c + unix/cellrendererbutton.c + unix/checkbox.c + unix/child.c + unix/colorbutton.c + unix/combobox.c + unix/control.c + unix/datetimepicker.c + unix/debug.c + unix/draw.c + unix/drawmatrix.c + unix/drawpath.c + unix/drawtext.c + unix/editablecombo.c + unix/entry.c + unix/fontbutton.c + unix/fontmatch.c + unix/form.c + unix/future.c + unix/graphemes.c + unix/grid.c + unix/group.c + unix/image.c + unix/label.c + unix/main.c + unix/menu.c + unix/multilineentry.c + unix/opentype.c + unix/progressbar.c + unix/radiobuttons.c + unix/separator.c + unix/slider.c + unix/spinbox.c + unix/stddialogs.c + unix/tab.c + unix/table.c + unix/tablemodel.c + unix/text.c + unix/util.c + unix/window.c +) +set(_LIBUI_SOURCES ${_LIBUI_SOURCES} PARENT_SCOPE) + +list(APPEND _LIBUI_INCLUDEDIRS + unix +) +set(_LIBUI_INCLUDEDIRS _LIBUI_INCLUDEDIRS PARENT_SCOPE) + +# TODO the other variables don't work? +set(_LIBUI_CFLAGS + ${GTK_CFLAGS} +PARENT_SCOPE) + +set(_LIBUI_LIBS + ${GTK_LDFLAGS} m ${CMAKE_DL_LIBS} +PARENT_SCOPE) diff --git a/unix/GNUfiles.mk b/unix/GNUfiles.mk deleted file mode 100644 index 4987f177..00000000 --- a/unix/GNUfiles.mk +++ /dev/null @@ -1,60 +0,0 @@ -# 22 april 2015 - -CFILES += \ - unix/alloc.c \ - unix/area.c \ - unix/box.c \ - unix/button.c \ - unix/checkbox.c \ - unix/child.c \ - unix/combobox.c \ - unix/control.c \ - unix/datetimepicker.c \ - unix/draw.c \ - unix/drawmatrix.c \ - unix/drawpath.c \ - unix/drawtext.c \ - unix/entry.c \ - unix/fontbutton.c \ - unix/group.c \ - unix/label.c \ - unix/main.c \ - unix/menu.c \ - unix/multilineentry.c \ - unix/progressbar.c \ - unix/radiobuttons.c \ - unix/separator.c \ - unix/slider.c \ - unix/spinbox.c \ - unix/stddialogs.c \ - unix/tab.c \ - unix/text.c \ - unix/util.c \ - unix/window.c - -HFILES += \ - unix/draw.h \ - unix/uipriv_unix.h - -# TODO split into a separate file or put in GNUmakefile.libui somehow? - -# flags for GTK+ -CFLAGS += \ - `pkg-config --cflags gtk+-3.0` -CXXFLAGS += \ - `pkg-config --cflags gtk+-3.0` -LDFLAGS += \ - `pkg-config --libs gtk+-3.0` -lm -ldl - -# flags for building a shared library -# OS X does support -shared but it has a preferred name for this so let's use that there instead; hence this is not gcc-global -LDFLAGS += \ - -shared - -# flags for warning on undefined symbols -# this is not gcc-global because OS X doesn't support these flags -# TODO figure out why FreeBSD follows linked libraries here -ifneq ($(shell uname -s),FreeBSD) -LDFLAGS += \ - -Wl,--no-undefined -Wl,--no-allow-shlib-undefined -endif diff --git a/unix/GNUinstall.mk b/unix/GNUinstall.mk deleted file mode 100644 index 634dbc06..00000000 --- a/unix/GNUinstall.mk +++ /dev/null @@ -1,8 +0,0 @@ -ifndef PREFIX - PREFIX=/usr -endif - -install: $(OUT) - cp $(OUT) $(DESTDIR)$(PREFIX)/lib/libui.so.0 - ln -fs libui.so.0 $(DESTDIR)$(PREFIX)/lib/libui.so - cp ui.h ui_$(OS).h $(DESTDIR)$(PREFIX)/include/ diff --git a/unix/GNUosspecific.mk b/unix/GNUosspecific.mk deleted file mode 100644 index b5fa75e5..00000000 --- a/unix/GNUosspecific.mk +++ /dev/null @@ -1,13 +0,0 @@ -# 16 october 2015 - -EXESUFFIX = -LIBSUFFIX = .so -OSHSUFFIX = .h -TOOLCHAIN = gcc - -# TODO clean up all the NAMEs and SUFFIXs and NOSOSUFFIXs or whatever it was -USESSONAME = 1 -SOVERSION = $(SOVERSION0) -SONAMEEXT = $(LIBSUFFIX).$(SOVERSION) -# this is not gcc-global because OS X uses a different filename format -SONAMEFLAG = -Wl,-soname, \ No newline at end of file diff --git a/unix/OLD_table.c b/unix/OLD_table.c new file mode 100644 index 00000000..3774345c --- /dev/null +++ b/unix/OLD_table.c @@ -0,0 +1,406 @@ +// 26 june 2016 +#include "uipriv_unix.h" + +void *uiTableModelStrdup(const char *str) +{ + return g_strdup(str); +} + +void *uiTableModelGiveColor(double r, double g, double b, double a) +{ + GdkRGBA rgba; + + rgba.red = r; + rgba.green = g; + rgba.blue = b; + rgba.alpha = a; + return gdk_rgba_copy(&rgba); +} + +uiTableModel *uiNewTableModel(uiTableModelHandler *mh) +{ + uiTableModel *m; + + m = uiTableModel(g_object_new(uiTableModelType, NULL)); + m->mh = mh; + return m; +} + +void uiFreeTableModel(uiTableModel *m) +{ + g_object_unref(m); +} + +void uiTableModelRowInserted(uiTableModel *m, int newIndex) +{ + GtkTreePath *path; + GtkTreeIter iter; + + path = gtk_tree_path_new_from_indices(newIndex, -1); + iter.stamp = STAMP_GOOD; + iter.user_data = GINT_TO_POINTER(newIndex); + gtk_tree_model_row_inserted(GTK_TREE_MODEL(m), path, &iter); + gtk_tree_path_free(path); +} + +void uiTableModelRowChanged(uiTableModel *m, int index) +{ + GtkTreePath *path; + GtkTreeIter iter; + + path = gtk_tree_path_new_from_indices(index, -1); + iter.stamp = STAMP_GOOD; + iter.user_data = GINT_TO_POINTER(index); + gtk_tree_model_row_changed(GTK_TREE_MODEL(m), path, &iter); + gtk_tree_path_free(path); +} + +void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) +{ + GtkTreePath *path; + + path = gtk_tree_path_new_from_indices(oldIndex, -1); + gtk_tree_model_row_deleted(GTK_TREE_MODEL(m), path); + gtk_tree_path_free(path); +} + +enum { + partText, + partImage, + partButton, + partCheckbox, + partProgressBar, +}; + +struct tablePart { + int type; + int textColumn; + int imageColumn; + int valueColumn; + int colorColumn; + GtkCellRenderer *r; + uiTable *tv; // for pixbufs and background color +}; + +struct uiTableColumn { + GtkTreeViewColumn *c; + uiTable *tv; // for pixbufs and background color + GPtrArray *parts; +}; + +struct uiTable { + uiUnixControl c; + GtkWidget *widget; + GtkContainer *scontainer; + GtkScrolledWindow *sw; + GtkWidget *treeWidget; + GtkTreeView *tv; + GPtrArray *columns; + uiTableModel *model; + int backgroundColumn; +}; + +// use the same size as GtkFileChooserWidget's treeview +// TODO refresh when icon theme changes +// TODO doesn't work when scaled +// TODO is this even necessary? +static void setImageSize(GtkCellRenderer *r) +{ + gint size; + gint width, height; + gint xpad, ypad; + + size = 16; // fallback used by GtkFileChooserWidget + if (gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height) != FALSE) + size = MAX(width, height); + gtk_cell_renderer_get_padding(r, &xpad, &ypad); + gtk_cell_renderer_set_fixed_size(r, + 2 * xpad + size, + 2 * ypad + size); +} + +static void applyColor(GtkTreeModel *mm, GtkTreeIter *iter, int modelColumn, GtkCellRenderer *r, const char *prop, const char *propSet) +{ + GValue value = G_VALUE_INIT; + GdkRGBA *rgba; + + gtk_tree_model_get_value(mm, iter, modelColumn, &value); + rgba = (GdkRGBA *) g_value_get_boxed(&value); + if (rgba != NULL) + g_object_set(r, prop, rgba, NULL); + else + g_object_set(r, propSet, FALSE, NULL); + g_value_unset(&value); +} + +static void dataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *mm, GtkTreeIter *iter, gpointer data) +{ + struct tablePart *part = (struct tablePart *) data; + GValue value = G_VALUE_INIT; + const gchar *str; + uiImage *img; + int pval; + + switch (part->type) { + case partText: + gtk_tree_model_get_value(mm, iter, part->textColumn, &value); + str = g_value_get_string(&value); + g_object_set(r, "text", str, NULL); + if (part->colorColumn != -1) + applyColor(mm, iter, + part->colorColumn, + r, "foreground-rgba", "foreground-set"); + break; + case partImage: +//TODO setImageSize(r); + gtk_tree_model_get_value(mm, iter, part->imageColumn, &value); + img = (uiImage *) g_value_get_pointer(&value); + g_object_set(r, "surface", + uiprivImageAppropriateSurface(img, part->tv->treeWidget), + NULL); + break; + case partButton: + gtk_tree_model_get_value(mm, iter, part->textColumn, &value); + str = g_value_get_string(&value); + g_object_set(r, "text", str, NULL); + break; + case partCheckbox: + gtk_tree_model_get_value(mm, iter, part->valueColumn, &value); + g_object_set(r, "active", g_value_get_int(&value) != 0, NULL); + break; + case partProgressBar: + gtk_tree_model_get_value(mm, iter, part->valueColumn, &value); + pval = g_value_get_int(&value); + if (pval == -1) { + // TODO + } else + g_object_set(r, + "pulse", -1, + "value", pval, + NULL); + break; + } + g_value_unset(&value); + + if (part->tv->backgroundColumn != -1) + applyColor(mm, iter, + part->tv->backgroundColumn, + r, "cell-background-rgba", "cell-background-set"); +} + +static void onEdited(struct tablePart *part, int column, const char *pathstr, const void *data) +{ + GtkTreePath *path; + int row; + uiTableModel *m; + + path = gtk_tree_path_new_from_string(pathstr); + row = gtk_tree_path_get_indices(path)[0]; + gtk_tree_path_free(path); + m = part->tv->model; + (*(m->mh->SetCellValue))(m->mh, m, row, column, data); + // and update + uiTableModelRowChanged(m, row); +} + +static void appendPart(uiTableColumn *c, struct tablePart *part, GtkCellRenderer *r, int expand) +{ + part->r = r; + gtk_tree_view_column_pack_start(c->c, part->r, expand != 0); + gtk_tree_view_column_set_cell_data_func(c->c, part->r, dataFunc, part, NULL); + g_ptr_array_add(c->parts, part); +} + +static void textEdited(GtkCellRendererText *renderer, gchar *path, gchar *newText, gpointer data) +{ + struct tablePart *part = (struct tablePart *) data; + + onEdited(part, part->textColumn, path, newText); +} + +void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand) +{ + struct tablePart *part; + GtkCellRenderer *r; + + part = uiprivNew(struct tablePart); + part->type = partText; + part->textColumn = modelColumn; + part->tv = c->tv; + part->colorColumn = -1; + + r = gtk_cell_renderer_text_new(); + g_object_set(r, "editable", FALSE, NULL); + g_signal_connect(r, "edited", G_CALLBACK(textEdited), part); + + appendPart(c, part, r, expand); +} + +void uiTableColumnAppendImagePart(uiTableColumn *c, int modelColumn, int expand) +{ + struct tablePart *part; + + part = uiprivNew(struct tablePart); + part->type = partImage; + part->imageColumn = modelColumn; + part->tv = c->tv; + appendPart(c, part, + gtk_cell_renderer_pixbuf_new(), + expand); +} + +// TODO wrong type here +static void buttonClicked(GtkCellRenderer *r, gchar *pathstr, gpointer data) +{ + struct tablePart *part = (struct tablePart *) data; + + onEdited(part, part->textColumn, pathstr, NULL); +} + +void uiTableColumnAppendButtonPart(uiTableColumn *c, int modelColumn, int expand) +{ + struct tablePart *part; + GtkCellRenderer *r; + + part = uiprivNew(struct tablePart); + part->type = partButton; + part->textColumn = modelColumn; + part->tv = c->tv; + + r = uiprivNewCellRendererButton(); + g_object_set(r, "sensitive", TRUE, NULL); // editable by default + g_signal_connect(r, "clicked", G_CALLBACK(buttonClicked), part); + + appendPart(c, part, r, expand); +} + +// yes, we need to do all this twice :| +static void checkboxToggled(GtkCellRendererToggle *r, gchar *pathstr, gpointer data) +{ + struct tablePart *part = (struct tablePart *) data; + GtkTreePath *path; + int row; + uiTableModel *m; + void *value; + int intval; + + path = gtk_tree_path_new_from_string(pathstr); + row = gtk_tree_path_get_indices(path)[0]; + gtk_tree_path_free(path); + m = part->tv->model; + value = (*(m->mh->CellValue))(m->mh, m, row, part->valueColumn); + intval = !uiTableModelTakeInt(value); + onEdited(part, part->valueColumn, pathstr, uiTableModelGiveInt(intval)); +} + +void uiTableColumnAppendCheckboxPart(uiTableColumn *c, int modelColumn, int expand) +{ + struct tablePart *part; + GtkCellRenderer *r; + + part = uiprivNew(struct tablePart); + part->type = partCheckbox; + part->valueColumn = modelColumn; + part->tv = c->tv; + + r = gtk_cell_renderer_toggle_new(); + g_object_set(r, "sensitive", TRUE, NULL); // editable by default + g_signal_connect(r, "toggled", G_CALLBACK(checkboxToggled), part); + + appendPart(c, part, r, expand); +} + +void uiTableColumnAppendProgressBarPart(uiTableColumn *c, int modelColumn, int expand) +{ + struct tablePart *part; + + part = uiprivNew(struct tablePart); + part->type = partProgressBar; + part->valueColumn = modelColumn; + part->tv = c->tv; + appendPart(c, part, + gtk_cell_renderer_progress_new(), + expand); +} + +void uiTableColumnPartSetEditable(uiTableColumn *c, int part, int editable) +{ + struct tablePart *p; + + p = (struct tablePart *) g_ptr_array_index(c->parts, part); + switch (p->type) { + case partImage: + case partProgressBar: + return; + case partButton: + case partCheckbox: + g_object_set(p->r, "sensitive", editable != 0, NULL); + return; + } + g_object_set(p->r, "editable", editable != 0, NULL); +} + +void uiTableColumnPartSetTextColor(uiTableColumn *c, int part, int modelColumn) +{ + struct tablePart *p; + + p = (struct tablePart *) g_ptr_array_index(c->parts, part); + p->colorColumn = modelColumn; + // TODO refresh table +} + +uiUnixControlAllDefaultsExceptDestroy(uiTable) + +static void uiTableDestroy(uiControl *c) +{ + uiTable *t = uiTable(c); + + // TODO + g_object_unref(t->widget); + uiFreeControl(uiControl(t)); +} + +uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name) +{ + uiTableColumn *c; + + c = uiprivNew(uiTableColumn); + c->c = gtk_tree_view_column_new(); + gtk_tree_view_column_set_resizable(c->c, TRUE); + gtk_tree_view_column_set_title(c->c, name); + gtk_tree_view_append_column(t->tv, c->c); + c->tv = t; // TODO rename field to t, cascade + c->parts = g_ptr_array_new(); + return c; +} + +void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn) +{ + t->backgroundColumn = modelColumn; + // TODO refresh table +} + +uiTable *uiNewTable(uiTableModel *model) +{ + uiTable *t; + + uiUnixNewControl(uiTable, t); + + t->model = model; + t->backgroundColumn = -1; + + t->widget = gtk_scrolled_window_new(NULL, NULL); + t->scontainer = GTK_CONTAINER(t->widget); + t->sw = GTK_SCROLLED_WINDOW(t->widget); + gtk_scrolled_window_set_shadow_type(t->sw, GTK_SHADOW_IN); + + t->treeWidget = gtk_tree_view_new_with_model(GTK_TREE_MODEL(t->model)); + t->tv = GTK_TREE_VIEW(t->treeWidget); + // TODO set up t->tv + + gtk_container_add(t->scontainer, t->treeWidget); + // and make the tree view visible; only the scrolled window's visibility is controlled by libui + gtk_widget_show(t->treeWidget); + + return t; +} diff --git a/unix/alloc.c b/unix/alloc.c index cb37fa10..3fa0fd41 100644 --- a/unix/alloc.c +++ b/unix/alloc.c @@ -13,28 +13,37 @@ static GPtrArray *allocations; #define CCHAR(p) ((const char **) (p)) #define TYPE(p) CCHAR(UINT8(p) + sizeof (size_t)) -void initAlloc(void) +void uiprivInitAlloc(void) { allocations = g_ptr_array_new(); } static void uninitComplain(gpointer ptr, gpointer data) { - fprintf(stderr, "[libui] %p %s\n", ptr, *TYPE(ptr)); + char **str = (char **) data; + char *str2; + + if (*str == NULL) + *str = g_strdup_printf(""); + str2 = g_strdup_printf("%s%p %s\n", *str, ptr, *TYPE(ptr)); + g_free(*str); + *str = str2; } -void uninitAlloc(void) +void uiprivUninitAlloc(void) { + char *str = NULL; + if (allocations->len == 0) { g_ptr_array_free(allocations, TRUE); return; } - fprintf(stderr, "[libui] leaked allocations:\n"); - g_ptr_array_foreach(allocations, uninitComplain, NULL); - complain("either you left something around or there's a bug in libui"); + g_ptr_array_foreach(allocations, uninitComplain, &str); + uiprivUserBug("Some data was leaked; either you left a uiControl lying around or there's a bug in libui itself. Leaked data:\n%s", str); + g_free(str); } -void *uiAlloc(size_t size, const char *type) +void *uiprivAlloc(size_t size, const char *type) { void *out; @@ -45,31 +54,31 @@ void *uiAlloc(size_t size, const char *type) return DATA(out); } -void *uiRealloc(void *p, size_t new, const char *type) +void *uiprivRealloc(void *p, size_t new, const char *type) { void *out; size_t *s; if (p == NULL) - return uiAlloc(new, type); + return uiprivAlloc(new, type); p = BASE(p); out = g_realloc(p, EXTRA + new); s = SIZE(out); - if (new <= *s) + if (new > *s) memset(((uint8_t *) DATA(out)) + *s, 0, new - *s); *s = new; if (g_ptr_array_remove(allocations, p) == FALSE) - complain("%p not found in allocations array in uiRealloc()", p); + uiprivImplBug("%p not found in allocations array in uiprivRealloc()", p); g_ptr_array_add(allocations, out); return DATA(out); } -void uiFree(void *p) +void uiprivFree(void *p) { if (p == NULL) - complain("attempt to uiFree(NULL); there's a bug somewhere"); + uiprivImplBug("attempt to uiprivFree(NULL)"); p = BASE(p); g_free(p); if (g_ptr_array_remove(allocations, p) == FALSE) - complain("%p not found in allocations array in uiFree()", p); + uiprivImplBug("%p not found in allocations array in uiprivFree()", p); } diff --git a/unix/area.c b/unix/area.c index ee4e081c..0a2e6d69 100644 --- a/unix/area.c +++ b/unix/area.c @@ -19,7 +19,7 @@ struct areaWidget { // construct-only parameters aare not set until after the init() function has returned // we need this particular object available during init(), so put it here instead of in uiArea // keep a pointer in uiArea for convenience, though - clickCounter cc; + uiprivClickCounter cc; }; struct areaWidgetClass { @@ -41,11 +41,14 @@ struct uiArea { uiAreaHandler *ah; gboolean scrolling; - intmax_t scrollWidth; - intmax_t scrollHeight; + int scrollWidth; + int scrollHeight; // note that this is a pointer; see above - clickCounter *cc; + uiprivClickCounter *cc; + + // for user window drags + GdkEventButton *dragevent; }; G_DEFINE_TYPE(areaWidget, areaWidget, GTK_TYPE_DRAWING_AREA) @@ -65,7 +68,7 @@ static void areaWidget_init(areaWidget *aw) gtk_widget_set_can_focus(GTK_WIDGET(aw), TRUE); - clickCounterReset(&(aw->cc)); + uiprivClickCounterReset(&(aw->cc)); } static void areaWidget_dispose(GObject *obj) @@ -89,6 +92,9 @@ static void areaWidget_size_allocate(GtkWidget *w, GtkAllocation *allocation) if (!a->scrolling) // we must redraw everything on resize because Windows requires it + // TODO https://developer.gnome.org/gtk3/3.10/GtkWidget.html#gtk-widget-set-redraw-on-allocate ? + // TODO drop this rule; it was stupid and documenting this was stupid — let platforms where it matters do it on their own + // TODO or do we not, for parity of performance? gtk_widget_queue_resize(w); } @@ -116,7 +122,8 @@ static gboolean areaWidget_draw(GtkWidget *w, cairo_t *cr) uiAreaDrawParams dp; double clipX0, clipY0, clipX1, clipY1; - dp.Context = newContext(cr); + dp.Context = uiprivNewContext(cr, + gtk_widget_get_style_context(a->widget)); loadAreaSize(a, &(dp.AreaWidth), &(dp.AreaHeight)); @@ -129,7 +136,7 @@ static gboolean areaWidget_draw(GtkWidget *w, cairo_t *cr) // no need to save or restore the graphics state to reset transformations; GTK+ does that for us (*(a->ah->Draw))(a->ah, a, &dp); - freeContext(dp.Context); + uiprivFreeContext(dp.Context); return FALSE; } @@ -251,14 +258,21 @@ static gboolean areaWidget_button_press_event(GtkWidget *w, GdkEventButton *e) "gtk-double-click-distance", &maxDistance, NULL); // don't unref settings; it's transfer-none (thanks gregier in irc.gimp.net/#gtk+) - me.Count = clickCounterClick(a->cc, me.Down, + // e->time is guint32 + // e->x and e->y are floating-point; just make them 32-bit integers + // maxTime and maxDistance... are gint, which *should* fit, hopefully... + me.Count = uiprivClickCounterClick(a->cc, me.Down, e->x, e->y, e->time, maxTime, maxDistance, maxDistance); me.Down = e->button; me.Up = 0; + + // and set things up for window drags + a->dragevent = e; finishMouseEvent(a, &me, e->button, e->x, e->y, e->state, e->window); + a->dragevent = NULL; return GDK_EVENT_PROPAGATE; } @@ -295,7 +309,7 @@ static gboolean onCrossing(areaWidget *aw, int left) uiArea *a = aw->a; (*(a->ah->MouseCrossed))(a->ah, a, left); - clickCounterReset(a->cc); + uiprivClickCounterReset(a->cc); return GDK_EVENT_PROPAGATE; } @@ -397,7 +411,7 @@ static int areaKeyEvent(uiArea *a, int up, GdkEventKey *e) goto keyFound; } - if (fromScancode(e->hardware_keycode - 8, &ke)) + if (uiprivFromScancode(e->hardware_keycode - 8, &ke)) goto keyFound; // no supported key found; treat as unhandled @@ -482,10 +496,10 @@ static void areaWidget_class_init(areaWidgetClass *class) uiUnixControlAllDefaults(uiArea) -void uiAreaSetSize(uiArea *a, intmax_t width, intmax_t height) +void uiAreaSetSize(uiArea *a, int width, int height) { if (!a->scrolling) - complain("attempt to call uiAreaSetSize() on a non-scrolling uiArea"); + uiprivUserBug("You cannot call uiAreaSetSize() on a non-scrolling uiArea. (area: %p)", a); a->scrollWidth = width; a->scrollHeight = height; gtk_widget_queue_resize(a->areaWidget); @@ -502,6 +516,76 @@ void uiAreaScrollTo(uiArea *a, double x, double y, double width, double height) // TODO adjust adjustments and find source for that } +void uiAreaBeginUserWindowMove(uiArea *a) +{ + GtkWidget *toplevel; + + if (a->dragevent == NULL) + uiprivUserBug("cannot call uiAreaBeginUserWindowMove() outside of a Mouse() with Down != 0"); + // TODO don't we have a libui function for this? did I scrap it? + // TODO widget or areaWidget? + toplevel = gtk_widget_get_toplevel(a->widget); + if (toplevel == NULL) { + // TODO + return; + } + // the docs say to do this + if (!gtk_widget_is_toplevel(toplevel)) { + // TODO + return; + } + if (!GTK_IS_WINDOW(toplevel)) { + // TODO + return; + } + gtk_window_begin_move_drag(GTK_WINDOW(toplevel), + a->dragevent->button, + a->dragevent->x_root, // TODO are these correct? + a->dragevent->y_root, + a->dragevent->time); +} + +static const GdkWindowEdge edges[] = { + [uiWindowResizeEdgeLeft] = GDK_WINDOW_EDGE_WEST, + [uiWindowResizeEdgeTop] = GDK_WINDOW_EDGE_NORTH, + [uiWindowResizeEdgeRight] = GDK_WINDOW_EDGE_EAST, + [uiWindowResizeEdgeBottom] = GDK_WINDOW_EDGE_SOUTH, + [uiWindowResizeEdgeTopLeft] = GDK_WINDOW_EDGE_NORTH_WEST, + [uiWindowResizeEdgeTopRight] = GDK_WINDOW_EDGE_NORTH_EAST, + [uiWindowResizeEdgeBottomLeft] = GDK_WINDOW_EDGE_SOUTH_WEST, + [uiWindowResizeEdgeBottomRight] = GDK_WINDOW_EDGE_SOUTH_EAST, +}; + +void uiAreaBeginUserWindowResize(uiArea *a, uiWindowResizeEdge edge) +{ + GtkWidget *toplevel; + + if (a->dragevent == NULL) + uiprivUserBug("cannot call uiAreaBeginUserWindowResize() outside of a Mouse() with Down != 0"); + // TODO don't we have a libui function for this? did I scrap it? + // TODO widget or areaWidget? + toplevel = gtk_widget_get_toplevel(a->widget); + if (toplevel == NULL) { + // TODO + return; + } + // the docs say to do this + if (!gtk_widget_is_toplevel(toplevel)) { + // TODO + return; + } + if (!GTK_IS_WINDOW(toplevel)) { + // TODO + return; + } + gtk_window_begin_resize_drag(GTK_WINDOW(toplevel), + edges[edge], + a->dragevent->button, + a->dragevent->x_root, // TODO are these correct? + a->dragevent->y_root, + a->dragevent->time); +} + uiArea *uiNewArea(uiAreaHandler *ah) { uiArea *a; @@ -522,7 +606,7 @@ uiArea *uiNewArea(uiAreaHandler *ah) return a; } -uiArea *uiNewScrollingArea(uiAreaHandler *ah, intmax_t width, intmax_t height) +uiArea *uiNewScrollingArea(uiAreaHandler *ah, int width, int height) { uiArea *a; diff --git a/unix/attrstr.c b/unix/attrstr.c new file mode 100644 index 00000000..f543943b --- /dev/null +++ b/unix/attrstr.c @@ -0,0 +1,145 @@ +// 12 february 2017 +#include "uipriv_unix.h" +#include "attrstr.h" + +// TODO pango alpha attributes turn 0 into 65535 :| + +// TODO make this name less generic? +struct foreachParams { + PangoAttrList *attrs; +}; + +static void addattr(struct foreachParams *p, size_t start, size_t end, PangoAttribute *attr) +{ + if (attr == NULL) // in case of a future attribute + return; + attr->start_index = start; + attr->end_index = end; + pango_attr_list_insert(p->attrs, attr); +} + +static uiForEach processAttribute(const uiAttributedString *s, const uiAttribute *attr, size_t start, size_t end, void *data) +{ + struct foreachParams *p = (struct foreachParams *) data; + double r, g, b, a; + PangoUnderline underline; + uiUnderlineColor colorType; + const uiOpenTypeFeatures *features; + GString *featurestr; + + switch (uiAttributeGetType(attr)) { + case uiAttributeTypeFamily: + addattr(p, start, end, + pango_attr_family_new(uiAttributeFamily(attr))); + break; + case uiAttributeTypeSize: + addattr(p, start, end, + pango_attr_size_new(cairoToPango(uiAttributeSize(attr)))); + break; + case uiAttributeTypeWeight: + // TODO reverse the misalignment from drawtext.c if it is corrected + addattr(p, start, end, + pango_attr_weight_new(uiprivWeightToPangoWeight(uiAttributeWeight(attr)))); + break; + case uiAttributeTypeItalic: + addattr(p, start, end, + pango_attr_style_new(uiprivItalicToPangoStyle(uiAttributeItalic(attr)))); + break; + case uiAttributeTypeStretch: + addattr(p, start, end, + pango_attr_stretch_new(uiprivStretchToPangoStretch(uiAttributeStretch(attr)))); + break; + case uiAttributeTypeColor: + uiAttributeColor(attr, &r, &g, &b, &a); + addattr(p, start, end, + pango_attr_foreground_new( + (guint16) (r * 65535.0), + (guint16) (g * 65535.0), + (guint16) (b * 65535.0))); + addattr(p, start, end, + uiprivFUTURE_pango_attr_foreground_alpha_new( + (guint16) (a * 65535.0))); + break; + case uiAttributeTypeBackground: + // TODO make sure this works properly with line paragraph spacings (after figuring out what that means, of course) + uiAttributeColor(attr, &r, &g, &b, &a); + addattr(p, start, end, + pango_attr_background_new( + (guint16) (r * 65535.0), + (guint16) (g * 65535.0), + (guint16) (b * 65535.0))); + addattr(p, start, end, + uiprivFUTURE_pango_attr_background_alpha_new( + (guint16) (a * 65535.0))); + break; + case uiAttributeTypeUnderline: + switch (uiAttributeUnderline(attr)) { + case uiUnderlineNone: + underline = PANGO_UNDERLINE_NONE; + break; + case uiUnderlineSingle: + underline = PANGO_UNDERLINE_SINGLE; + break; + case uiUnderlineDouble: + underline = PANGO_UNDERLINE_DOUBLE; + break; + case uiUnderlineSuggestion: + underline = PANGO_UNDERLINE_ERROR; + break; + } + addattr(p, start, end, + pango_attr_underline_new(underline)); + break; + case uiAttributeTypeUnderlineColor: + uiAttributeUnderlineColor(attr, &colorType, &r, &g, &b, &a); + switch (colorType) { + case uiUnderlineColorCustom: + addattr(p, start, end, + pango_attr_underline_color_new( + (guint16) (r * 65535.0), + (guint16) (g * 65535.0), + (guint16) (b * 65535.0))); + break; + case uiUnderlineColorSpelling: + // TODO GtkTextView style property error-underline-color + addattr(p, start, end, + pango_attr_underline_color_new(65535, 0, 0)); + break; + case uiUnderlineColorGrammar: + // TODO find a more appropriate color + addattr(p, start, end, + pango_attr_underline_color_new(0, 65535, 0)); + break; + case uiUnderlineColorAuxiliary: + // TODO find a more appropriate color + addattr(p, start, end, + pango_attr_underline_color_new(0, 0, 65535)); + break; + } + break; + case uiAttributeTypeFeatures: + // only generate an attribute if the features object is not NULL + // TODO state that this is allowed + features = uiAttributeFeatures(attr); + if (features == NULL) + break; + featurestr = uiprivOpenTypeFeaturesToPangoCSSFeaturesString(features); + addattr(p, start, end, + uiprivFUTURE_pango_attr_font_features_new(featurestr->str)); + g_string_free(featurestr, TRUE); + break; + default: + // TODO complain + ; + } + return uiForEachContinue; +} + +PangoAttrList *uiprivAttributedStringToPangoAttrList(uiDrawTextLayoutParams *p) +{ + struct foreachParams fep; + + fep.attrs = pango_attr_list_new(); + uiAttributedStringForEachAttribute(p->String, processAttribute, &fep); + return fep.attrs; +} diff --git a/unix/attrstr.h b/unix/attrstr.h new file mode 100644 index 00000000..984ac1f5 --- /dev/null +++ b/unix/attrstr.h @@ -0,0 +1,20 @@ +// 11 march 2018 +#include "../common/attrstr.h" + +// See https://developer.gnome.org/pango/1.30/pango-Cairo-Rendering.html#pango-Cairo-Rendering.description +// For the conversion, see https://developer.gnome.org/pango/1.30/pango-Glyph-Storage.html#pango-units-to-double and https://developer.gnome.org/pango/1.30/pango-Glyph-Storage.html#pango-units-from-double +#define pangoToCairo(pango) (pango_units_to_double(pango)) +#define cairoToPango(cairo) (pango_units_from_double(cairo)) + +// opentype.c +extern GString *uiprivOpenTypeFeaturesToPangoCSSFeaturesString(const uiOpenTypeFeatures *otf); + +// fontmatch.c +extern PangoWeight uiprivWeightToPangoWeight(uiTextWeight w); +extern PangoStyle uiprivItalicToPangoStyle(uiTextItalic i); +extern PangoStretch uiprivStretchToPangoStretch(uiTextStretch s); +extern PangoFontDescription *uiprivFontDescriptorToPangoFontDescription(const uiFontDescriptor *uidesc); +extern void uiprivFontDescriptorFromPangoFontDescription(PangoFontDescription *pdesc, uiFontDescriptor *uidesc); + +// attrstr.c +extern PangoAttrList *uiprivAttributedStringToPangoAttrList(uiDrawTextLayoutParams *p); diff --git a/unix/box.c b/unix/box.c index 47efe278..82cf2dd1 100644 --- a/unix/box.c +++ b/unix/box.c @@ -88,7 +88,7 @@ void uiBoxAppend(uiBox *b, uiControl *c, int stretchy) g_array_append_val(b->controls, bc); } -void uiBoxDelete(uiBox *b, uintmax_t index) +void uiBoxDelete(uiBox *b, int index) { struct boxChild *bc; GtkWidget *widget; @@ -119,9 +119,9 @@ void uiBoxSetPadded(uiBox *b, int padded) b->padded = padded; if (b->padded) if (b->vertical) - gtk_box_set_spacing(b->box, gtkYPadding); + gtk_box_set_spacing(b->box, uiprivGTKYPadding); else - gtk_box_set_spacing(b->box, gtkXPadding); + gtk_box_set_spacing(b->box, uiprivGTKXPadding); else gtk_box_set_spacing(b->box, 0); } diff --git a/unix/cellrendererbutton.c b/unix/cellrendererbutton.c new file mode 100644 index 00000000..608cd61d --- /dev/null +++ b/unix/cellrendererbutton.c @@ -0,0 +1,332 @@ +// 28 june 2016 +#include "uipriv_unix.h" + +// TODOs +// - it's a rather tight fit +// - selected row text color is white (TODO not on 3.22) +// - accessibility +// - right side too big? (TODO reverify) + +#define cellRendererButtonType (cellRendererButton_get_type()) +#define cellRendererButton(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), cellRendererButtonType, cellRendererButton)) +#define isCellRendererButton(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), cellRendererButtonType)) +#define cellRendererButtonClass(class) (G_TYPE_CHECK_CLASS_CAST((class), cellRendererButtonType, cellRendererButtonClass)) +#define isCellRendererButtonClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), cellRendererButton)) +#define getCellRendererButtonClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), cellRendererButtonType, cellRendererButtonClass)) + +typedef struct cellRendererButton cellRendererButton; +typedef struct cellRendererButtonClass cellRendererButtonClass; + +struct cellRendererButton { + GtkCellRenderer parent_instance; + char *text; +}; + +struct cellRendererButtonClass { + GtkCellRendererClass parent_class; +}; + +G_DEFINE_TYPE(cellRendererButton, cellRendererButton, GTK_TYPE_CELL_RENDERER) + +static void cellRendererButton_init(cellRendererButton *c) +{ + g_object_set(c, "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL); + // the standard cell renderers all do this + gtk_cell_renderer_set_padding(GTK_CELL_RENDERER(c), 2, 2); +} + +static void cellRendererButton_dispose(GObject *obj) +{ + G_OBJECT_CLASS(cellRendererButton_parent_class)->dispose(obj); +} + +static void cellRendererButton_finalize(GObject *obj) +{ + cellRendererButton *c = cellRendererButton(obj); + + if (c->text != NULL) { + g_free(c->text); + c->text = NULL; + } + G_OBJECT_CLASS(cellRendererButton_parent_class)->finalize(obj); +} + +static GtkSizeRequestMode cellRendererButton_get_request_mode(GtkCellRenderer *r) +{ + return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; +} + +// this is basically what GtkCellRendererToggle did in 3.10 and does in 3.20, as well as what the Foreign Drawing gtk3-demo demo does +// TODO how does this seem to work with highlight on 3.22, and does that work with 3.10 too +static GtkStyleContext *setButtonStyle(GtkWidget *widget) +{ + GtkStyleContext *base, *context; + GtkWidgetPath *path; + + base = gtk_widget_get_style_context(widget); + context = gtk_style_context_new(); + + path = gtk_widget_path_copy(gtk_style_context_get_path(base)); + gtk_widget_path_append_type(path, G_TYPE_NONE); + if (!uiprivFUTURE_gtk_widget_path_iter_set_object_name(path, -1, "button")) + // not on 3.20; try the type + gtk_widget_path_iter_set_object_type(path, -1, GTK_TYPE_BUTTON); + + gtk_style_context_set_path(context, path); + gtk_style_context_set_parent(context, base); + // the gtk3-demo example (which says we need to do this) uses gtk_widget_path_iter_get_state(path, -1) but that's not available until 3.14 + // TODO make a future for that too + gtk_style_context_set_state(context, gtk_style_context_get_state(base)); + gtk_widget_path_unref(path); + + // and if the above widget path screwery stil doesn't work, this will + gtk_style_context_add_class(context, GTK_STYLE_CLASS_BUTTON); + + return context; +} + +void unsetButtonStyle(GtkStyleContext *context) +{ + g_object_unref(context); +} + +// this is based on what GtkCellRendererText in GTK+ 3.22.30 does +// TODO compare to 3.10.9 (https://gitlab.gnome.org/GNOME/gtk/blob/3.10.9/gtk/gtkcellrenderertext.c) +static PangoLayout *cellRendererButtonPangoLayout(cellRendererButton *c, GtkWidget *widget) +{ + PangoLayout *layout; + + layout = gtk_widget_create_pango_layout(widget, c->text); + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE); + pango_layout_set_width(layout, -1); + pango_layout_set_wrap(layout, PANGO_WRAP_CHAR); + pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER); + return layout; +} + +// this is based on what GtkCellRendererText in GTK+ 3.22.30 does +// TODO compare to 3.10.9 (https://gitlab.gnome.org/GNOME/gtk/blob/3.10.9/gtk/gtkcellrenderertext.c) +static void cellRendererButtonSize(cellRendererButton *c, GtkWidget *widget, PangoLayout *layout, const GdkRectangle *cell_area, gint *xoff, gint *yoff, gint *width, gint *height) +{ + PangoRectangle rect; + gint xpad, ypad; + gfloat xalign, yalign; + + gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(c), &xpad, &ypad); + pango_layout_get_pixel_extents(layout, NULL, &rect); + if (rect.width > cell_area->width - (2 * xpad)) + rect.width = cell_area->width - (2 * xpad); + if (rect.height > cell_area->height - (2 * ypad)) + rect.height = cell_area->height - (2 * ypad); + + gtk_cell_renderer_get_alignment(GTK_CELL_RENDERER(c), &xalign, &yalign); + if (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL) + xalign = 1.0 - xalign; + if (xoff != NULL) { + *xoff = cell_area->width - (rect.width + (2 * xpad)); + *xoff = (gint) ((gfloat) (*xoff) * xalign); + } + if (yoff != NULL) { + *yoff = cell_area->height - (rect.height + (2 * ypad)); + *yoff = (gint) ((gfloat) (*yoff) * yalign); + if (*yoff < 0) + *yoff = 0; + } + if (width != NULL) + *width = rect.width - (2 * xpad); + if (height != NULL) + *height = rect.height - (2 * ypad); +} + +// this is based on what GtkCellRendererText in GTK+ 3.22.30 does +// TODO compare to 3.10.9 (https://gitlab.gnome.org/GNOME/gtk/blob/3.10.9/gtk/gtkcellrenderertext.c) +static void cellRendererButton_get_preferred_width(GtkCellRenderer *r, GtkWidget *widget, gint *minimum, gint *natural) +{ + cellRendererButton *c = cellRendererButton(r); + gint xpad; + PangoLayout *layout; + PangoRectangle rect; + gint out; + + gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(c), &xpad, NULL); + + layout = cellRendererButtonPangoLayout(c, widget); + pango_layout_get_extents(layout, NULL, &rect); + g_object_unref(layout); + + out = PANGO_PIXELS_CEIL(rect.width) + (2 * xpad); + if (rect.x > 0) + out += rect.x; + if (minimum != NULL) + *minimum = out; + if (natural != NULL) + *natural = out; +} + +// this is based on what GtkCellRendererText in GTK+ 3.22.30 does +// TODO compare to 3.10.9 (https://gitlab.gnome.org/GNOME/gtk/blob/3.10.9/gtk/gtkcellrenderertext.c) +static void cellRendererButton_get_preferred_height_for_width(GtkCellRenderer *r, GtkWidget *widget, gint width, gint *minimum, gint *natural) +{ + cellRendererButton *c = cellRendererButton(r); + gint xpad, ypad; + PangoLayout *layout; + gint height; + gint out; + + gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(c), &xpad, &ypad); + + layout = cellRendererButtonPangoLayout(c, widget); + pango_layout_set_width(layout, (width + (xpad * 2)) * PANGO_SCALE); + pango_layout_get_pixel_size(layout, NULL, &height); + g_object_unref(layout); + + out = height + (ypad * 2); + if (minimum != NULL) + *minimum = out; + if (natural != NULL) + *natural = out; +} + +// this is based on what GtkCellRendererText in GTK+ 3.22.30 does +// TODO compare to 3.10.9 (https://gitlab.gnome.org/GNOME/gtk/blob/3.10.9/gtk/gtkcellrenderertext.c) +static void cellRendererButton_get_preferred_height(GtkCellRenderer *r, GtkWidget *widget, gint *minimum, gint *natural) +{ + gint width; + + gtk_cell_renderer_get_preferred_width(r, widget, &width, NULL); + gtk_cell_renderer_get_preferred_height_for_width(r, widget, width, minimum, natural); +} + +// this is based on what GtkCellRendererText in GTK+ 3.22.30 does +// TODO compare to 3.10.9 (https://gitlab.gnome.org/GNOME/gtk/blob/3.10.9/gtk/gtkcellrenderertext.c) +static void cellRendererButton_get_aligned_area(GtkCellRenderer *r, GtkWidget *widget, GtkCellRendererState flags, const GdkRectangle *cell_area, GdkRectangle *aligned_area) +{ + cellRendererButton *c = cellRendererButton(r); + PangoLayout *layout; + gint xoff, yoff; + gint width, height; + + layout = cellRendererButtonPangoLayout(c, widget); + cellRendererButtonSize(c, widget, layout, cell_area, + &xoff, &yoff, &width, &height); + + aligned_area->x = cell_area->x + xoff; + aligned_area->y = cell_area->y + yoff; + aligned_area->width = width; + aligned_area->height = height; + + g_object_unref(layout); +} + +// this is based on both what GtkCellRendererText on 3.22.30 does and what GtkCellRendererToggle does (TODO verify the latter; both on 3.10.9) +static void cellRendererButton_render(GtkCellRenderer *r, cairo_t *cr, GtkWidget *widget, const GdkRectangle *background_area, const GdkRectangle *cell_area, GtkCellRendererState flags) +{ + cellRendererButton *c = cellRendererButton(r); + gint xpad, ypad; + GdkRectangle alignedArea; + gint xoff, yoff; + GtkStyleContext *context; + PangoLayout *layout; + PangoRectangle rect; + + gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(c), &xpad, &ypad); + layout = cellRendererButtonPangoLayout(c, widget); + cellRendererButtonSize(c, widget, layout, cell_area, + &xoff, &yoff, NULL, NULL); + + context = setButtonStyle(widget); + + gtk_render_background(context, cr, + background_area->x + xpad, + background_area->y + ypad, + background_area->width - (xpad * 2), + background_area->height - (ypad * 2)); + gtk_render_frame(context, cr, + background_area->x + xpad, + background_area->y + ypad, + background_area->width - (xpad * 2), + background_area->height - (ypad * 2)); + + pango_layout_get_pixel_extents(layout, NULL, &rect); + xoff -= rect.x; + gtk_render_layout(context, cr, + cell_area->x + xoff + xpad, + cell_area->y + yoff + ypad, + layout); + + unsetButtonStyle(context); + g_object_unref(layout); +} + +static guint clickedSignal; + +static gboolean cellRendererButton_activate(GtkCellRenderer *r, GdkEvent *e, GtkWidget *widget, const gchar *path, const GdkRectangle *background_area, const GdkRectangle *cell_area, GtkCellRendererState flags) +{ + g_signal_emit(r, clickedSignal, 0, path); + return TRUE; +} + +static GParamSpec *props[2] = { NULL, NULL }; + +static void cellRendererButton_set_property(GObject *object, guint prop, const GValue *value, GParamSpec *pspec) +{ + cellRendererButton *c = cellRendererButton(object); + + if (prop != 1) { + G_OBJECT_WARN_INVALID_PROPERTY_ID(c, prop, pspec); + return; + } + if (c->text != NULL) + g_free(c->text); + c->text = g_value_dup_string(value); + // GtkCellRendererText doesn't queue a redraw; we won't either +} + +static void cellRendererButton_get_property(GObject *object, guint prop, GValue *value, GParamSpec *pspec) +{ + cellRendererButton *c = cellRendererButton(object); + + if (prop != 1) { + G_OBJECT_WARN_INVALID_PROPERTY_ID(c, prop, pspec); + return; + } + g_value_set_string(value, c->text); +} + +static void cellRendererButton_class_init(cellRendererButtonClass *class) +{ + G_OBJECT_CLASS(class)->dispose = cellRendererButton_dispose; + G_OBJECT_CLASS(class)->finalize = cellRendererButton_finalize; + G_OBJECT_CLASS(class)->set_property = cellRendererButton_set_property; + G_OBJECT_CLASS(class)->get_property = cellRendererButton_get_property; + GTK_CELL_RENDERER_CLASS(class)->get_request_mode = cellRendererButton_get_request_mode; + GTK_CELL_RENDERER_CLASS(class)->get_preferred_width = cellRendererButton_get_preferred_width; + GTK_CELL_RENDERER_CLASS(class)->get_preferred_height_for_width = cellRendererButton_get_preferred_height_for_width; + GTK_CELL_RENDERER_CLASS(class)->get_preferred_height = cellRendererButton_get_preferred_height; + // don't provide a get_preferred_width_for_height() + GTK_CELL_RENDERER_CLASS(class)->get_aligned_area = cellRendererButton_get_aligned_area; + // don't provide a get_size() + GTK_CELL_RENDERER_CLASS(class)->render = cellRendererButton_render; + GTK_CELL_RENDERER_CLASS(class)->activate = cellRendererButton_activate; + // don't provide a start_editing() + + props[1] = g_param_spec_string("text", + "Text", + "Button text", + "", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties(G_OBJECT_CLASS(class), 2, props); + + clickedSignal = g_signal_new("clicked", + G_TYPE_FROM_CLASS(class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, + 1, G_TYPE_STRING); +} + +GtkCellRenderer *uiprivNewCellRendererButton(void) +{ + return GTK_CELL_RENDERER(g_object_new(cellRendererButtonType, NULL)); +} diff --git a/unix/child.c b/unix/child.c index b4a09677..240cc82a 100644 --- a/unix/child.c +++ b/unix/child.c @@ -3,7 +3,7 @@ // This file contains helpers for managing child controls. -struct child { +struct uiprivChild { uiControl *c; GtkWidget *widget; @@ -26,14 +26,14 @@ struct child { int flag; }; -struct child *newChild(uiControl *child, uiControl *parent, GtkContainer *parentContainer) +uiprivChild *uiprivNewChild(uiControl *child, uiControl *parent, GtkContainer *parentContainer) { - struct child *c; + uiprivChild *c; if (child == NULL) return NULL; - c = uiNew(struct child); + c = uiprivNew(uiprivChild); c->c = child; c->widget = GTK_WIDGET(uiControlHandle(c->c)); @@ -49,27 +49,27 @@ struct child *newChild(uiControl *child, uiControl *parent, GtkContainer *parent return c; } -struct child *newChildWithBox(uiControl *child, uiControl *parent, GtkContainer *parentContainer, int margined) +uiprivChild *uiprivNewChildWithBox(uiControl *child, uiControl *parent, GtkContainer *parentContainer, int margined) { - struct child *c; + uiprivChild *c; GtkWidget *box; if (child == NULL) return NULL; box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_widget_show(box); - c = newChild(child, parent, GTK_CONTAINER(box)); + c = uiprivNewChild(child, parent, GTK_CONTAINER(box)); gtk_widget_set_hexpand(c->widget, TRUE); gtk_widget_set_halign(c->widget, GTK_ALIGN_FILL); gtk_widget_set_vexpand(c->widget, TRUE); gtk_widget_set_valign(c->widget, GTK_ALIGN_FILL); c->box = box; gtk_container_add(parentContainer, c->box); - childSetMargined(c, margined); + uiprivChildSetMargined(c, margined); return c; } -void childRemove(struct child *c) +void uiprivChildRemove(uiprivChild *c) { uiControlSetParent(c->c, NULL); uiUnixControlSetContainer(uiUnixControl(c->c), c->parent, TRUE); @@ -82,39 +82,39 @@ void childRemove(struct child *c) if (c->box != NULL) gtk_widget_destroy(c->box); - uiFree(c); + uiprivFree(c); } -void childDestroy(struct child *c) +void uiprivChildDestroy(uiprivChild *c) { uiControl *child; child = c->c; - childRemove(c); + uiprivChildRemove(c); uiControlDestroy(child); } -GtkWidget *childWidget(struct child *c) +GtkWidget *uiprivChildWidget(uiprivChild *c) { return c->widget; } -int childFlag(struct child *c) +int uiprivChildFlag(uiprivChild *c) { return c->flag; } -void childSetFlag(struct child *c, int flag) +void uiprivChildSetFlag(uiprivChild *c, int flag) { c->flag = flag; } -GtkWidget *childBox(struct child *c) +GtkWidget *uiprivChildBox(uiprivChild *c) { return c->box; } -void childSetMargined(struct child *c, int margined) +void uiprivChildSetMargined(uiprivChild *c, int margined) { - setMargined(GTK_CONTAINER(c->box), margined); + uiprivSetMargined(GTK_CONTAINER(c->box), margined); } diff --git a/unix/colorbutton.c b/unix/colorbutton.c new file mode 100644 index 00000000..393b16f1 --- /dev/null +++ b/unix/colorbutton.c @@ -0,0 +1,80 @@ +// 15 may 2016 +#include "uipriv_unix.h" + +struct uiColorButton { + uiUnixControl c; + GtkWidget *widget; + GtkButton *button; + GtkColorButton *cb; + GtkColorChooser *cc; + void (*onChanged)(uiColorButton *, void *); + void *onChangedData; +}; + +uiUnixControlAllDefaults(uiColorButton) + +static void onColorSet(GtkColorButton *button, gpointer data) +{ + uiColorButton *b = uiColorButton(data); + + (*(b->onChanged))(b, b->onChangedData); +} + +static void defaultOnChanged(uiColorButton *b, void *data) +{ + // do nothing +} + +void uiColorButtonColor(uiColorButton *b, double *r, double *g, double *bl, double *a) +{ + GdkRGBA rgba; + + gtk_color_chooser_get_rgba(b->cc, &rgba); + *r = rgba.red; + *g = rgba.green; + *bl = rgba.blue; + *a = rgba.alpha; +} + +void uiColorButtonSetColor(uiColorButton *b, double r, double g, double bl, double a) +{ + GdkRGBA rgba; + + rgba.red = r; + rgba.green = g; + rgba.blue = bl; + rgba.alpha = a; + // no need to inhibit the signal; color-set is documented as only being sent when the user changes the color + gtk_color_chooser_set_rgba(b->cc, &rgba); +} + +void uiColorButtonOnChanged(uiColorButton *b, void (*f)(uiColorButton *, void *), void *data) +{ + b->onChanged = f; + b->onChangedData = data; +} + +uiColorButton *uiNewColorButton(void) +{ + uiColorButton *b; + GdkRGBA black; + + uiUnixNewControl(uiColorButton, b); + + // I'm not sure what the initial color is; set up a real one + black.red = 0.0; + black.green = 0.0; + black.blue = 0.0; + black.alpha = 1.0; + b->widget = gtk_color_button_new_with_rgba(&black); + b->button = GTK_BUTTON(b->widget); + b->cb = GTK_COLOR_BUTTON(b->widget); + b->cc = GTK_COLOR_CHOOSER(b->widget); + + gtk_color_chooser_set_use_alpha(b->cc, TRUE); + + g_signal_connect(b->widget, "color-set", G_CALLBACK(onColorSet), b); + uiColorButtonOnChanged(b, defaultOnChanged, NULL); + + return b; +} diff --git a/unix/combobox.c b/unix/combobox.c index c50b161e..6fed804b 100644 --- a/unix/combobox.c +++ b/unix/combobox.c @@ -13,7 +13,6 @@ struct uiCombobox { uiUnixControlAllDefaults(uiCombobox) -// TODO this is triggered when editing an editable combobox's text static void onChanged(GtkComboBox *cbox, gpointer data) { uiCombobox *c = uiCombobox(data); @@ -31,12 +30,12 @@ void uiComboboxAppend(uiCombobox *c, const char *text) gtk_combo_box_text_append(c->comboboxText, NULL, text); } -intmax_t uiComboboxSelected(uiCombobox *c) +int uiComboboxSelected(uiCombobox *c) { return gtk_combo_box_get_active(c->combobox); } -void uiComboboxSetSelected(uiCombobox *c, intmax_t n) +void uiComboboxSetSelected(uiCombobox *c, int n) { // we need to inhibit sending of ::changed because this WILL send a ::changed otherwise g_signal_handler_block(c->combobox, c->onSelectedSignal); @@ -50,13 +49,13 @@ void uiComboboxOnSelected(uiCombobox *c, void (*f)(uiCombobox *c, void *data), v c->onSelectedData = data; } -static uiCombobox *finishNewCombobox(GtkWidget *(*newfunc)(void)) +uiCombobox *uiNewCombobox(void) { uiCombobox *c; uiUnixNewControl(uiCombobox, c); - c->widget = (*newfunc)(); + c->widget = gtk_combo_box_text_new(); c->combobox = GTK_COMBO_BOX(c->widget); c->comboboxText = GTK_COMBO_BOX_TEXT(c->widget); @@ -65,13 +64,3 @@ static uiCombobox *finishNewCombobox(GtkWidget *(*newfunc)(void)) return c; } - -uiCombobox *uiNewCombobox(void) -{ - return finishNewCombobox(gtk_combo_box_text_new); -} - -uiCombobox *uiNewEditableCombobox(void) -{ - return finishNewCombobox(gtk_combo_box_text_new_with_entry); -} diff --git a/unix/datetimepicker.c b/unix/datetimepicker.c index 2093e0e7..8ca3a274 100644 --- a/unix/datetimepicker.c +++ b/unix/datetimepicker.c @@ -1,21 +1,20 @@ // 4 september 2015 #include "uipriv_unix.h" -// TODO imitate gnome-calendar's day/month/year entries? -// TODO 24-hour time -// TODO don't assume all locales use : +// LONGTERM imitate gnome-calendar's day/month/year entries above the calendar +// LONGTERM allow entering a 24-hour hour in the hour spinbutton and adjust accordingly -#define dateTimePickerWidgetType (dateTimePickerWidget_get_type()) -#define dateTimePickerWidget(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), dateTimePickerWidgetType, dateTimePickerWidget)) -#define isDateTimePickerWidget(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), dateTimePickerWidgetType)) -#define dateTimePickerWidgetClass(class) (G_TYPE_CHECK_CLASS_CAST((class), dateTimePickerWidgetType, dateTimePickerWidgetClass)) -#define isDateTimePickerWidgetClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), dateTimePickerWidget)) -#define getDateTimePickerWidgetClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), dateTimePickerWidgetType, dateTimePickerWidgetClass)) +#define uiprivDateTimePickerWidgetType (uiprivDateTimePickerWidget_get_type()) +#define uiprivDateTimePickerWidget(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), uiprivDateTimePickerWidgetType, uiprivDateTimePickerWidget)) +#define isDateTimePickerWidget(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), uiprivDateTimePickerWidgetType)) +#define uiprivDateTimePickerWidgetClass(class) (G_TYPE_CHECK_CLASS_CAST((class), uiprivDateTimePickerWidgetType, uiprivDateTimePickerWidgetClass)) +#define isDateTimePickerWidgetClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), uiprivDateTimePickerWidget)) +#define getDateTimePickerWidgetClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), uiprivDateTimePickerWidgetType, uiprivDateTimePickerWidgetClass)) -typedef struct dateTimePickerWidget dateTimePickerWidget; -typedef struct dateTimePickerWidgetClass dateTimePickerWidgetClass; +typedef struct uiprivDateTimePickerWidget uiprivDateTimePickerWidget; +typedef struct uiprivDateTimePickerWidgetClass uiprivDateTimePickerWidgetClass; -struct dateTimePickerWidget { +struct uiprivDateTimePickerWidget { GtkToggleButton parent_instance; gulong toggledSignal; @@ -32,6 +31,7 @@ struct dateTimePickerWidget { GtkWidget *seconds; GtkWidget *ampm; + gulong calendarBlock; gulong hoursBlock; gulong minutesBlock; gulong secondsBlock; @@ -41,11 +41,11 @@ struct dateTimePickerWidget { GdkDevice *mouse; }; -struct dateTimePickerWidgetClass { +struct uiprivDateTimePickerWidgetClass { GtkToggleButtonClass parent_class; }; -G_DEFINE_TYPE(dateTimePickerWidget, dateTimePickerWidget, GTK_TYPE_TOGGLE_BUTTON) +G_DEFINE_TYPE(uiprivDateTimePickerWidget, uiprivDateTimePickerWidget, GTK_TYPE_TOGGLE_BUTTON) static int realSpinValue(GtkSpinButton *spinButton) { @@ -65,7 +65,7 @@ static void setRealSpinValue(GtkSpinButton *spinButton, int value, gulong block) g_signal_handler_unblock(spinButton, block); } -static GDateTime *selected(dateTimePickerWidget *d) +static GDateTime *selected(uiprivDateTimePickerWidget *d) { // choose a day for which all times are likely to be valid for the default date in case we're only dealing with time guint year = 1970, month = 1, day = 1; @@ -85,7 +85,7 @@ static GDateTime *selected(dateTimePickerWidget *d) return g_date_time_new_local(year, month, day, hour, minute, second); } -static void setLabel(dateTimePickerWidget *d) +static void setLabel(uiprivDateTimePickerWidget *d) { GDateTime *dt; char *fmt; @@ -110,14 +110,17 @@ static void setLabel(dateTimePickerWidget *d) g_date_time_unref(dt); } -static void dateTimeChanged(dateTimePickerWidget *d) +static int changedSignal; + +static void dateTimeChanged(uiprivDateTimePickerWidget *d) { + g_signal_emit(d, changedSignal, 0); setLabel(d); - // TODO fire event here + // TODO fire event here instead? } // we don't want ::toggled to be sent again -static void setActive(dateTimePickerWidget *d, gboolean active) +static void setActive(uiprivDateTimePickerWidget *d, gboolean active) { g_signal_handler_block(d, d->toggledSignal); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d), active); @@ -125,7 +128,7 @@ static void setActive(dateTimePickerWidget *d, gboolean active) } // like startGrab() below, a lot of this is in the order that GtkComboBox does it -static void endGrab(dateTimePickerWidget *d) +static void endGrab(uiprivDateTimePickerWidget *d) { if (d->keyboard != NULL) gdk_device_ungrab(d->keyboard, GDK_CURRENT_TIME); @@ -135,7 +138,7 @@ static void endGrab(dateTimePickerWidget *d) d->mouse = NULL; } -static void hidePopup(dateTimePickerWidget *d) +static void hidePopup(uiprivDateTimePickerWidget *d) { endGrab(d); gtk_widget_hide(d->window); @@ -143,7 +146,7 @@ static void hidePopup(dateTimePickerWidget *d) } // this consolidates a good chunk of what GtkComboBox does -static gboolean startGrab(dateTimePickerWidget *d) +static gboolean startGrab(uiprivDateTimePickerWidget *d) { GdkDevice *dev; guint32 time; @@ -151,8 +154,19 @@ static gboolean startGrab(dateTimePickerWidget *d) GdkDevice *keyboard, *mouse; dev = gtk_get_current_event_device(); - if (dev == NULL) - return FALSE; // TODO + if (dev == NULL) { + // this is what GtkComboBox does + // since no device was set, just use the first available "master device" + GdkDisplay *disp; + GdkDeviceManager *dm; + GList *list; + + disp = gtk_widget_get_display(GTK_WIDGET(d)); + dm = gdk_display_get_device_manager(disp); + list = gdk_device_manager_list_devices(dm, GDK_DEVICE_TYPE_MASTER); + dev = (GdkDevice *) (list->data); + g_list_free(list); + } time = gtk_get_current_event_time(); keyboard = dev; @@ -187,12 +201,17 @@ static gboolean startGrab(dateTimePickerWidget *d) } // based on gtk_combo_box_list_position() in the GTK+ source code -static void allocationToScreen(dateTimePickerWidget *d, gint *x, gint *y) +static void allocationToScreen(uiprivDateTimePickerWidget *d, gint *x, gint *y) { GdkWindow *window; GtkAllocation a; + GtkRequisition aWin; + GdkScreen *screen; + GdkRectangle workarea; + int otherY; gtk_widget_get_allocation(GTK_WIDGET(d), &a); + gtk_widget_get_preferred_size(d->window, &aWin, NULL); *x = 0; *y = 0; if (!gtk_widget_get_has_window(GTK_WIDGET(d))) { @@ -202,12 +221,27 @@ static void allocationToScreen(dateTimePickerWidget *d, gint *x, gint *y) window = gtk_widget_get_window(GTK_WIDGET(d)); gdk_window_get_root_coords(window, *x, *y, x, y); if (gtk_widget_get_direction(GTK_WIDGET(d)) == GTK_TEXT_DIR_RTL) - *x += a.width; // TODO subtract target width - // TODO monitor detection + *x += a.width - aWin.width; + + // now adjust to prevent the box from going offscreen + screen = gtk_widget_get_screen(GTK_WIDGET(d)); + gdk_screen_get_monitor_workarea(screen, + gdk_screen_get_monitor_at_window(screen, window), + &workarea); + if (*x < workarea.x) // too far to the left? + *x = workarea.x; + else if (*x + aWin.width > (workarea.x + workarea.width)) // too far to the right? + *x = (workarea.x + workarea.width) - aWin.width; + // this isn't the same algorithm used by GtkComboBox + // first, get our two choices; *y for down and otherY for up + otherY = *y - aWin.height; *y += a.height; + // and use otherY if we're too low + if (*y + aWin.height >= workarea.y + workarea.height) + *y = otherY; } -static void showPopup(dateTimePickerWidget *d) +static void showPopup(uiprivDateTimePickerWidget *d) { GtkWidget *toplevel; gint x, y; @@ -229,7 +263,7 @@ static void showPopup(dateTimePickerWidget *d) static void onToggled(GtkToggleButton *b, gpointer data) { - dateTimePickerWidget *d = dateTimePickerWidget(b); + uiprivDateTimePickerWidget *d = uiprivDateTimePickerWidget(b); if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d))) showPopup(d); @@ -239,7 +273,7 @@ static void onToggled(GtkToggleButton *b, gpointer data) static gboolean grabBroken(GtkWidget *w, GdkEventGrabBroken *e, gpointer data) { - dateTimePickerWidget *d = dateTimePickerWidget(data); + uiprivDateTimePickerWidget *d = uiprivDateTimePickerWidget(data); hidePopup(d); return TRUE; // this is what GtkComboBox does @@ -247,7 +281,7 @@ static gboolean grabBroken(GtkWidget *w, GdkEventGrabBroken *e, gpointer data) static gboolean buttonReleased(GtkWidget *w, GdkEventButton *e, gpointer data) { - dateTimePickerWidget *d = dateTimePickerWidget(data); + uiprivDateTimePickerWidget *d = uiprivDateTimePickerWidget(data); int winx, winy; GtkAllocation wina; gboolean in; @@ -324,7 +358,7 @@ static gint ampmSpinboxInput(GtkSpinButton *sb, gpointer ptr, gpointer data) char firstAM, firstPM; text = gtk_entry_get_text(GTK_ENTRY(sb)); - // TODO don't use ASCII here for case insensitivity + // LONGTERM don't use ASCII here for case insensitivity firstAM = g_ascii_tolower(nl_langinfo(AM_STR)[0]); firstPM = g_ascii_tolower(nl_langinfo(PM_STR)[0]); for (; *text != '\0'; text++) @@ -352,12 +386,12 @@ static gboolean ampmSpinboxOutput(GtkSpinButton *sb, gpointer data) static void spinboxChanged(GtkSpinButton *sb, gpointer data) { - dateTimePickerWidget *d = dateTimePickerWidget(data); + uiprivDateTimePickerWidget *d = uiprivDateTimePickerWidget(data); dateTimeChanged(d); } -static GtkWidget *newSpinbox(dateTimePickerWidget *d, int min, int max, gint (*input)(GtkSpinButton *, gpointer, gpointer), gboolean (*output)(GtkSpinButton *, gpointer), gulong *block) +static GtkWidget *newSpinbox(uiprivDateTimePickerWidget *d, int min, int max, gint (*input)(GtkSpinButton *, gpointer, gpointer), gboolean (*output)(GtkSpinButton *, gpointer), gulong *block) { GtkWidget *sb; @@ -375,41 +409,60 @@ static GtkWidget *newSpinbox(dateTimePickerWidget *d, int min, int max, gint (*i static void dateChanged(GtkCalendar *c, gpointer data) { - dateTimePickerWidget *d = dateTimePickerWidget(data); + uiprivDateTimePickerWidget *d = uiprivDateTimePickerWidget(data); dateTimeChanged(d); } -static void setDateOnly(dateTimePickerWidget *d) +static void setDateOnly(uiprivDateTimePickerWidget *d) { d->hasTime = FALSE; gtk_container_remove(GTK_CONTAINER(d->box), d->timebox); } -static void setTimeOnly(dateTimePickerWidget *d) +static void setTimeOnly(uiprivDateTimePickerWidget *d) { d->hasDate = FALSE; gtk_container_remove(GTK_CONTAINER(d->box), d->calendar); } -static void dateTimePickerWidget_init(dateTimePickerWidget *d) +static void uiprivDateTimePickerWidget_setTime(uiprivDateTimePickerWidget *d, GDateTime *dt) { - GDateTime *dt; gint year, month, day; gint hour; - gulong calendarBlock; + // notice how we block signals from firing + if (d->hasDate) { + g_date_time_get_ymd(dt, &year, &month, &day); + month--; // GDateTime/GtkCalendar differences + g_signal_handler_block(d->calendar, d->calendarBlock); + gtk_calendar_select_month(GTK_CALENDAR(d->calendar), month, year); + gtk_calendar_select_day(GTK_CALENDAR(d->calendar), day); + g_signal_handler_unblock(d->calendar, d->calendarBlock); + } + if (d->hasTime) { + hour = g_date_time_get_hour(dt); + if (hour >= 12) { + hour -= 12; + setRealSpinValue(GTK_SPIN_BUTTON(d->ampm), 1, d->ampmBlock); + } + setRealSpinValue(GTK_SPIN_BUTTON(d->hours), hour, d->hoursBlock); + setRealSpinValue(GTK_SPIN_BUTTON(d->minutes), g_date_time_get_minute(dt), d->minutesBlock); + setRealSpinValue(GTK_SPIN_BUTTON(d->seconds), g_date_time_get_seconds(dt), d->secondsBlock); + } + g_date_time_unref(dt); +} + +static void uiprivDateTimePickerWidget_init(uiprivDateTimePickerWidget *d) +{ d->window = gtk_window_new(GTK_WINDOW_POPUP); gtk_window_set_resizable(GTK_WINDOW(d->window), FALSE); gtk_window_set_attached_to(GTK_WINDOW(d->window), GTK_WIDGET(d)); - // TODO set_keep_above()? gtk_window_set_decorated(GTK_WINDOW(d->window), FALSE); gtk_window_set_deletable(GTK_WINDOW(d->window), FALSE); gtk_window_set_type_hint(GTK_WINDOW(d->window), GDK_WINDOW_TYPE_HINT_COMBO); gtk_window_set_skip_taskbar_hint(GTK_WINDOW(d->window), TRUE); gtk_window_set_skip_pager_hint(GTK_WINDOW(d->window), TRUE); - // TODO accept_focus()? - // TODO focus_on_map()? gtk_window_set_has_resize_grip(GTK_WINDOW(d->window), FALSE); gtk_container_set_border_width(GTK_CONTAINER(d->window), 12); // and make it stand out a bit @@ -419,7 +472,7 @@ static void dateTimePickerWidget_init(dateTimePickerWidget *d) gtk_container_add(GTK_CONTAINER(d->window), d->box); d->calendar = gtk_calendar_new(); - calendarBlock = g_signal_connect(d->calendar, "day-selected", G_CALLBACK(dateChanged), d); + d->calendarBlock = g_signal_connect(d->calendar, "day-selected", G_CALLBACK(dateChanged), d); gtk_container_add(GTK_CONTAINER(d->box), d->calendar); d->timebox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); @@ -441,7 +494,7 @@ static void dateTimePickerWidget_init(dateTimePickerWidget *d) d->seconds = newSpinbox(d, 0, 59, NULL, zeroPadSpinbox, &(d->secondsBlock)); gtk_container_add(GTK_CONTAINER(d->timebox), d->seconds); - // TODO this should be the case, but that interferes with grabs + // LONGTERM this should be the case, but that interferes with grabs // switch to it when we can drop GTK+ 3.10 and use popovers #if 0 d->ampm = gtk_combo_box_text_new(); @@ -466,48 +519,109 @@ static void dateTimePickerWidget_init(dateTimePickerWidget *d) d->hasDate = TRUE; // set the current date/time - // notice how we block signals from firing - dt = g_date_time_new_now_local(); - g_date_time_get_ymd(dt, &year, &month, &day); - month--; // GDateTime/GtkCalendar differences - g_signal_handler_block(d->calendar, calendarBlock); - gtk_calendar_select_month(GTK_CALENDAR(d->calendar), month, year); - gtk_calendar_select_day(GTK_CALENDAR(d->calendar), day); - g_signal_handler_unblock(d->calendar, calendarBlock); - hour = g_date_time_get_hour(dt); - if (hour >= 12) { - hour -= 12; - setRealSpinValue(GTK_SPIN_BUTTON(d->ampm), 1, d->ampmBlock); + uiprivDateTimePickerWidget_setTime(d, g_date_time_new_now_local()); +} + +static void uiprivDateTimePickerWidget_dispose(GObject *obj) +{ + uiprivDateTimePickerWidget *d = uiprivDateTimePickerWidget(obj); + + if (d->window != NULL) { + gtk_widget_destroy(d->window); + d->window = NULL; } - setRealSpinValue(GTK_SPIN_BUTTON(d->hours), hour, d->hoursBlock); - setRealSpinValue(GTK_SPIN_BUTTON(d->minutes), g_date_time_get_minute(dt), d->minutesBlock); - setRealSpinValue(GTK_SPIN_BUTTON(d->seconds), g_date_time_get_seconds(dt), d->secondsBlock); + G_OBJECT_CLASS(uiprivDateTimePickerWidget_parent_class)->dispose(obj); +} + +static void uiprivDateTimePickerWidget_finalize(GObject *obj) +{ + G_OBJECT_CLASS(uiprivDateTimePickerWidget_parent_class)->finalize(obj); +} + +static void uiprivDateTimePickerWidget_class_init(uiprivDateTimePickerWidgetClass *class) +{ + G_OBJECT_CLASS(class)->dispose = uiprivDateTimePickerWidget_dispose; + G_OBJECT_CLASS(class)->finalize = uiprivDateTimePickerWidget_finalize; + + changedSignal = g_signal_new("changed", + G_TYPE_FROM_CLASS(class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, + 0); +} + +struct uiDateTimePicker { + uiUnixControl c; + GtkWidget *widget; + uiprivDateTimePickerWidget *d; + void (*onChanged)(uiDateTimePicker *, void *); + void *onChangedData; + gulong setBlock; +}; + +uiUnixControlAllDefaults(uiDateTimePicker) + +static void defaultOnChanged(uiDateTimePicker *d, void *data) +{ + // do nothing +} + +void uiDateTimePickerTime(uiDateTimePicker *d, struct tm *time) +{ + time_t t; + struct tm tmbuf; + GDateTime *dt; + + dt = selected(d->d); + t = g_date_time_to_unix(dt); g_date_time_unref(dt); + + // Copy time to minimize a race condition + // time.h functions use global non-thread-safe data + tmbuf = *localtime(&t); + memcpy(time, &tmbuf, sizeof (struct tm)); } -static void dateTimePickerWidget_dispose(GObject *obj) +void uiDateTimePickerSetTime(uiDateTimePicker *d, const struct tm *time) { - // TODO destroy window - G_OBJECT_CLASS(dateTimePickerWidget_parent_class)->dispose(obj); + time_t t; + struct tm tmbuf; + + // TODO find a better way to avoid this; possibly by removing the signal entirely, or the call to dateTimeChanged() (most likely both) + g_signal_handler_block(d->d, d->setBlock); + + // Copy time because mktime() modifies its argument + memcpy(&tmbuf, time, sizeof (struct tm)); + t = mktime(&tmbuf); + + uiprivDateTimePickerWidget_setTime(d->d, g_date_time_new_from_unix_local(t)); + dateTimeChanged(d->d); + + g_signal_handler_unblock(d->d, d->setBlock); } -static void dateTimePickerWidget_finalize(GObject *obj) +void uiDateTimePickerOnChanged(uiDateTimePicker *d, void (*f)(uiDateTimePicker *, void *), void *data) { - G_OBJECT_CLASS(dateTimePickerWidget_parent_class)->finalize(obj); + d->onChanged = f; + d->onChangedData = data; } -static void dateTimePickerWidget_class_init(dateTimePickerWidgetClass *class) +static void onChanged(uiprivDateTimePickerWidget *d, gpointer data) { - G_OBJECT_CLASS(class)->dispose = dateTimePickerWidget_dispose; - G_OBJECT_CLASS(class)->finalize = dateTimePickerWidget_finalize; + uiDateTimePicker *c; + + c = uiDateTimePicker(data); + (*(c->onChanged))(c, c->onChangedData); } static GtkWidget *newDTP(void) { GtkWidget *w; - w = GTK_WIDGET(g_object_new(dateTimePickerWidgetType, "label", "", NULL)); - setLabel(dateTimePickerWidget(w)); + w = GTK_WIDGET(g_object_new(uiprivDateTimePickerWidgetType, "label", "", NULL)); + setLabel(uiprivDateTimePickerWidget(w)); return w; } @@ -515,9 +629,9 @@ static GtkWidget *newDP(void) { GtkWidget *w; - w = GTK_WIDGET(g_object_new(dateTimePickerWidgetType, "label", "", NULL)); - setDateOnly(dateTimePickerWidget(w)); - setLabel(dateTimePickerWidget(w)); + w = GTK_WIDGET(g_object_new(uiprivDateTimePickerWidgetType, "label", "", NULL)); + setDateOnly(uiprivDateTimePickerWidget(w)); + setLabel(uiprivDateTimePickerWidget(w)); return w; } @@ -525,20 +639,12 @@ static GtkWidget *newTP(void) { GtkWidget *w; - w = GTK_WIDGET(g_object_new(dateTimePickerWidgetType, "label", "", NULL)); - setTimeOnly(dateTimePickerWidget(w)); - setLabel(dateTimePickerWidget(w)); + w = GTK_WIDGET(g_object_new(uiprivDateTimePickerWidgetType, "label", "", NULL)); + setTimeOnly(uiprivDateTimePickerWidget(w)); + setLabel(uiprivDateTimePickerWidget(w)); return w; } -struct uiDateTimePicker { - uiUnixControl c; - GtkWidget *widget; - dateTimePickerWidget *d; -}; - -uiUnixControlAllDefaults(uiDateTimePicker) - uiDateTimePicker *finishNewDateTimePicker(GtkWidget *(*fn)(void)) { uiDateTimePicker *d; @@ -546,7 +652,9 @@ uiDateTimePicker *finishNewDateTimePicker(GtkWidget *(*fn)(void)) uiUnixNewControl(uiDateTimePicker, d); d->widget = (*fn)(); - d->d = dateTimePickerWidget(d->widget); + d->d = uiprivDateTimePickerWidget(d->widget); + d->setBlock = g_signal_connect(d->widget, "changed", G_CALLBACK(onChanged), d); + uiDateTimePickerOnChanged(d, defaultOnChanged, NULL); return d; } diff --git a/unix/debug.c b/unix/debug.c new file mode 100644 index 00000000..fd97c9ed --- /dev/null +++ b/unix/debug.c @@ -0,0 +1,14 @@ +// 13 may 2016 +#include "uipriv_unix.h" + +// LONGTERM don't halt on release builds + +void uiprivRealBug(const char *file, const char *line, const char *func, const char *prefix, const char *format, va_list ap) +{ + char *a, *b; + + a = g_strdup_printf("[libui] %s:%s:%s() %s", file, line, func, prefix); + b = g_strdup_vprintf(format, ap); + g_critical("%s%s", a, b); + G_BREAKPOINT(); +} diff --git a/unix/draw.c b/unix/draw.c index 388de7be..a8f26d7f 100644 --- a/unix/draw.c +++ b/unix/draw.c @@ -2,18 +2,20 @@ #include "uipriv_unix.h" #include "draw.h" -uiDrawContext *newContext(cairo_t *cr) +uiDrawContext *uiprivNewContext(cairo_t *cr, GtkStyleContext *style) { uiDrawContext *c; - c = uiNew(uiDrawContext); + c = uiprivNew(uiDrawContext); c->cr = cr; + c->style = style; return c; } -void freeContext(uiDrawContext *c) +void uiprivFreeContext(uiDrawContext *c) { - uiFree(c); + // free neither cr nor style; we own neither + uiprivFree(c); } static cairo_pattern_t *mkbrush(uiDrawBrush *b) @@ -37,7 +39,7 @@ static cairo_pattern_t *mkbrush(uiDrawBrush *b) // case uiDrawBrushTypeImage: } if (cairo_pattern_status(pat) != CAIRO_STATUS_SUCCESS) - complain("error creating pattern in mkbrush(): %s", + uiprivImplBug("error creating pattern in mkbrush(): %s", cairo_status_to_string(cairo_pattern_status(pat))); switch (b->Type) { case uiDrawBrushTypeLinearGradient: @@ -57,7 +59,7 @@ void uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStro { cairo_pattern_t *pat; - runPath(path, c->cr); + uiprivRunPath(path, c->cr); pat = mkbrush(b); cairo_set_source(c->cr, pat); switch (p->Cap) { @@ -93,10 +95,10 @@ void uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b) { cairo_pattern_t *pat; - runPath(path, c->cr); + uiprivRunPath(path, c->cr); pat = mkbrush(b); cairo_set_source(c->cr, pat); - switch (pathFillMode(path)) { + switch (uiprivPathFillMode(path)) { case uiDrawFillModeWinding: cairo_set_fill_rule(c->cr, CAIRO_FILL_RULE_WINDING); break; @@ -112,14 +114,14 @@ void uiDrawTransform(uiDrawContext *c, uiDrawMatrix *m) { cairo_matrix_t cm; - m2c(m, &cm); + uiprivM2C(m, &cm); cairo_transform(c->cr, &cm); } void uiDrawClip(uiDrawContext *c, uiDrawPath *path) { - runPath(path, c->cr); - switch (pathFillMode(path)) { + uiprivRunPath(path, c->cr); + switch (uiprivPathFillMode(path)) { case uiDrawFillModeWinding: cairo_set_fill_rule(c->cr, CAIRO_FILL_RULE_WINDING); break; diff --git a/unix/draw.h b/unix/draw.h index dbd82ff5..d46d074f 100644 --- a/unix/draw.h +++ b/unix/draw.h @@ -3,11 +3,12 @@ // draw.c struct uiDrawContext { cairo_t *cr; + GtkStyleContext *style; }; // drawpath.c -extern void runPath(uiDrawPath *p, cairo_t *cr); -extern uiDrawFillMode pathFillMode(uiDrawPath *path); +extern void uiprivRunPath(uiDrawPath *p, cairo_t *cr); +extern uiDrawFillMode uiprivPathFillMode(uiDrawPath *path); // drawmatrix.c -extern void m2c(uiDrawMatrix *m, cairo_matrix_t *c); +extern void uiprivM2C(uiDrawMatrix *m, cairo_matrix_t *c); diff --git a/unix/drawmatrix.c b/unix/drawmatrix.c index 815caf49..ffb4db34 100644 --- a/unix/drawmatrix.c +++ b/unix/drawmatrix.c @@ -2,7 +2,7 @@ #include "uipriv_unix.h" #include "draw.h" -void m2c(uiDrawMatrix *m, cairo_matrix_t *c) +static void m2c(uiDrawMatrix *m, cairo_matrix_t *c) { c->xx = m->M11; c->yx = m->M12; @@ -12,6 +12,12 @@ void m2c(uiDrawMatrix *m, cairo_matrix_t *c) c->y0 = m->M32; } +// needed by uiDrawTransform() +void uiprivM2C(uiDrawMatrix *m, cairo_matrix_t *c) +{ + m2c(m, c); +} + static void c2m(cairo_matrix_t *c, uiDrawMatrix *m) { m->M11 = c->xx; @@ -22,11 +28,6 @@ static void c2m(cairo_matrix_t *c, uiDrawMatrix *m) m->M32 = c->y0; } -void uiDrawMatrixSetIdentity(uiDrawMatrix *m) -{ - setIdentity(m); -} - void uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y) { cairo_matrix_t c; @@ -42,13 +43,12 @@ void uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x double xt, yt; m2c(m, &c); - // TODO explain why the translation must come first xt = x; yt = y; - scaleCenter(xCenter, yCenter, &xt, &yt); + uiprivScaleCenter(xCenter, yCenter, &xt, &yt); cairo_matrix_translate(&c, xt, yt); cairo_matrix_scale(&c, x, y); - // TODO undo the translation? + cairo_matrix_translate(&c, -xt, -yt); c2m(&c, m); } @@ -59,14 +59,13 @@ void uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount) m2c(m, &c); cairo_matrix_translate(&c, x, y); cairo_matrix_rotate(&c, amount); - // TODO undo the translation? also cocoa backend cairo_matrix_translate(&c, -x, -y); c2m(&c, m); } void uiDrawMatrixSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount) { - fallbackSkew(m, x, y, xamount, yamount); + uiprivFallbackSkew(m, x, y, xamount, yamount); } void uiDrawMatrixMultiply(uiDrawMatrix *dest, uiDrawMatrix *src) diff --git a/unix/drawpath.c b/unix/drawpath.c index 1204315b..045660f5 100644 --- a/unix/drawpath.c +++ b/unix/drawpath.c @@ -28,7 +28,7 @@ uiDrawPath *uiDrawNewPath(uiDrawFillMode mode) { uiDrawPath *p; - p = uiNew(uiDrawPath); + p = uiprivNew(uiDrawPath); p->pieces = g_array_new(FALSE, TRUE, sizeof (struct piece)); p->fillMode = mode; return p; @@ -37,13 +37,13 @@ uiDrawPath *uiDrawNewPath(uiDrawFillMode mode) void uiDrawFreePath(uiDrawPath *p) { g_array_free(p->pieces, TRUE); - uiFree(p); + uiprivFree(p); } static void add(uiDrawPath *p, struct piece *piece) { if (p->ended) - complain("path ended in add()"); + uiprivUserBug("You cannot modify a uiDrawPath that has been ended. (path: %p)", p); g_array_append_vals(p->pieces, piece, 1); } @@ -61,8 +61,8 @@ void uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, d { struct piece piece; - if (sweep > 2 * M_PI) - sweep = 2 * M_PI; + if (sweep > 2 * uiPi) + sweep = 2 * uiPi; piece.type = newFigureArc; piece.d[0] = xCenter; piece.d[1] = yCenter; @@ -87,8 +87,8 @@ void uiDrawPathArcTo(uiDrawPath *p, double xCenter, double yCenter, double radiu { struct piece piece; - if (sweep > 2 * M_PI) - sweep = 2 * M_PI; + if (sweep > 2 * uiPi) + sweep = 2 * uiPi; piece.type = arcTo; piece.d[0] = xCenter; piece.d[1] = yCenter; @@ -138,14 +138,14 @@ void uiDrawPathEnd(uiDrawPath *p) p->ended = TRUE; } -void runPath(uiDrawPath *p, cairo_t *cr) +void uiprivRunPath(uiDrawPath *p, cairo_t *cr) { guint i; struct piece *piece; void (*arc)(cairo_t *, double, double, double, double, double); if (!p->ended) - complain("path not ended in runPath()"); + uiprivUserBug("You cannot draw with a uiDrawPath that has not been ended. (path: %p)", p); cairo_new_path(cr); for (i = 0; i < p->pieces->len; i++) { piece = &g_array_index(p->pieces, struct piece, i); @@ -193,7 +193,7 @@ void runPath(uiDrawPath *p, cairo_t *cr) } } -uiDrawFillMode pathFillMode(uiDrawPath *path) +uiDrawFillMode uiprivPathFillMode(uiDrawPath *path) { return path->fillMode; } diff --git a/unix/drawtext.c b/unix/drawtext.c index 597f2643..477e9ca3 100644 --- a/unix/drawtext.c +++ b/unix/drawtext.c @@ -1,328 +1,81 @@ -// 6 september 2015 +// 11 march 2018 #include "uipriv_unix.h" #include "draw.h" +#include "attrstr.h" -struct uiDrawFontFamilies { - PangoFontFamily **f; - int n; -}; - -uiDrawFontFamilies *uiDrawListFontFamilies(void) -{ - uiDrawFontFamilies *ff; - PangoFontMap *map; - - ff = uiNew(uiDrawFontFamilies); - map = pango_cairo_font_map_get_default(); - pango_font_map_list_families(map, &(ff->f), &(ff->n)); - // do not free map; it's a shared resource - return ff; -} - -uintmax_t uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff) -{ - return ff->n; -} - -char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, uintmax_t n) -{ - PangoFontFamily *f; - - f = ff->f[n]; - return uiUnixStrdupText(pango_font_family_get_name(f)); -} - -void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff) -{ - g_free(ff->f); - uiFree(ff); -} - -struct uiDrawTextFont { - PangoFont *f; -}; - -uiDrawTextFont *mkTextFont(PangoFont *f, gboolean ref) -{ - uiDrawTextFont *font; - - font = uiNew(uiDrawTextFont); - font->f = f; - if (ref) - g_object_ref(font->f); - return font; -} - -static const PangoWeight pangoWeights[] = { - [uiDrawTextWeightThin] = PANGO_WEIGHT_THIN, - [uiDrawTextWeightUltraLight] = PANGO_WEIGHT_ULTRALIGHT, - [uiDrawTextWeightLight] = PANGO_WEIGHT_LIGHT, - [uiDrawTextWeightBook] = PANGO_WEIGHT_BOOK, - [uiDrawTextWeightNormal] = PANGO_WEIGHT_NORMAL, - [uiDrawTextWeightMedium] = PANGO_WEIGHT_MEDIUM, - [uiDrawTextWeightSemiBold] = PANGO_WEIGHT_SEMIBOLD, - [uiDrawTextWeightBold] = PANGO_WEIGHT_BOLD, - [uiDrawTextWeightUtraBold] = PANGO_WEIGHT_ULTRABOLD, - [uiDrawTextWeightHeavy] = PANGO_WEIGHT_HEAVY, - [uiDrawTextWeightUltraHeavy] = PANGO_WEIGHT_ULTRAHEAVY, -}; - -static const PangoStyle pangoItalics[] = { - [uiDrawTextItalicNormal] = PANGO_STYLE_NORMAL, - [uiDrawTextItalicOblique] = PANGO_STYLE_OBLIQUE, - [uiDrawTextItalicItalic] = PANGO_STYLE_ITALIC, -}; - -static const PangoStretch pangoStretches[] = { - [uiDrawTextStretchUltraCondensed] = PANGO_STRETCH_ULTRA_CONDENSED, - [uiDrawTextStretchExtraCondensed] = PANGO_STRETCH_EXTRA_CONDENSED, - [uiDrawTextStretchCondensed] = PANGO_STRETCH_CONDENSED, - [uiDrawTextStretchSemiCondensed] = PANGO_STRETCH_SEMI_CONDENSED, - [uiDrawTextStretchNormal] = PANGO_STRETCH_NORMAL, - [uiDrawTextStretchSemiExpanded] = PANGO_STRETCH_SEMI_EXPANDED, - [uiDrawTextStretchExpanded] = PANGO_STRETCH_EXPANDED, - [uiDrawTextStretchExtraExpanded] = PANGO_STRETCH_EXTRA_EXPANDED, - [uiDrawTextStretchUltraExpanded] = PANGO_STRETCH_ULTRA_EXPANDED, +struct uiDrawTextLayout { + PangoLayout *layout; }; // we need a context for a few things // the documentation suggests creating cairo_t-specific, GdkScreen-specific, or even GtkWidget-specific contexts, but we can't really do that because we want our uiDrawTextFonts and uiDrawTextLayouts to be context-independent -// so this will have to do -// TODO really see if there's a better way instead; what do GDK and GTK+ do internally? gdk_pango_context_get()? -#define mkGenericPangoCairoContext() (pango_font_map_create_context(pango_cairo_font_map_get_default())) +// we could use pango_font_map_create_context(pango_cairo_font_map_get_default()) but that will ignore GDK-specific settings +// so let's use gdk_pango_context_get() instead; even though it's for the default screen only, it's good enough for us +#define mkGenericPangoCairoContext() (gdk_pango_context_get()) -PangoFont *pangoDescToPangoFont(PangoFontDescription *pdesc) -{ - PangoFont *f; - PangoContext *context; - - // in this case, the context is necessary for the metrics to be correct - context = mkGenericPangoCairoContext(); - f = pango_font_map_load_font(pango_cairo_font_map_get_default(), context, pdesc); - if (f == NULL) { - // TODO - g_error("[libui] no match in pangoDescToPangoFont(); report to andlabs"); - } - g_object_unref(context); - return f; -} - -uiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc) -{ - PangoFont *f; - PangoFontDescription *pdesc; - - pdesc = pango_font_description_new(); - pango_font_description_set_family(pdesc, - desc->Family); - pango_font_description_set_size(pdesc, - (gint) (desc->Size * PANGO_SCALE)); - pango_font_description_set_weight(pdesc, - pangoWeights[desc->Weight]); - pango_font_description_set_style(pdesc, - pangoItalics[desc->Italic]); - pango_font_description_set_stretch(pdesc, - pangoStretches[desc->Stretch]); - f = pangoDescToPangoFont(pdesc); - pango_font_description_free(pdesc); - return mkTextFont(f, FALSE); // we hold the initial reference; no need to ref -} - -void uiDrawFreeTextFont(uiDrawTextFont *font) -{ - g_object_unref(font->f); - uiFree(font); -} - -uintptr_t uiDrawTextFontHandle(uiDrawTextFont *font) -{ - return (uintptr_t) (font->f); -} - -void uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc) -{ - PangoFontDescription *pdesc; - - // this creates a copy; we free it later - pdesc = pango_font_describe(font->f); - - // TODO - - pango_font_description_free(pdesc); -} - -// See https://developer.gnome.org/pango/1.30/pango-Cairo-Rendering.html#pango-Cairo-Rendering.description -// Note that we convert to double before dividing to make sure the floating-point stuff is right -#define pangoToCairo(pango) (((double) (pango)) / PANGO_SCALE) -#define cairoToPango(cairo) ((gint) ((cairo) * PANGO_SCALE)) - -// TODO this isn't enough; pango adds extra space to each layout -void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics) -{ - PangoFontMetrics *pm; - - pm = pango_font_get_metrics(font->f, NULL); - metrics->Ascent = pangoToCairo(pango_font_metrics_get_ascent(pm)); - metrics->Descent = pangoToCairo(pango_font_metrics_get_descent(pm)); - // Pango doesn't seem to expose this :( Use 0 and hope for the best. - metrics->Leading = 0; - metrics->UnderlinePos = pangoToCairo(pango_font_metrics_get_underline_position(pm)); - metrics->UnderlineThickness = pangoToCairo(pango_font_metrics_get_underline_thickness(pm)); - pango_font_metrics_unref(pm); -} - -// note: PangoCairoLayouts are tied to a given cairo_t, so we can't store one in this device-independent structure -struct uiDrawTextLayout { - char *s; - ptrdiff_t *charsToBytes; - PangoFont *defaultFont; - double width; - PangoAttrList *attrs; +static const PangoAlignment pangoAligns[] = { + [uiDrawTextAlignLeft] = PANGO_ALIGN_LEFT, + [uiDrawTextAlignCenter] = PANGO_ALIGN_CENTER, + [uiDrawTextAlignRight] = PANGO_ALIGN_RIGHT, }; -static ptrdiff_t *computeCharsToBytes(const char *s) -{ - ptrdiff_t *charsToBytes; - glong i, charlen; - - // we INCLUDE the null terminator as a character in the string - // g_utf8_offset_to_pointer() doesn't stop on null terminator so this should work - charlen = g_utf8_strlen(s, -1) + 1; - charsToBytes = (ptrdiff_t *) uiAlloc(charlen * sizeof (ptrdiff_t), "ptrdiff_t[]"); - // TODO speed this up by not needing to scan the whole string again - for (i = 0; i < charlen; i++) - charsToBytes[i] = g_utf8_offset_to_pointer(s, i) - s; - return charsToBytes; -} - -uiDrawTextLayout *uiDrawNewTextLayout(const char *text, uiDrawTextFont *defaultFont, double width) -{ - uiDrawTextLayout *layout; - - layout = uiNew(uiDrawTextLayout); - layout->s = g_strdup(text); - layout->charsToBytes = computeCharsToBytes(layout->s); - layout->defaultFont = defaultFont->f; - g_object_ref(layout->defaultFont); // retain a copy - uiDrawTextLayoutSetWidth(layout, width); - layout->attrs = pango_attr_list_new(); - return layout; -} - -void uiDrawFreeTextLayout(uiDrawTextLayout *layout) -{ - pango_attr_list_unref(layout->attrs); - g_object_unref(layout->defaultFont); - uiFree(layout->charsToBytes); - g_free(layout->s); - uiFree(layout); -} - -void uiDrawTextLayoutSetWidth(uiDrawTextLayout *layout, double width) -{ - layout->width = width; -} - -static void prepareLayout(uiDrawTextLayout *layout, PangoLayout *pl) -{ - PangoFontDescription *desc; - int width; - - pango_layout_set_text(pl, layout->s, -1); - - // again, this makes a copy - desc = pango_font_describe(layout->defaultFont); - // this is safe; the description is copied - pango_layout_set_font_description(pl, desc); - pango_font_description_free(desc); - - width = cairoToPango(layout->width); - if (layout->width < 0) - width = -1; - pango_layout_set_width(pl, width); - - pango_layout_set_attributes(pl, layout->attrs); -} - -void uiDrawTextLayoutExtents(uiDrawTextLayout *layout, double *width, double *height) +uiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *p) { + uiDrawTextLayout *tl; PangoContext *context; - PangoLayout *pl; - PangoRectangle logical; + PangoFontDescription *desc; + PangoAttrList *attrs; + int pangoWidth; + + tl = uiprivNew(uiDrawTextLayout); // in this case, the context is necessary to create the layout + // the layout takes a ref on the context so we can unref it afterward context = mkGenericPangoCairoContext(); - pl = pango_layout_new(context); - // TODO g_object_unref() context? - prepareLayout(layout, pl); + tl->layout = pango_layout_new(context); + g_object_unref(context); - pango_layout_get_extents(pl, NULL, &logical); + // this is safe; pango_layout_set_text() copies the string + pango_layout_set_text(tl->layout, uiAttributedStringString(p->String), -1); - g_object_unref(pl); + desc = uiprivFontDescriptorToPangoFontDescription(p->DefaultFont); + pango_layout_set_font_description(tl->layout, desc); + // this is safe; the description is copied + pango_font_description_free(desc); + pangoWidth = cairoToPango(p->Width); + if (p->Width < 0) + pangoWidth = -1; + pango_layout_set_width(tl->layout, pangoWidth); + + pango_layout_set_alignment(tl->layout, pangoAligns[p->Align]); + + attrs = uiprivAttributedStringToPangoAttrList(p); + pango_layout_set_attributes(tl->layout, attrs); + pango_attr_list_unref(attrs); + + return tl; +} + +void uiDrawFreeTextLayout(uiDrawTextLayout *tl) +{ + g_object_unref(tl->layout); + uiprivFree(tl); +} + +void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y) +{ + // TODO have an implicit save/restore on each drawing functions instead? and is this correct? + cairo_set_source_rgb(c->cr, 0.0, 0.0, 0.0); + cairo_move_to(c->cr, x, y); + pango_cairo_show_layout(c->cr, tl->layout); +} + +void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height) +{ + PangoRectangle logical; + + pango_layout_get_extents(tl->layout, NULL, &logical); *width = pangoToCairo(logical.width); *height = pangoToCairo(logical.height); } - -void uiDrawText(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout) -{ - PangoLayout *pl; - - pl = pango_cairo_create_layout(c->cr); - prepareLayout(layout, pl); - - cairo_move_to(c->cr, x, y); - pango_cairo_show_layout(c->cr, pl); - - g_object_unref(pl); -} - -static void addAttr(uiDrawTextLayout *layout, PangoAttribute *attr, intmax_t startChar, intmax_t endChar) -{ - attr->start_index = layout->charsToBytes[startChar]; - attr->end_index = layout->charsToBytes[endChar]; - pango_attr_list_insert(layout->attrs, attr); - // pango_attr_list_insert() takes attr; we don't free it -} - -// these attributes are only supported on 1.38 and higher; we need to support 1.36 -// use dynamic linking to make them work at least on newer systems -// TODO warn programmers -static PangoAttribute *(*newFGAlphaAttr)(guint16 alpha) = NULL; -static gboolean tried138 = FALSE; - -// note that we treat any error as "the 1.38 symbols aren't there" (and don't care if dlclose() failed) -static void try138(void) -{ - void *handle; - - tried138 = TRUE; - // dlsym() walks the dependency chain, so opening the current process should be sufficient - handle = dlopen(NULL, RTLD_LAZY); - if (handle == NULL) - return; - *((void **) (&newFGAlphaAttr)) = dlsym(handle, "pango_attr_foreground_alpha_new"); - dlclose(handle); -} - -void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, intmax_t startChar, intmax_t endChar, double r, double g, double b, double a) -{ - PangoAttribute *attr; - guint16 rr, gg, bb, aa; - - rr = (guint16) (r * 65535); - gg = (guint16) (g * 65535); - bb = (guint16) (b * 65535); - aa = (guint16) (a * 65535); - - attr = pango_attr_foreground_new(rr, gg, bb); - addAttr(layout, attr, startChar, endChar); - - if (!tried138) - try138(); - // TODO what if aa == 0? - if (newFGAlphaAttr != NULL) { - attr = (*newFGAlphaAttr)(aa); - addAttr(layout, attr, startChar, endChar); - } -} diff --git a/unix/editablecombo.c b/unix/editablecombo.c new file mode 100644 index 00000000..7ee3829e --- /dev/null +++ b/unix/editablecombo.c @@ -0,0 +1,79 @@ +// 11 june 2015 +#include "uipriv_unix.h" + +struct uiEditableCombobox { + uiUnixControl c; + GtkWidget *widget; + GtkBin *bin; + GtkComboBox *combobox; + GtkComboBoxText *comboboxText; + void (*onChanged)(uiEditableCombobox *, void *); + void *onChangedData; + gulong onChangedSignal; +}; + +uiUnixControlAllDefaults(uiEditableCombobox) + +static void onChanged(GtkComboBox *cbox, gpointer data) +{ + uiEditableCombobox *c = uiEditableCombobox(data); + + (*(c->onChanged))(c, c->onChangedData); +} + +static void defaultOnChanged(uiEditableCombobox *c, void *data) +{ + // do nothing +} + +void uiEditableComboboxAppend(uiEditableCombobox *c, const char *text) +{ + gtk_combo_box_text_append(c->comboboxText, NULL, text); +} + +char *uiEditableComboboxText(uiEditableCombobox *c) +{ + char *s; + char *out; + + s = gtk_combo_box_text_get_active_text(c->comboboxText); + // s will always be non-NULL in the case of a combobox with an entry (according to the source code) + out = uiUnixStrdupText(s); + g_free(s); + return out; +} + +void uiEditableComboboxSetText(uiEditableCombobox *c, const char *text) +{ + GtkEntry *e; + + // we need to inhibit sending of ::changed because this WILL send a ::changed otherwise + g_signal_handler_block(c->combobox, c->onChangedSignal); + // since there isn't a gtk_combo_box_text_set_active_text()... + e = GTK_ENTRY(gtk_bin_get_child(c->bin)); + gtk_entry_set_text(e, text); + g_signal_handler_unblock(c->combobox, c->onChangedSignal); +} + +void uiEditableComboboxOnChanged(uiEditableCombobox *c, void (*f)(uiEditableCombobox *c, void *data), void *data) +{ + c->onChanged = f; + c->onChangedData = data; +} + +uiEditableCombobox *uiNewEditableCombobox(void) +{ + uiEditableCombobox *c; + + uiUnixNewControl(uiEditableCombobox, c); + + c->widget = gtk_combo_box_text_new_with_entry(); + c->bin = GTK_BIN(c->widget); + c->combobox = GTK_COMBO_BOX(c->widget); + c->comboboxText = GTK_COMBO_BOX_TEXT(c->widget); + + c->onChangedSignal = g_signal_connect(c->widget, "changed", G_CALLBACK(onChanged), c); + uiEditableComboboxOnChanged(c, defaultOnChanged, NULL); + + return c; +} diff --git a/unix/entry.c b/unix/entry.c index a71a47b5..4a9a1d04 100644 --- a/unix/entry.c +++ b/unix/entry.c @@ -60,18 +60,38 @@ void uiEntrySetReadOnly(uiEntry *e, int readonly) gtk_editable_set_editable(e->editable, editable); } -uiEntry *uiNewEntry(void) +static uiEntry *finishNewEntry(GtkWidget *w, const gchar *signal) { uiEntry *e; uiUnixNewControl(uiEntry, e); - e->widget = gtk_entry_new(); + e->widget = w; e->entry = GTK_ENTRY(e->widget); e->editable = GTK_EDITABLE(e->widget); - e->onChangedSignal = g_signal_connect(e->widget, "changed", G_CALLBACK(onChanged), e); + e->onChangedSignal = g_signal_connect(e->widget, signal, G_CALLBACK(onChanged), e); uiEntryOnChanged(e, defaultOnChanged, NULL); return e; } + +uiEntry *uiNewEntry(void) +{ + return finishNewEntry(gtk_entry_new(), "changed"); +} + +uiEntry *uiNewPasswordEntry(void) +{ + GtkWidget *e; + + e = gtk_entry_new(); + gtk_entry_set_visibility(GTK_ENTRY(e), FALSE); + return finishNewEntry(e, "changed"); +} + +// TODO make it use a separate function to be type-safe +uiEntry *uiNewSearchEntry(void) +{ + return finishNewEntry(gtk_search_entry_new(), "search-changed"); +} diff --git a/unix/fontbutton.c b/unix/fontbutton.c index f8047e08..d239952b 100644 --- a/unix/fontbutton.c +++ b/unix/fontbutton.c @@ -1,5 +1,6 @@ // 14 april 2016 #include "uipriv_unix.h" +#include "attrstr.h" struct uiFontButton { uiUnixControl c; @@ -26,16 +27,14 @@ static void defaultOnChanged(uiFontButton *b, void *data) // do nothing } -uiDrawTextFont *uiFontButtonFont(uiFontButton *b) +void uiFontButtonFont(uiFontButton *b, uiFontDescriptor *desc) { - PangoFont *f; - PangoFontDescription *desc; + PangoFontDescription *pdesc; - desc = gtk_font_chooser_get_font_desc(b->fc); - f = pangoDescToPangoFont(desc); - // desc is transfer-full and thus is a copy - pango_font_description_free(desc); - return mkTextFont(f, FALSE); // we hold the initial reference; no need to ref + pdesc = gtk_font_chooser_get_font_desc(b->fc); + uiprivFontDescriptorFromPangoFontDescription(pdesc, desc); + // pdesc is transfer-full and thus is a copy + pango_font_description_free(pdesc); } void uiFontButtonOnChanged(uiFontButton *b, void (*f)(uiFontButton *, void *), void *data) @@ -68,3 +67,9 @@ uiFontButton *uiNewFontButton(void) return b; } + +void uiFreeFontButtonFont(uiFontDescriptor *desc) +{ + // TODO ensure this is synchronized with fontmatch.c + uiFreeText((char *) (desc->Family)); +} diff --git a/unix/fontmatch.c b/unix/fontmatch.c new file mode 100644 index 00000000..95f4fdd7 --- /dev/null +++ b/unix/fontmatch.c @@ -0,0 +1,76 @@ +// 11 march 2018 +#include "uipriv_unix.h" +#include "attrstr.h" + +static const PangoStyle pangoItalics[] = { + [uiTextItalicNormal] = PANGO_STYLE_NORMAL, + [uiTextItalicOblique] = PANGO_STYLE_OBLIQUE, + [uiTextItalicItalic] = PANGO_STYLE_ITALIC, +}; + +static const PangoStretch pangoStretches[] = { + [uiTextStretchUltraCondensed] = PANGO_STRETCH_ULTRA_CONDENSED, + [uiTextStretchExtraCondensed] = PANGO_STRETCH_EXTRA_CONDENSED, + [uiTextStretchCondensed] = PANGO_STRETCH_CONDENSED, + [uiTextStretchSemiCondensed] = PANGO_STRETCH_SEMI_CONDENSED, + [uiTextStretchNormal] = PANGO_STRETCH_NORMAL, + [uiTextStretchSemiExpanded] = PANGO_STRETCH_SEMI_EXPANDED, + [uiTextStretchExpanded] = PANGO_STRETCH_EXPANDED, + [uiTextStretchExtraExpanded] = PANGO_STRETCH_EXTRA_EXPANDED, + [uiTextStretchUltraExpanded] = PANGO_STRETCH_ULTRA_EXPANDED, +}; + +// for the most part, pango weights correlate to ours +// the differences: +// - Book — libui: 350, Pango: 380 +// - Ultra Heavy — libui: 950, Pango: 1000 +// TODO figure out what to do about this misalignment +PangoWeight uiprivWeightToPangoWeight(uiTextWeight w) +{ + return (PangoWeight) w; +} + +PangoStyle uiprivItalicToPangoStyle(uiTextItalic i) +{ + return pangoItalics[i]; +} + +PangoStretch uiprivStretchToPangoStretch(uiTextStretch s) +{ + return pangoStretches[s]; +} + +PangoFontDescription *uiprivFontDescriptorToPangoFontDescription(const uiFontDescriptor *uidesc) +{ + PangoFontDescription *desc; + + desc = pango_font_description_new(); + pango_font_description_set_family(desc, uidesc->Family); + // see https://developer.gnome.org/pango/1.30/pango-Fonts.html#pango-font-description-set-size and https://developer.gnome.org/pango/1.30/pango-Glyph-Storage.html#pango-units-from-double + pango_font_description_set_size(desc, pango_units_from_double(uidesc->Size)); + pango_font_description_set_weight(desc, uiprivWeightToPangoWeight(uidesc->Weight)); + pango_font_description_set_style(desc, uiprivItalicToPangoStyle(uidesc->Italic)); + pango_font_description_set_stretch(desc, uiprivStretchToPangoStretch(uidesc->Stretch)); + return desc; +} + +void uiprivFontDescriptorFromPangoFontDescription(PangoFontDescription *pdesc, uiFontDescriptor *uidesc) +{ + PangoStyle pitalic; + PangoStretch pstretch; + + uidesc->Family = uiUnixStrdupText(pango_font_description_get_family(pdesc)); + pitalic = pango_font_description_get_style(pdesc); + // TODO reverse the above misalignment if it is corrected + uidesc->Weight = pango_font_description_get_weight(pdesc); + pstretch = pango_font_description_get_stretch(pdesc); + // absolute size does not matter because, as above, 1 device unit == 1 cairo point + uidesc->Size = pango_units_to_double(pango_font_description_get_size(pdesc)); + + for (uidesc->Italic = uiTextItalicNormal; uidesc->Italic < uiTextItalicItalic; uidesc->Italic++) + if (pangoItalics[uidesc->Italic] == pitalic) + break; + for (uidesc->Stretch = uiTextStretchUltraCondensed; uidesc->Stretch < uiTextStretchUltraExpanded; uidesc->Stretch++) + if (pangoStretches[uidesc->Stretch] == pstretch) + break; +} diff --git a/unix/form.c b/unix/form.c new file mode 100644 index 00000000..36ad77d5 --- /dev/null +++ b/unix/form.c @@ -0,0 +1,159 @@ +// 8 june 2016 +#include "uipriv_unix.h" + +struct formChild { + uiControl *c; + int stretchy; + GtkWidget *label; + gboolean oldhexpand; + GtkAlign oldhalign; + gboolean oldvexpand; + GtkAlign oldvalign; + GBinding *labelBinding; +}; + +struct uiForm { + uiUnixControl c; + GtkWidget *widget; + GtkContainer *container; + GtkGrid *grid; + GArray *children; + int padded; + GtkSizeGroup *stretchygroup; // ensures all stretchy controls have the same size +}; + +uiUnixControlAllDefaultsExceptDestroy(uiForm) + +#define ctrl(f, i) &g_array_index(f->children, struct formChild, i) + +static void uiFormDestroy(uiControl *c) +{ + uiForm *f = uiForm(c); + struct formChild *fc; + guint i; + + // kill the size group + g_object_unref(f->stretchygroup); + // free all controls + for (i = 0; i < f->children->len; i++) { + fc = ctrl(f, i); + uiControlSetParent(fc->c, NULL); + uiUnixControlSetContainer(uiUnixControl(fc->c), f->container, TRUE); + uiControlDestroy(fc->c); + gtk_widget_destroy(fc->label); + } + g_array_free(f->children, TRUE); + // and then ourselves + g_object_unref(f->widget); + uiFreeControl(uiControl(f)); +} + +void uiFormAppend(uiForm *f, const char *label, uiControl *c, int stretchy) +{ + struct formChild fc; + GtkWidget *widget; + guint row; + + fc.c = c; + widget = GTK_WIDGET(uiControlHandle(fc.c)); + fc.stretchy = stretchy; + fc.oldhexpand = gtk_widget_get_hexpand(widget); + fc.oldhalign = gtk_widget_get_halign(widget); + fc.oldvexpand = gtk_widget_get_vexpand(widget); + fc.oldvalign = gtk_widget_get_valign(widget); + + if (stretchy) { + gtk_widget_set_vexpand(widget, TRUE); + gtk_widget_set_valign(widget, GTK_ALIGN_FILL); + gtk_size_group_add_widget(f->stretchygroup, widget); + } else + gtk_widget_set_vexpand(widget, FALSE); + // and make them fill horizontally + gtk_widget_set_hexpand(widget, TRUE); + gtk_widget_set_halign(widget, GTK_ALIGN_FILL); + + fc.label = gtk_label_new(label); + gtk_widget_set_hexpand(fc.label, FALSE); + gtk_widget_set_halign(fc.label, GTK_ALIGN_END); + gtk_widget_set_vexpand(fc.label, FALSE); + if (GTK_IS_SCROLLED_WINDOW(widget)) + gtk_widget_set_valign(fc.label, GTK_ALIGN_START); + else + gtk_widget_set_valign(fc.label, GTK_ALIGN_CENTER); + gtk_style_context_add_class(gtk_widget_get_style_context(fc.label), "dim-label"); + row = f->children->len; + gtk_grid_attach(f->grid, fc.label, + 0, row, + 1, 1); + // and make them share visibility so if the control is hidden, so is its label + fc.labelBinding = g_object_bind_property(GTK_WIDGET(uiControlHandle(fc.c)), "visible", + fc.label, "visible", + G_BINDING_SYNC_CREATE); + + uiControlSetParent(fc.c, uiControl(f)); + uiUnixControlSetContainer(uiUnixControl(fc.c), f->container, FALSE); + g_array_append_val(f->children, fc); + + // move the widget to the correct place + gtk_container_child_set(f->container, widget, + "left-attach", 1, + "top-attach", row, + NULL); +} + +void uiFormDelete(uiForm *f, int index) +{ + struct formChild *fc; + GtkWidget *widget; + + fc = ctrl(f, index); + widget = GTK_WIDGET(uiControlHandle(fc->c)); + + gtk_widget_destroy(fc->label); + + uiControlSetParent(fc->c, NULL); + uiUnixControlSetContainer(uiUnixControl(fc->c), f->container, TRUE); + + if (fc->stretchy) + gtk_size_group_remove_widget(f->stretchygroup, widget); + gtk_widget_set_hexpand(widget, fc->oldhexpand); + gtk_widget_set_halign(widget, fc->oldhalign); + gtk_widget_set_vexpand(widget, fc->oldvexpand); + gtk_widget_set_valign(widget, fc->oldvalign); + + g_array_remove_index(f->children, index); +} + +int uiFormPadded(uiForm *f) +{ + return f->padded; +} + +void uiFormSetPadded(uiForm *f, int padded) +{ + f->padded = padded; + if (f->padded) { + gtk_grid_set_row_spacing(f->grid, uiprivGTKYPadding); + gtk_grid_set_column_spacing(f->grid, uiprivGTKXPadding); + } else { + gtk_grid_set_row_spacing(f->grid, 0); + gtk_grid_set_column_spacing(f->grid, 0); + } +} + +uiForm *uiNewForm(void) +{ + uiForm *f; + + uiUnixNewControl(uiForm, f); + + f->widget = gtk_grid_new(); + f->container = GTK_CONTAINER(f->widget); + f->grid = GTK_GRID(f->widget); + + f->stretchygroup = gtk_size_group_new(GTK_SIZE_GROUP_VERTICAL); + + f->children = g_array_new(FALSE, TRUE, sizeof (struct formChild)); + + return f; +} diff --git a/unix/future.c b/unix/future.c new file mode 100644 index 00000000..475dbc19 --- /dev/null +++ b/unix/future.c @@ -0,0 +1,60 @@ +// 29 june 2016 +#include "uipriv_unix.h" + +// functions FROM THE FUTURE! +// in some cases, because being held back by LTS releases sucks :/ +// in others, because parts of GTK+ being unstable until recently also sucks :/ + +// added in pango 1.38; we need 1.36 +static PangoAttribute *(*newFeaturesAttr)(const gchar *features) = NULL; +static PangoAttribute *(*newFGAlphaAttr)(guint16 alpha) = NULL; +static PangoAttribute *(*newBGAlphaAttr)(guint16 alpha) = NULL; + +// added in GTK+ 3.20; we need 3.10 +static void (*gwpIterSetObjectName)(GtkWidgetPath *path, gint pos, const char *name) = NULL; + +// note that we treat any error as "the symbols aren't there" (and don't care if dlclose() failed) +void uiprivLoadFutures(void) +{ + void *handle; + + // dlsym() walks the dependency chain, so opening the current process should be sufficient + handle = dlopen(NULL, RTLD_LAZY); + if (handle == NULL) + return; +#define GET(var, fn) *((void **) (&var)) = dlsym(handle, #fn) + GET(newFeaturesAttr, pango_attr_font_features_new); + GET(newFGAlphaAttr, pango_attr_foreground_alpha_new); + GET(newBGAlphaAttr, pango_attr_background_alpha_new); + GET(gwpIterSetObjectName, gtk_widget_path_iter_set_object_name); + dlclose(handle); +} + +PangoAttribute *uiprivFUTURE_pango_attr_font_features_new(const gchar *features) +{ + if (newFeaturesAttr == NULL) + return NULL; + return (*newFeaturesAttr)(features); +} + +PangoAttribute *uiprivFUTURE_pango_attr_foreground_alpha_new(guint16 alpha) +{ + if (newFGAlphaAttr == NULL) + return NULL; + return (*newFGAlphaAttr)(alpha); +} + +PangoAttribute *uiprivFUTURE_pango_attr_background_alpha_new(guint16 alpha) +{ + if (newBGAlphaAttr == NULL) + return NULL; + return (*newBGAlphaAttr)(alpha); +} + +gboolean uiprivFUTURE_gtk_widget_path_iter_set_object_name(GtkWidgetPath *path, gint pos, const char *name) +{ + if (gwpIterSetObjectName == NULL) + return FALSE; + (*gwpIterSetObjectName)(path, pos, name); + return TRUE; +} diff --git a/unix/graphemes.c b/unix/graphemes.c new file mode 100644 index 00000000..952f1ef8 --- /dev/null +++ b/unix/graphemes.c @@ -0,0 +1,63 @@ +// 25 may 2016 +#include "uipriv_unix.h" +#include "attrstr.h" + +int uiprivGraphemesTakesUTF16(void) +{ + return 0; +} + +uiprivGraphemes *uiprivNewGraphemes(void *s, size_t len) +{ + uiprivGraphemes *g; + char *text = (char *) s; + size_t lenchars; + PangoLogAttr *logattrs; + size_t i; + size_t *op; + + g = uiprivNew(uiprivGraphemes); + + // TODO see if we can use the utf routines + lenchars = g_utf8_strlen(text, -1); + logattrs = (PangoLogAttr *) uiprivAlloc((lenchars + 1) * sizeof (PangoLogAttr), "PangoLogAttr[] (graphemes)"); + pango_get_log_attrs(text, len, + -1, NULL, + logattrs, lenchars + 1); + + // first figure out how many graphemes there are + g->len = 0; + for (i = 0; i < lenchars; i++) + if (logattrs[i].is_cursor_position != 0) + g->len++; + + g->pointsToGraphemes = (size_t *) uiprivAlloc((len + 1) * sizeof (size_t), "size_t[] (graphemes)"); + g->graphemesToPoints = (size_t *) uiprivAlloc((g->len + 1) * sizeof (size_t), "size_t[] (graphemes)"); + + // compute the graphemesToPoints array + // TODO merge with the next for loop somehow? + op = g->graphemesToPoints; + for (i = 0; i < lenchars; i++) + if (logattrs[i].is_cursor_position != 0) + // TODO optimize this + *op++ = g_utf8_offset_to_pointer(text, i) - text; + // and do the last one + *op++ = len; + + // and finally build the pointsToGraphemes array + op = g->pointsToGraphemes; + for (i = 0; i < g->len; i++) { + size_t j; + size_t first, last; + + first = g->graphemesToPoints[i]; + last = g->graphemesToPoints[i + 1]; + for (j = first; j < last; j++) + *op++ = i; + } + // and do the last one + *op++ = i; + + uiprivFree(logattrs); + return g; +} diff --git a/unix/grid.c b/unix/grid.c new file mode 100644 index 00000000..7759cecc --- /dev/null +++ b/unix/grid.c @@ -0,0 +1,141 @@ +// 9 june 2016 +#include "uipriv_unix.h" + +struct gridChild { + uiControl *c; + GtkWidget *label; + gboolean oldhexpand; + GtkAlign oldhalign; + gboolean oldvexpand; + GtkAlign oldvalign; +}; + +struct uiGrid { + uiUnixControl c; + GtkWidget *widget; + GtkContainer *container; + GtkGrid *grid; + GArray *children; + int padded; +}; + +uiUnixControlAllDefaultsExceptDestroy(uiGrid) + +#define ctrl(g, i) &g_array_index(g->children, struct gridChild, i) + +static void uiGridDestroy(uiControl *c) +{ + uiGrid *g = uiGrid(c); + struct gridChild *gc; + guint i; + + // free all controls + for (i = 0; i < g->children->len; i++) { + gc = ctrl(g, i); + uiControlSetParent(gc->c, NULL); + uiUnixControlSetContainer(uiUnixControl(gc->c), g->container, TRUE); + uiControlDestroy(gc->c); + } + g_array_free(g->children, TRUE); + // and then ourselves + g_object_unref(g->widget); + uiFreeControl(uiControl(g)); +} + +#define TODO_MASSIVE_HACK(c) \ + if (!uiUnixControl(c)->addedBefore) { \ + g_object_ref_sink(GTK_WIDGET(uiControlHandle(uiControl(c)))); \ + gtk_widget_show(GTK_WIDGET(uiControlHandle(uiControl(c)))); \ + uiUnixControl(c)->addedBefore = TRUE; \ + } + +static const GtkAlign gtkAligns[] = { + [uiAlignFill] = GTK_ALIGN_FILL, + [uiAlignStart] = GTK_ALIGN_START, + [uiAlignCenter] = GTK_ALIGN_CENTER, + [uiAlignEnd] = GTK_ALIGN_END, +}; + +static const GtkPositionType gtkPositions[] = { + [uiAtLeading] = GTK_POS_LEFT, + [uiAtTop] = GTK_POS_TOP, + [uiAtTrailing] = GTK_POS_RIGHT, + [uiAtBottom] = GTK_POS_BOTTOM, +}; + +static GtkWidget *prepare(struct gridChild *gc, uiControl *c, int hexpand, uiAlign halign, int vexpand, uiAlign valign) +{ + GtkWidget *widget; + + gc->c = c; + widget = GTK_WIDGET(uiControlHandle(gc->c)); + gc->oldhexpand = gtk_widget_get_hexpand(widget); + gc->oldhalign = gtk_widget_get_halign(widget); + gc->oldvexpand = gtk_widget_get_vexpand(widget); + gc->oldvalign = gtk_widget_get_valign(widget); + gtk_widget_set_hexpand(widget, hexpand != 0); + gtk_widget_set_halign(widget, gtkAligns[halign]); + gtk_widget_set_vexpand(widget, vexpand != 0); + gtk_widget_set_valign(widget, gtkAligns[valign]); + return widget; +} + +void uiGridAppend(uiGrid *g, uiControl *c, int left, int top, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign) +{ + struct gridChild gc; + GtkWidget *widget; + + widget = prepare(&gc, c, hexpand, halign, vexpand, valign); + uiControlSetParent(gc.c, uiControl(g)); + TODO_MASSIVE_HACK(uiUnixControl(gc.c)); + gtk_grid_attach(g->grid, widget, + left, top, + xspan, yspan); + g_array_append_val(g->children, gc); +} + +void uiGridInsertAt(uiGrid *g, uiControl *c, uiControl *existing, uiAt at, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign) +{ + struct gridChild gc; + GtkWidget *widget; + + widget = prepare(&gc, c, hexpand, halign, vexpand, valign); + uiControlSetParent(gc.c, uiControl(g)); + TODO_MASSIVE_HACK(uiUnixControl(gc.c)); + gtk_grid_attach_next_to(g->grid, widget, + GTK_WIDGET(uiControlHandle(existing)), gtkPositions[at], + xspan, yspan); + g_array_append_val(g->children, gc); +} + +int uiGridPadded(uiGrid *g) +{ + return g->padded; +} + +void uiGridSetPadded(uiGrid *g, int padded) +{ + g->padded = padded; + if (g->padded) { + gtk_grid_set_row_spacing(g->grid, uiprivGTKYPadding); + gtk_grid_set_column_spacing(g->grid, uiprivGTKXPadding); + } else { + gtk_grid_set_row_spacing(g->grid, 0); + gtk_grid_set_column_spacing(g->grid, 0); + } +} + +uiGrid *uiNewGrid(void) +{ + uiGrid *g; + + uiUnixNewControl(uiGrid, g); + + g->widget = gtk_grid_new(); + g->container = GTK_CONTAINER(g->widget); + g->grid = GTK_GRID(g->widget); + + g->children = g_array_new(FALSE, TRUE, sizeof (struct gridChild)); + + return g; +} diff --git a/unix/group.c b/unix/group.c index 6238a1b6..545c7121 100644 --- a/unix/group.c +++ b/unix/group.c @@ -8,8 +8,8 @@ struct uiGroup { GtkBin *bin; GtkFrame *frame; - // unfortunately, even though a GtkFrame is a GtkBin, calling gtk_container_set_border_width() on it /includes/ the GtkFrame's label; we don't want tht - struct child *child; + // unfortunately, even though a GtkFrame is a GtkBin, calling gtk_container_set_border_width() on it /includes/ the GtkFrame's label; we don't want that + uiprivChild *child; int margined; }; @@ -21,7 +21,7 @@ static void uiGroupDestroy(uiControl *c) uiGroup *g = uiGroup(c); if (g->child != NULL) - childDestroy(g->child); + uiprivChildDestroy(g->child); g_object_unref(g->widget); uiFreeControl(uiControl(g)); } @@ -39,8 +39,8 @@ void uiGroupSetTitle(uiGroup *g, const char *text) void uiGroupSetChild(uiGroup *g, uiControl *child) { if (g->child != NULL) - childRemove(g->child); - g->child = newChildWithBox(child, uiControl(g), g->container, g->margined); + uiprivChildRemove(g->child); + g->child = uiprivNewChildWithBox(child, uiControl(g), g->container, g->margined); } int uiGroupMargined(uiGroup *g) @@ -52,7 +52,7 @@ void uiGroupSetMargined(uiGroup *g, int margined) { g->margined = margined; if (g->child != NULL) - childSetMargined(g->child, g->margined); + uiprivChildSetMargined(g->child, g->margined); } uiGroup *uiNewGroup(const char *text) diff --git a/unix/image.c b/unix/image.c new file mode 100644 index 00000000..cf21900d --- /dev/null +++ b/unix/image.c @@ -0,0 +1,134 @@ +// 27 june 2016 +#include "uipriv_unix.h" + +struct uiImage { + double width; + double height; + GPtrArray *images; +}; + +static void freeImageRep(gpointer item) +{ + cairo_surface_t *cs = (cairo_surface_t *) item; + + cairo_surface_destroy(cs); +} + +uiImage *uiNewImage(double width, double height) +{ + uiImage *i; + + i = uiprivNew(uiImage); + i->width = width; + i->height = height; + i->images = g_ptr_array_new_with_free_func(freeImageRep); + return i; +} + +void uiFreeImage(uiImage *i) +{ + g_ptr_array_free(i->images, TRUE); + uiprivFree(i); +} + +void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int byteStride) +{ + cairo_surface_t *cs; + uint8_t *data, *pix; + int realStride; + int x, y; + + // note that this is native-endian + cs = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + pixelWidth, pixelHeight); + if (cairo_surface_status(cs) != CAIRO_STATUS_SUCCESS) + /* TODO */; + cairo_surface_flush(cs); + + pix = (uint8_t *) pixels; + data = (uint8_t *) cairo_image_surface_get_data(cs); + realStride = cairo_image_surface_get_stride(cs); + for (y = 0; y < pixelHeight; y++) { + for (x = 0; x < pixelWidth * 4; x += 4) { + union { + uint32_t v32; + uint8_t v8[4]; + } v; + + v.v32 = ((uint32_t) (pix[x + 3])) << 24; + v.v32 |= ((uint32_t) (pix[x])) << 16; + v.v32 |= ((uint32_t) (pix[x + 1])) << 8; + v.v32 |= ((uint32_t) (pix[x + 2])); + data[x] = v.v8[0]; + data[x + 1] = v.v8[1]; + data[x + 2] = v.v8[2]; + data[x + 3] = v.v8[3]; + } + pix += byteStride; + data += realStride; + } + + cairo_surface_mark_dirty(cs); + g_ptr_array_add(i->images, cs); +} + +struct matcher { + cairo_surface_t *best; + int distX; + int distY; + int targetX; + int targetY; + gboolean foundLarger; +}; + +// TODO is this the right algorithm? +static void match(gpointer surface, gpointer data) +{ + cairo_surface_t *cs = (cairo_surface_t *) surface; + struct matcher *m = (struct matcher *) data; + int x, y; + int x2, y2; + + x = cairo_image_surface_get_width(cs); + y = cairo_image_surface_get_height(cs); + if (m->best == NULL) + goto writeMatch; + + if (x < m->targetX && y < m->targetY) + if (m->foundLarger) + // always prefer larger ones + return; + if (x >= m->targetX && y >= m->targetY && !m->foundLarger) + // we set foundLarger below + goto writeMatch; + + x2 = abs(m->targetX - x); + y2 = abs(m->targetY - y); + if (x2 < m->distX && y2 < m->distY) + goto writeMatch; + + // TODO weight one dimension? threshhold? + return; + +writeMatch: + // must set this here too; otherwise the first image will never have ths set + if (x >= m->targetX && y >= m->targetY && !m->foundLarger) + m->foundLarger = TRUE; + m->best = cs; + m->distX = abs(m->targetX - x); + m->distY = abs(m->targetY - y); +} + +cairo_surface_t *uiprivImageAppropriateSurface(uiImage *i, GtkWidget *w) +{ + struct matcher m; + + m.best = NULL; + m.distX = G_MAXINT; + m.distY = G_MAXINT; + m.targetX = i->width * gtk_widget_get_scale_factor(w); + m.targetY = i->height * gtk_widget_get_scale_factor(w); + m.foundLarger = FALSE; + g_ptr_array_foreach(i->images, match, &m); + return m.best; +} diff --git a/unix/main.c b/unix/main.c index 07010cd5..5d349d32 100644 --- a/unix/main.c +++ b/unix/main.c @@ -1,27 +1,40 @@ // 6 april 2015 #include "uipriv_unix.h" -uiInitOptions options; +uiInitOptions uiprivOptions; + +static GHashTable *timers; const char *uiInit(uiInitOptions *o) { GError *err = NULL; const char *msg; - options = *o; + uiprivOptions = *o; if (gtk_init_with_args(NULL, NULL, NULL, NULL, NULL, &err) == FALSE) { msg = g_strdup(err->message); g_error_free(err); return msg; } - initAlloc(); + uiprivInitAlloc(); + uiprivLoadFutures(); + timers = g_hash_table_new(g_direct_hash, g_direct_equal); return NULL; } +struct timer; // TODO get rid of forward declaration + +static void uninitTimer(gpointer key, gpointer value, gpointer data) +{ + uiprivFree((struct timer *) key); +} + void uiUninit(void) { - uninitMenus(); - uninitAlloc(); + g_hash_table_foreach(timers, uninitTimer, NULL); + g_hash_table_destroy(timers); + uiprivUninitMenus(); + uiprivUninitAlloc(); } void uiFreeInitError(const char *err) @@ -29,17 +42,49 @@ void uiFreeInitError(const char *err) g_free((gpointer) err); } +static gboolean (*iteration)(gboolean) = NULL; + void uiMain(void) { + iteration = gtk_main_iteration_do; gtk_main(); } +static gboolean stepsQuit = FALSE; + +// the only difference is we ignore the return value from gtk_main_iteration_do(), since it will always be TRUE if gtk_main() was never called +// gtk_main_iteration_do() will still run the main loop regardless +static gboolean stepsIteration(gboolean block) +{ + gtk_main_iteration_do(block); + return stepsQuit; +} + +void uiMainSteps(void) +{ + iteration = stepsIteration; +} + +int uiMainStep(int wait) +{ + gboolean block; + + block = FALSE; + if (wait) + block = TRUE; + return (*iteration)(block) == FALSE; +} + // gtk_main_quit() may run immediately, or it may wait for other pending events; "it depends" (thanks mclasen in irc.gimp.net/#gtk+) // PostQuitMessage() on Windows always waits, so we must do so too // we'll do it by using an idle callback static gboolean quit(gpointer data) { - gtk_main_quit(); + if (iteration == stepsIteration) + stepsQuit = TRUE; + // TODO run a gtk_main() here just to do the cleanup steps of syncing the clipboard and other stuff gtk_main() does before it returns + else + gtk_main_quit(); return FALSE; } @@ -58,17 +103,46 @@ static gboolean doqueued(gpointer data) struct queued *q = (struct queued *) data; (*(q->f))(q->data); - uiFree(q); + g_free(q); return FALSE; } -// TODO document that the effect of calling this function after uiQuit() is called (either directly or via a nonzero return to uiShouldQuit()) is undefined void uiQueueMain(void (*f)(void *data), void *data) { struct queued *q; - q = uiNew(struct queued); + // we have to use g_new0()/g_free() because uiprivAlloc() is only safe to call on the main thread + // for some reason it didn't affect me, but it did affect krakjoe + q = g_new0(struct queued, 1); q->f = f; q->data = data; gdk_threads_add_idle(doqueued, q); } + +struct timer { + int (*f)(void *); + void *data; +}; + +static gboolean doTimer(gpointer data) +{ + struct timer *t = (struct timer *) data; + + if (!(*(t->f))(t->data)) { + g_hash_table_remove(timers, t); + uiprivFree(t); + return FALSE; + } + return TRUE; +} + +void uiTimer(int milliseconds, int (*f)(void *data), void *data) +{ + struct timer *t; + + t = uiprivNew(struct timer); + t->f = f; + t->data = data; + g_timeout_add(milliseconds, doTimer, t); + g_hash_table_add(timers, t); +} diff --git a/unix/menu.c b/unix/menu.c index cf276dac..a3142ee0 100644 --- a/unix/menu.c +++ b/unix/menu.c @@ -81,7 +81,7 @@ static void defaultOnClicked(uiMenuItem *item, uiWindow *w, void *data) static void onQuitClicked(uiMenuItem *item, uiWindow *w, void *data) { - if (shouldQuit()) + if (uiprivShouldQuit()) uiQuit(); } @@ -109,7 +109,7 @@ void uiMenuItemDisable(uiMenuItem *item) void uiMenuItemOnClicked(uiMenuItem *item, void (*f)(uiMenuItem *, uiWindow *, void *), void *data) { if (item->type == typeQuit) - complain("attempt to call uiMenuItemOnClicked() on a Quit item; use uiOnShouldQuit() instead"); + uiprivUserBug("You cannot call uiMenuItemOnClicked() on a Quit item; use uiOnShouldQuit() instead."); item->onClicked = f; item->onClickedData = data; } @@ -135,9 +135,9 @@ static uiMenuItem *newItem(uiMenu *m, int type, const char *name) uiMenuItem *item; if (menusFinalized) - complain("attempt to create a new menu item after menus have been finalized"); + uiprivUserBug("You cannot create a new menu item after menus have been finalized."); - item = uiNew(uiMenuItem); + item = uiprivNew(uiMenuItem); g_array_append_val(m->items, item); @@ -196,7 +196,7 @@ uiMenuItem *uiMenuAppendCheckItem(uiMenu *m, const char *name) uiMenuItem *uiMenuAppendQuitItem(uiMenu *m) { if (hasQuit) - complain("attempt to add multiple Quit menu items"); + uiprivUserBug("You cannot have multiple Quit menu items in the same program."); hasQuit = TRUE; newItem(m, typeSeparator, NULL); return newItem(m, typeQuit, NULL); @@ -205,7 +205,7 @@ uiMenuItem *uiMenuAppendQuitItem(uiMenu *m) uiMenuItem *uiMenuAppendPreferencesItem(uiMenu *m) { if (hasPreferences) - complain("attempt to add multiple Preferences menu items"); + uiprivUserBug("You cannot have multiple Preferences menu items in the same program."); hasPreferences = TRUE; newItem(m, typeSeparator, NULL); return newItem(m, typePreferences, NULL); @@ -214,7 +214,7 @@ uiMenuItem *uiMenuAppendPreferencesItem(uiMenu *m) uiMenuItem *uiMenuAppendAboutItem(uiMenu *m) { if (hasAbout) - complain("attempt to add multiple About menu items"); + uiprivUserBug("You cannot have multiple About menu items in the same program."); hasAbout = TRUE; newItem(m, typeSeparator, NULL); return newItem(m, typeAbout, NULL); @@ -230,11 +230,11 @@ uiMenu *uiNewMenu(const char *name) uiMenu *m; if (menusFinalized) - complain("attempt to create a new menu after menus have been finalized"); + uiprivUserBug("You cannot create a new menu after menus have been finalized."); if (menus == NULL) menus = g_array_new(FALSE, TRUE, sizeof (uiMenu *)); - m = uiNew(uiMenu); + m = uiprivNew(uiMenu); g_array_append_val(menus, m); @@ -260,13 +260,13 @@ static void appendMenuItem(GtkMenuShell *submenu, uiMenuItem *item, uiWindow *w) singleSetChecked(GTK_CHECK_MENU_ITEM(menuitem), item->checked, signal); } gtk_menu_shell_append(submenu, menuitem); - ww = uiNew(struct menuItemWindow); + ww = uiprivNew(struct menuItemWindow); ww->w = w; ww->signal = signal; g_hash_table_insert(item->windows, menuitem, ww); } -GtkWidget *makeMenubar(uiWindow *w) +GtkWidget *uiprivMakeMenubar(uiWindow *w) { GtkWidget *menubar; guint i, j; @@ -308,8 +308,8 @@ static void freeMenuItem(GtkWidget *widget, gpointer data) item = g_array_index(fmi->items, uiMenuItem *, fmi->i); w = (struct menuItemWindow *) g_hash_table_lookup(item->windows, widget); if (g_hash_table_remove(item->windows, widget) == FALSE) - complain("GtkMenuItem %p not in menu item's item/window map", widget); - uiFree(w); + uiprivImplBug("GtkMenuItem %p not in menu item's item/window map", widget); + uiprivFree(w); fmi->i++; } @@ -330,7 +330,7 @@ static void freeMenu(GtkWidget *widget, gpointer data) (*i)++; } -void freeMenubar(GtkWidget *mb) +void uiprivFreeMenubar(GtkWidget *mb) { guint i; @@ -339,7 +339,7 @@ void freeMenubar(GtkWidget *mb) // no need to worry about destroying any widgets; destruction of the window they're in will do it for us } -void uninitMenus(void) +void uiprivUninitMenus(void) { uiMenu *m; uiMenuItem *item; @@ -353,13 +353,14 @@ void uninitMenus(void) for (j = 0; j < m->items->len; j++) { item = g_array_index(m->items, uiMenuItem *, j); if (g_hash_table_size(item->windows) != 0) - complain("menu item %p (%s) still has uiWindows attached; did you forget to destroy some windows?", item, item->name); + // TODO is this really a uiprivUserBug()? + uiprivImplBug("menu item %p (%s) still has uiWindows attached; did you forget to destroy some windows?", item, item->name); g_free(item->name); g_hash_table_destroy(item->windows); - uiFree(item); + uiprivFree(item); } g_array_free(m->items, TRUE); - uiFree(m); + uiprivFree(m); } g_array_free(menus, TRUE); } diff --git a/unix/multilineentry.c b/unix/multilineentry.c index bcd56a74..228dc80f 100644 --- a/unix/multilineentry.c +++ b/unix/multilineentry.c @@ -1,7 +1,7 @@ // 6 december 2015 #include "uipriv_unix.h" -// TODO: ensure this can only be used to enter plain text +// TODO GTK_WRAP_WORD_CHAR to avoid spurious resizes? struct uiMultilineEntry { uiUnixControl c; @@ -46,8 +46,10 @@ char *uiMultilineEntryText(uiMultilineEntry *e) void uiMultilineEntrySetText(uiMultilineEntry *e, const char *text) { - // TODO does this send a changed signal? + // we need to inhibit sending of ::changed because this WILL send a ::changed otherwise + g_signal_handler_block(e->textbuf, e->onChangedSignal); gtk_text_buffer_set_text(e->textbuf, text, -1); + g_signal_handler_unblock(e->textbuf, e->onChangedSignal); } // TODO scroll to end? @@ -56,8 +58,10 @@ void uiMultilineEntryAppend(uiMultilineEntry *e, const char *text) GtkTextIter end; gtk_text_buffer_get_end_iter(e->textbuf, &end); - // TODO does this send a changed signal? + // we need to inhibit sending of ::changed because this WILL send a ::changed otherwise + g_signal_handler_block(e->textbuf, e->onChangedSignal); gtk_text_buffer_insert(e->textbuf, &end, text, -1); + g_signal_handler_unblock(e->textbuf, e->onChangedSignal); } void uiMultilineEntryOnChanged(uiMultilineEntry *e, void (*f)(uiMultilineEntry *e, void *data), void *data) @@ -81,7 +85,7 @@ void uiMultilineEntrySetReadOnly(uiMultilineEntry *e, int readonly) gtk_text_view_set_editable(e->textview, editable); } -uiMultilineEntry *uiNewMultilineEntry(void) +static uiMultilineEntry *finishMultilineEntry(GtkPolicyType hpolicy, GtkWrapMode wrapMode) { uiMultilineEntry *e; @@ -91,13 +95,13 @@ uiMultilineEntry *uiNewMultilineEntry(void) e->scontainer = GTK_CONTAINER(e->widget); e->sw = GTK_SCROLLED_WINDOW(e->widget); gtk_scrolled_window_set_policy(e->sw, - GTK_POLICY_NEVER, + hpolicy, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_shadow_type(e->sw, GTK_SHADOW_IN); e->textviewWidget = gtk_text_view_new(); e->textview = GTK_TEXT_VIEW(e->textviewWidget); - gtk_text_view_set_wrap_mode(e->textview, GTK_WRAP_WORD); + gtk_text_view_set_wrap_mode(e->textview, wrapMode); gtk_container_add(e->scontainer, e->textviewWidget); // and make the text view visible; only the scrolled window's visibility is controlled by libui @@ -110,3 +114,13 @@ uiMultilineEntry *uiNewMultilineEntry(void) return e; } + +uiMultilineEntry *uiNewMultilineEntry(void) +{ + return finishMultilineEntry(GTK_POLICY_NEVER, GTK_WRAP_WORD); +} + +uiMultilineEntry *uiNewNonWrappingMultilineEntry(void) +{ + return finishMultilineEntry(GTK_POLICY_AUTOMATIC, GTK_WRAP_NONE); +} diff --git a/unix/opentype.c b/unix/opentype.c new file mode 100644 index 00000000..281acda7 --- /dev/null +++ b/unix/opentype.c @@ -0,0 +1,26 @@ +// 11 may 2017 +#include "uipriv_unix.h" +#include "attrstr.h" + +// see https://developer.mozilla.org/en/docs/Web/CSS/font-feature-settings +static uiForEach toCSS(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value, void *data) +{ + GString *s = (GString *) data; + + // the last trailing comma is removed after foreach is done + g_string_append_printf(s, "\"%c%c%c%c\" %" PRIu32 ", ", + a, b, c, d, value); + return uiForEachContinue; +} + +GString *uiprivOpenTypeFeaturesToPangoCSSFeaturesString(const uiOpenTypeFeatures *otf) +{ + GString *s; + + s = g_string_new(""); + uiOpenTypeFeaturesForEach(otf, toCSS, s); + if (s->len != 0) + // and remove the last comma + g_string_truncate(s, s->len - 2); + return s; +} diff --git a/unix/progressbar.c b/unix/progressbar.c index 587bc8ad..46f0e103 100644 --- a/unix/progressbar.c +++ b/unix/progressbar.c @@ -1,18 +1,63 @@ // 11 june 2015 #include "uipriv_unix.h" +// LONGTERM: +// - in GTK+ 3.22 at least, running both a GtkProgressBar and a GtkCellRendererProgress in pulse mode with our code will cause the former to slow down and eventually stop, and I can't tell why at all + struct uiProgressBar { uiUnixControl c; GtkWidget *widget; GtkProgressBar *pbar; + gboolean indeterminate; + guint pulser; }; -uiUnixControlAllDefaults(uiProgressBar) +uiUnixControlAllDefaultsExceptDestroy(uiProgressBar) + +static void uiProgressBarDestroy(uiControl *c) +{ + uiProgressBar *p = uiProgressBar(c); + + // be sure to stop the timeout now + if (p->indeterminate) + g_source_remove(p->pulser); + g_object_unref(p->widget); + uiFreeControl(uiControl(p)); +} + +int uiProgressBarValue(uiProgressBar *p) +{ + if (p->indeterminate) + return -1; + return (int) (gtk_progress_bar_get_fraction(p->pbar) * 100); +} + +static gboolean pulse(void* data) +{ + uiProgressBar *p = uiProgressBar(data); + + gtk_progress_bar_pulse(p->pbar); + return TRUE; +} void uiProgressBarSetValue(uiProgressBar *p, int value) { + if (value == -1) { + if (!p->indeterminate) { + p->indeterminate = TRUE; + // TODO verify the timeout + p->pulser = g_timeout_add(100, pulse, p); + } + return; + } + if (p->indeterminate) { + p->indeterminate = FALSE; + g_source_remove(p->pulser); + } + if (value < 0 || value > 100) - complain("value %d out of range in progressbarSetValue()", value); + uiprivUserBug("Value %d is out of range for a uiProgressBar.", value); + gtk_progress_bar_set_fraction(p->pbar, ((gdouble) value) / 100); } diff --git a/unix/radiobuttons.c b/unix/radiobuttons.c index 9a6cb42c..da41107e 100644 --- a/unix/radiobuttons.c +++ b/unix/radiobuttons.c @@ -9,13 +9,44 @@ struct uiRadioButtons { GtkContainer *container; GtkBox *box; GPtrArray *buttons; + void (*onSelected)(uiRadioButtons *, void *); + void *onSelectedData; + gboolean changing; }; uiUnixControlAllDefaultsExceptDestroy(uiRadioButtons) +static void defaultOnSelected(uiRadioButtons *r, void *data) +{ + // do nothing +} + +static void onToggled(GtkToggleButton *tb, gpointer data) +{ + uiRadioButtons *r = uiRadioButtons(data); + + // only care if a button is selected + if (!gtk_toggle_button_get_active(tb)) + return; + // ignore programmatic changes + if (r->changing) + return; + (*(r->onSelected))(r, r->onSelectedData); +} + static void uiRadioButtonsDestroy(uiControl *c) { - // TODO + uiRadioButtons *r = uiRadioButtons(c); + GtkWidget *b; + + while (r->buttons->len != 0) { + b = GTK_WIDGET(g_ptr_array_remove_index(r->buttons, 0)); + gtk_widget_destroy(b); + } + g_ptr_array_free(r->buttons, TRUE); + // and free ourselves + g_object_unref(r->widget); + uiFreeControl(uiControl(r)); } void uiRadioButtonsAppend(uiRadioButtons *r, const char *text) @@ -27,11 +58,51 @@ void uiRadioButtonsAppend(uiRadioButtons *r, const char *text) if (r->buttons->len > 0) previous = GTK_RADIO_BUTTON(g_ptr_array_index(r->buttons, 0)); rb = gtk_radio_button_new_with_label_from_widget(previous, text); + g_signal_connect(rb, "toggled", G_CALLBACK(onToggled), r); gtk_container_add(r->container, rb); g_ptr_array_add(r->buttons, rb); gtk_widget_show(rb); } +int uiRadioButtonsSelected(uiRadioButtons *r) +{ + GtkToggleButton *tb; + guint i; + + for (i = 0; i < r->buttons->len; i++) { + tb = GTK_TOGGLE_BUTTON(g_ptr_array_index(r->buttons, i)); + if (gtk_toggle_button_get_active(tb)) + return i; + } + return -1; +} + +void uiRadioButtonsSetSelected(uiRadioButtons *r, int n) +{ + GtkToggleButton *tb; + gboolean active; + + active = TRUE; + // TODO this doesn't work + if (n == -1) { + n = uiRadioButtonsSelected(r); + if (n == -1) // no selection; keep it that way + return; + active = FALSE; + } + tb = GTK_TOGGLE_BUTTON(g_ptr_array_index(r->buttons, n)); + // this is easier than remembering all the signals + r->changing = TRUE; + gtk_toggle_button_set_active(tb, active); + r->changing = FALSE; +} + +void uiRadioButtonsOnSelected(uiRadioButtons *r, void (*f)(uiRadioButtons *, void *), void *data) +{ + r->onSelected = f; + r->onSelectedData = data; +} + uiRadioButtons *uiNewRadioButtons(void) { uiRadioButtons *r; @@ -44,5 +115,7 @@ uiRadioButtons *uiNewRadioButtons(void) r->buttons = g_ptr_array_new(); + uiRadioButtonsOnSelected(r, defaultOnSelected, NULL); + return r; } diff --git a/unix/separator.c b/unix/separator.c index 63217b72..02c75da5 100644 --- a/unix/separator.c +++ b/unix/separator.c @@ -20,3 +20,15 @@ uiSeparator *uiNewHorizontalSeparator(void) return s; } + +uiSeparator *uiNewVerticalSeparator(void) +{ + uiSeparator *s; + + uiUnixNewControl(uiSeparator, s); + + s->widget = gtk_separator_new(GTK_ORIENTATION_VERTICAL); + s->separator = GTK_SEPARATOR(s->widget); + + return s; +} diff --git a/unix/slider.c b/unix/slider.c index 81b261d8..7f0cc24a 100644 --- a/unix/slider.c +++ b/unix/slider.c @@ -25,12 +25,12 @@ static void defaultOnChanged(uiSlider *s, void *data) // do nothing } -intmax_t uiSliderValue(uiSlider *s) +int uiSliderValue(uiSlider *s) { - return (intmax_t) gtk_range_get_value(s->range); + return gtk_range_get_value(s->range); } -void uiSliderSetValue(uiSlider *s, intmax_t value) +void uiSliderSetValue(uiSlider *s, int value) { // we need to inhibit sending of ::value-changed because this WILL send a ::value-changed otherwise g_signal_handler_block(s->range, s->onChangedSignal); @@ -44,9 +44,16 @@ void uiSliderOnChanged(uiSlider *s, void (*f)(uiSlider *, void *), void *data) s->onChangedData = data; } -uiSlider *uiNewSlider(intmax_t min, intmax_t max) +uiSlider *uiNewSlider(int min, int max) { uiSlider *s; + int temp; + + if (min >= max) { + temp = min; + min = max; + max = temp; + } uiUnixNewControl(uiSlider, s); @@ -54,7 +61,7 @@ uiSlider *uiNewSlider(intmax_t min, intmax_t max) s->range = GTK_RANGE(s->widget); s->scale = GTK_SCALE(s->widget); - // TODO needed? + // ensure integers, just to be safe gtk_scale_set_digits(s->scale, 0); s->onChangedSignal = g_signal_connect(s->scale, "value-changed", G_CALLBACK(onChanged), s); diff --git a/unix/spinbox.c b/unix/spinbox.c index 0f1a4f5e..90a5d3c1 100644 --- a/unix/spinbox.c +++ b/unix/spinbox.c @@ -25,16 +25,16 @@ static void defaultOnChanged(uiSpinbox *s, void *data) // do nothing } -intmax_t uiSpinboxValue(uiSpinbox *s) +int uiSpinboxValue(uiSpinbox *s) { - return (intmax_t) gtk_spin_button_get_value(s->spinButton); + return gtk_spin_button_get_value(s->spinButton); } -void uiSpinboxSetValue(uiSpinbox *s, intmax_t value) +void uiSpinboxSetValue(uiSpinbox *s, int value) { // we need to inhibit sending of ::value-changed because this WILL send a ::value-changed otherwise g_signal_handler_block(s->spinButton, s->onChangedSignal); - // TODO does this clamp? + // this clamps for us gtk_spin_button_set_value(s->spinButton, (gdouble) value); g_signal_handler_unblock(s->spinButton, s->onChangedSignal); } @@ -45,12 +45,16 @@ void uiSpinboxOnChanged(uiSpinbox *s, void (*f)(uiSpinbox *, void *), void *data s->onChangedData = data; } -uiSpinbox *uiNewSpinbox(intmax_t min, intmax_t max) +uiSpinbox *uiNewSpinbox(int min, int max) { uiSpinbox *s; + int temp; - if (min >= max) - complain("error: min >= max in uiNewSpinbox()"); + if (min >= max) { + temp = min; + min = max; + max = temp; + } uiUnixNewControl(uiSpinbox, s); @@ -58,7 +62,7 @@ uiSpinbox *uiNewSpinbox(intmax_t min, intmax_t max) s->entry = GTK_ENTRY(s->widget); s->spinButton = GTK_SPIN_BUTTON(s->widget); - // TODO needed? + // ensure integers, just to be safe gtk_spin_button_set_digits(s->spinButton, 0); s->onChangedSignal = g_signal_connect(s->spinButton, "value-changed", G_CALLBACK(onChanged), s); diff --git a/unix/stddialogs.c b/unix/stddialogs.c index 341e0168..93302f75 100644 --- a/unix/stddialogs.c +++ b/unix/stddialogs.c @@ -1,11 +1,12 @@ // 26 june 2015 #include "uipriv_unix.h" -// TODO while this runs, other windows don't get /any/ events +// LONGTERM figure out why, and describe, that this is the desired behavior +// LONGTERM also point out that font and color buttons also work like this #define windowWindow(w) (GTK_WINDOW(uiControlHandle(uiControl(w)))) -static char *filedialog(GtkWindow *parent, GtkFileChooserAction mode, const gchar *stock) +static char *filedialog(GtkWindow *parent, GtkFileChooserAction mode, const gchar *confirm) { GtkWidget *fcd; GtkFileChooser *fc; @@ -13,8 +14,8 @@ static char *filedialog(GtkWindow *parent, GtkFileChooserAction mode, const gcha char *filename; fcd = gtk_file_chooser_dialog_new(NULL, parent, mode, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - stock, GTK_RESPONSE_ACCEPT, + "_Cancel", GTK_RESPONSE_CANCEL, + confirm, GTK_RESPONSE_ACCEPT, NULL); fc = GTK_FILE_CHOOSER(fcd); gtk_file_chooser_set_local_only(fc, FALSE); @@ -34,12 +35,12 @@ static char *filedialog(GtkWindow *parent, GtkFileChooserAction mode, const gcha char *uiOpenFile(uiWindow *parent) { - return filedialog(windowWindow(parent), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_OPEN); + return filedialog(windowWindow(parent), GTK_FILE_CHOOSER_ACTION_OPEN, "_Open"); } char *uiSaveFile(uiWindow *parent) { - return filedialog(windowWindow(parent), GTK_FILE_CHOOSER_ACTION_SAVE, GTK_STOCK_SAVE); + return filedialog(windowWindow(parent), GTK_FILE_CHOOSER_ACTION_SAVE, "_Save"); } static void msgbox(GtkWindow *parent, const char *title, const char *description, GtkMessageType type, GtkButtonsType buttons) diff --git a/unix/tab.c b/unix/tab.c index 1ab7f6ad..b49c98b9 100644 --- a/unix/tab.c +++ b/unix/tab.c @@ -8,7 +8,7 @@ struct uiTab { GtkContainer *container; GtkNotebook *notebook; - GArray *pages; // []*struct child + GArray *pages; // []*uiprivChild }; uiUnixControlAllDefaultsExceptDestroy(uiTab) @@ -17,11 +17,11 @@ static void uiTabDestroy(uiControl *c) { uiTab *t = uiTab(c); guint i; - struct child *page; + uiprivChild *page; for (i = 0; i < t->pages->len; i++) { - page = g_array_index(t->pages, struct child *, i); - childDestroy(page); + page = g_array_index(t->pages, uiprivChild *, i); + uiprivChildDestroy(page); } g_array_free(t->pages, TRUE); // and free ourselves @@ -34,49 +34,49 @@ void uiTabAppend(uiTab *t, const char *name, uiControl *child) uiTabInsertAt(t, name, t->pages->len, child); } -void uiTabInsertAt(uiTab *t, const char *name, uintmax_t n, uiControl *child) +void uiTabInsertAt(uiTab *t, const char *name, int n, uiControl *child) { - struct child *page; + uiprivChild *page; // this will create a tab, because of gtk_container_add() - page = newChildWithBox(child, uiControl(t), t->container, 0); + page = uiprivNewChildWithBox(child, uiControl(t), t->container, 0); - gtk_notebook_set_tab_label_text(t->notebook, childBox(page), name); - gtk_notebook_reorder_child(t->notebook, childBox(page), n); + gtk_notebook_set_tab_label_text(t->notebook, uiprivChildBox(page), name); + gtk_notebook_reorder_child(t->notebook, uiprivChildBox(page), n); g_array_insert_val(t->pages, n, page); } -void uiTabDelete(uiTab *t, uintmax_t n) +void uiTabDelete(uiTab *t, int n) { - struct child *page; + uiprivChild *page; - page = g_array_index(t->pages, struct child *, n); + page = g_array_index(t->pages, uiprivChild *, n); // this will remove the tab, because gtk_widget_destroy() calls gtk_container_remove() - childRemove(page); + uiprivChildRemove(page); g_array_remove_index(t->pages, n); } -uintmax_t uiTabNumPages(uiTab *t) +int uiTabNumPages(uiTab *t) { return t->pages->len; } -int uiTabMargined(uiTab *t, uintmax_t n) +int uiTabMargined(uiTab *t, int n) { - struct child *page; + uiprivChild *page; - page = g_array_index(t->pages, struct child *, n); - return childFlag(page); + page = g_array_index(t->pages, uiprivChild *, n); + return uiprivChildFlag(page); } -void uiTabSetMargined(uiTab *t, uintmax_t n, int margined) +void uiTabSetMargined(uiTab *t, int n, int margined) { - struct child *page; + uiprivChild *page; - page = g_array_index(t->pages, struct child *, n); - childSetFlag(page, margined); - childSetMargined(page, childFlag(page)); + page = g_array_index(t->pages, uiprivChild *, n); + uiprivChildSetFlag(page, margined); + uiprivChildSetMargined(page, uiprivChildFlag(page)); } uiTab *uiNewTab(void) @@ -91,7 +91,7 @@ uiTab *uiNewTab(void) gtk_notebook_set_scrollable(t->notebook, TRUE); - t->pages = g_array_new(FALSE, TRUE, sizeof (struct child *)); + t->pages = g_array_new(FALSE, TRUE, sizeof (uiprivChild *)); return t; } diff --git a/unix/table.c b/unix/table.c new file mode 100644 index 00000000..d1e6fe60 --- /dev/null +++ b/unix/table.c @@ -0,0 +1,522 @@ +// 26 june 2016 +#include "uipriv_unix.h" +#include "table.h" + +// TODO with GDK_SCALE set to 2 the 32x32 images are scaled up to 64x64? + +struct uiTable { + uiUnixControl c; + GtkWidget *widget; + GtkContainer *scontainer; + GtkScrolledWindow *sw; + GtkWidget *treeWidget; + GtkTreeView *tv; + uiTableModel *model; + GPtrArray *columnParams; + int backgroundColumn; + // keys are struct rowcol, values are gint + // TODO document this properly + GHashTable *indeterminatePositions; + guint indeterminateTimer; +}; + +// use the same size as GtkFileChooserWidget's treeview +// TODO refresh when icon theme changes +// TODO doesn't work when scaled? +// TODO is this even necessary? +static void setImageSize(GtkCellRenderer *r) +{ + gint size; + gint width, height; + gint xpad, ypad; + + size = 16; // fallback used by GtkFileChooserWidget + if (gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height) != FALSE) + size = MAX(width, height); + gtk_cell_renderer_get_padding(r, &xpad, &ypad); + gtk_cell_renderer_set_fixed_size(r, + 2 * xpad + size, + 2 * ypad + size); +} + +static void applyColor(GtkTreeModel *m, GtkTreeIter *iter, int modelColumn, GtkCellRenderer *r, const char *prop, const char *propSet) +{ + GValue value = G_VALUE_INIT; + GdkRGBA *rgba; + + gtk_tree_model_get_value(m, iter, modelColumn, &value); + rgba = (GdkRGBA *) g_value_get_boxed(&value); + if (rgba != NULL) + g_object_set(r, prop, rgba, NULL); + else + g_object_set(r, propSet, FALSE, NULL); + g_value_unset(&value); +} + +static void setEditable(uiTableModel *m, GtkTreeIter *iter, int modelColumn, GtkCellRenderer *r, const char *prop) +{ + GtkTreePath *path; + int row; + gboolean editable; + + // TODO avoid the need for this + path = gtk_tree_model_get_path(GTK_TREE_MODEL(m), iter); + row = gtk_tree_path_get_indices(path)[0]; + editable = uiprivTableModelCellEditable(m, row, modelColumn) != 0; + g_object_set(r, prop, editable, NULL); +} + +static void applyBackgroundColor(uiTable *t, GtkTreeModel *m, GtkTreeIter *iter, GtkCellRenderer *r) +{ + if (t->backgroundColumn != -1) + applyColor(m, iter, t->backgroundColumn, + r, "cell-background-rgba", "cell-background-set"); +} + +static void onEdited(uiTableModel *m, int column, const char *pathstr, const uiTableValue *tvalue, GtkTreeIter *iter) +{ + GtkTreePath *path; + int row; + + path = gtk_tree_path_new_from_string(pathstr); + row = gtk_tree_path_get_indices(path)[0]; + if (iter != NULL) + gtk_tree_model_get_iter(GTK_TREE_MODEL(m), iter, path); + gtk_tree_path_free(path); + uiprivTableModelSetCellValue(m, row, column, tvalue); +} + +struct textColumnParams { + uiTable *t; + uiTableModel *m; + int modelColumn; + int editableColumn; + uiTableTextColumnOptionalParams params; +}; + +static void textColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data) +{ + struct textColumnParams *p = (struct textColumnParams *) data; + GValue value = G_VALUE_INIT; + const gchar *str; + + gtk_tree_model_get_value(m, iter, p->modelColumn, &value); + str = g_value_get_string(&value); + g_object_set(r, "text", str, NULL); + g_value_unset(&value); + + setEditable(p->m, iter, p->editableColumn, r, "editable"); + + if (p->params.ColorModelColumn != -1) + applyColor(m, iter, p->params.ColorModelColumn, + r, "foreground-rgba", "foreground-set"); + + applyBackgroundColor(p->t, m, iter, r); +} + +static void textColumnEdited(GtkCellRendererText *r, gchar *path, gchar *newText, gpointer data) +{ + struct textColumnParams *p = (struct textColumnParams *) data; + uiTableValue *tvalue; + GtkTreeIter iter; + + tvalue = uiNewTableValueString(newText); + onEdited(p->m, p->modelColumn, path, tvalue, &iter); + uiFreeTableValue(tvalue); + // and update the column TODO copy comment here + textColumnDataFunc(NULL, GTK_CELL_RENDERER(r), GTK_TREE_MODEL(p->m), &iter, data); +} + +struct imageColumnParams { + uiTable *t; + int modelColumn; +}; + +static void imageColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data) +{ + struct imageColumnParams *p = (struct imageColumnParams *) data; + GValue value = G_VALUE_INIT; + uiImage *img; + +//TODO setImageSize(r); + gtk_tree_model_get_value(m, iter, p->modelColumn, &value); + img = (uiImage *) g_value_get_pointer(&value); + g_object_set(r, "surface", + uiprivImageAppropriateSurface(img, p->t->treeWidget), + NULL); + g_value_unset(&value); + + applyBackgroundColor(p->t, m, iter, r); +} + +struct checkboxColumnParams { + uiTable *t; + uiTableModel *m; + int modelColumn; + int editableColumn; +}; + +static void checkboxColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data) +{ + struct checkboxColumnParams *p = (struct checkboxColumnParams *) data; + GValue value = G_VALUE_INIT; + gboolean active; + + gtk_tree_model_get_value(m, iter, p->modelColumn, &value); + active = g_value_get_int(&value) != 0; + g_object_set(r, "active", active, NULL); + g_value_unset(&value); + + setEditable(p->m, iter, p->editableColumn, r, "activatable"); + + applyBackgroundColor(p->t, m, iter, r); +} + +static void checkboxColumnToggled(GtkCellRendererToggle *r, gchar *pathstr, gpointer data) +{ + struct checkboxColumnParams *p = (struct checkboxColumnParams *) data; + GValue value = G_VALUE_INIT; + int v; + uiTableValue *tvalue; + GtkTreePath *path; + GtkTreeIter iter; + + path = gtk_tree_path_new_from_string(pathstr); + gtk_tree_model_get_iter(GTK_TREE_MODEL(p->m), &iter, path); + gtk_tree_path_free(path); + gtk_tree_model_get_value(GTK_TREE_MODEL(p->m), &iter, p->modelColumn, &value); + v = g_value_get_int(&value); + g_value_unset(&value); + tvalue = uiNewTableValueInt(!v); + onEdited(p->m, p->modelColumn, pathstr, tvalue, NULL); + uiFreeTableValue(tvalue); + // and update the column TODO copy comment here + // TODO avoid fetching the model data twice + checkboxColumnDataFunc(NULL, GTK_CELL_RENDERER(r), GTK_TREE_MODEL(p->m), &iter, data); +} + +struct progressBarColumnParams { + uiTable *t; + int modelColumn; +}; + +struct rowcol { + int row; + int col; +}; + +static guint rowcolHash(gconstpointer key) +{ + const struct rowcol *rc = (const struct rowcol *) key; + guint row, col; + + row = (guint) (rc->row); + col = (guint) (rc->col); + return row ^ col; +} + +static gboolean rowcolEqual(gconstpointer a, gconstpointer b) +{ + const struct rowcol *ra = (const struct rowcol *) a; + const struct rowcol *rb = (const struct rowcol *) b; + + return (ra->row == rb->row) && (ra->col == rb->col); +} + +static void pulseOne(gpointer key, gpointer value, gpointer data) +{ + uiTable *t = uiTable(data); + struct rowcol *rc = (struct rowcol *) key; + + // TODO this is bad: it produces changed handlers for every table because that's how GtkTreeModel works, yet this is per-table because that's how it works + // however, a proper fix would require decoupling progress from normal integers, which we could do... + uiTableModelRowChanged(t->model, rc->row); +} + +static gboolean indeterminatePulse(gpointer data) +{ + uiTable *t = uiTable(data); + + g_hash_table_foreach(t->indeterminatePositions, pulseOne, t); + return TRUE; +} + +static void progressBarColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data) +{ + struct progressBarColumnParams *p = (struct progressBarColumnParams *) data; + GValue value = G_VALUE_INIT; + int pval; + struct rowcol *rc; + gint *val; + GtkTreePath *path; + + gtk_tree_model_get_value(m, iter, p->modelColumn, &value); + pval = g_value_get_int(&value); + rc = uiprivNew(struct rowcol); + // TODO avoid the need for this + path = gtk_tree_model_get_path(GTK_TREE_MODEL(m), iter); + rc->row = gtk_tree_path_get_indices(path)[0]; + rc->col = p->modelColumn; + val = (gint *) g_hash_table_lookup(p->t->indeterminatePositions, rc); + if (pval == -1) { + if (val == NULL) { + val = uiprivNew(gint); + *val = 1; + g_hash_table_insert(p->t->indeterminatePositions, rc, val); + } else { + uiprivFree(rc); + (*val)++; + if (*val == G_MAXINT) + *val = 1; + } + g_object_set(r, + "pulse", *val, + NULL); + if (p->t->indeterminateTimer == 0) + // TODO verify the timeout + p->t->indeterminateTimer = g_timeout_add(100, indeterminatePulse, p->t); + } else { + if (val != NULL) { + g_hash_table_remove(p->t->indeterminatePositions, rc); + if (g_hash_table_size(p->t->indeterminatePositions) == 0) { + g_source_remove(p->t->indeterminateTimer); + p->t->indeterminateTimer = 0; + } + } + uiprivFree(rc); + g_object_set(r, + "pulse", -1, + "value", pval, + NULL); + } + g_value_unset(&value); + + applyBackgroundColor(p->t, m, iter, r); +} + +struct buttonColumnParams { + uiTable *t; + uiTableModel *m; + int modelColumn; + int clickableColumn; +}; + +static void buttonColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data) +{ + struct buttonColumnParams *p = (struct buttonColumnParams *) data; + GValue value = G_VALUE_INIT; + const gchar *str; + + gtk_tree_model_get_value(m, iter, p->modelColumn, &value); + str = g_value_get_string(&value); + g_object_set(r, "text", str, NULL); + g_value_unset(&value); + + setEditable(p->m, iter, p->clickableColumn, r, "sensitive"); + + applyBackgroundColor(p->t, m, iter, r); +} + +// TODO wrong type here +static void buttonColumnClicked(GtkCellRenderer *r, gchar *pathstr, gpointer data) +{ + struct buttonColumnParams *p = (struct buttonColumnParams *) data; + + onEdited(p->m, p->modelColumn, pathstr, NULL, NULL); +} + +static GtkTreeViewColumn *addColumn(uiTable *t, const char *name) +{ + GtkTreeViewColumn *c; + + c = gtk_tree_view_column_new(); + gtk_tree_view_column_set_resizable(c, TRUE); + gtk_tree_view_column_set_title(c, name); + gtk_tree_view_append_column(t->tv, c); + return c; +} + +static void addTextColumn(uiTable *t, GtkTreeViewColumn *c, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) +{ + struct textColumnParams *p; + GtkCellRenderer *r; + + p = uiprivNew(struct textColumnParams); + p->t = t; + // TODO get rid of these fields AND rename t->model in favor of t->m + p->m = t->model; + p->modelColumn = textModelColumn; + p->editableColumn = textEditableModelColumn; + if (textParams != NULL) + p->params = *textParams; + else + p->params = uiprivDefaultTextColumnOptionalParams; + + r = gtk_cell_renderer_text_new(); + gtk_tree_view_column_pack_start(c, r, TRUE); + gtk_tree_view_column_set_cell_data_func(c, r, textColumnDataFunc, p, NULL); + g_signal_connect(r, "edited", G_CALLBACK(textColumnEdited), p); + g_ptr_array_add(t->columnParams, p); +} + +// TODO rename modelCOlumn and params everywhere +void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) +{ + GtkTreeViewColumn *c; + + c = addColumn(t, name); + addTextColumn(t, c, textModelColumn, textEditableModelColumn, textParams); +} + +static void addImageColumn(uiTable *t, GtkTreeViewColumn *c, int imageModelColumn) +{ + struct imageColumnParams *p; + GtkCellRenderer *r; + + p = uiprivNew(struct imageColumnParams); + p->t = t; + p->modelColumn = imageModelColumn; + + r = gtk_cell_renderer_pixbuf_new(); + gtk_tree_view_column_pack_start(c, r, FALSE); + gtk_tree_view_column_set_cell_data_func(c, r, imageColumnDataFunc, p, NULL); + g_ptr_array_add(t->columnParams, p); +} + +void uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn) +{ + GtkTreeViewColumn *c; + + c = addColumn(t, name); + addImageColumn(t, c, imageModelColumn); +} + +void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) +{ + GtkTreeViewColumn *c; + + c = addColumn(t, name); + addImageColumn(t, c, imageModelColumn); + addTextColumn(t, c, textModelColumn, textEditableModelColumn, textParams); +} + +static void addCheckboxColumn(uiTable *t, GtkTreeViewColumn *c, int checkboxModelColumn, int checkboxEditableModelColumn) +{ + struct checkboxColumnParams *p; + GtkCellRenderer *r; + + p = uiprivNew(struct checkboxColumnParams); + p->t = t; + p->m = t->model; + p->modelColumn = checkboxModelColumn; + p->editableColumn = checkboxEditableModelColumn; + + r = gtk_cell_renderer_toggle_new(); + gtk_tree_view_column_pack_start(c, r, FALSE); + gtk_tree_view_column_set_cell_data_func(c, r, checkboxColumnDataFunc, p, NULL); + g_signal_connect(r, "toggled", G_CALLBACK(checkboxColumnToggled), p); + g_ptr_array_add(t->columnParams, p); +} + +void uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn) +{ + GtkTreeViewColumn *c; + + c = addColumn(t, name); + addCheckboxColumn(t, c, checkboxModelColumn, checkboxEditableModelColumn); +} + +void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) +{ + GtkTreeViewColumn *c; + + c = addColumn(t, name); + addCheckboxColumn(t, c, checkboxModelColumn, checkboxEditableModelColumn); + addTextColumn(t, c, textModelColumn, textEditableModelColumn, textParams); +} + +void uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressModelColumn) +{ + GtkTreeViewColumn *c; + struct progressBarColumnParams *p; + GtkCellRenderer *r; + + c = addColumn(t, name); + + p = uiprivNew(struct progressBarColumnParams); + p->t = t; + // TODO make progress and progressBar consistent everywhere + p->modelColumn = progressModelColumn; + + r = gtk_cell_renderer_progress_new(); + gtk_tree_view_column_pack_start(c, r, TRUE); + gtk_tree_view_column_set_cell_data_func(c, r, progressBarColumnDataFunc, p, NULL); + g_ptr_array_add(t->columnParams, p); +} + +void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonModelColumn, int buttonClickableModelColumn) +{ + GtkTreeViewColumn *c; + struct buttonColumnParams *p; + GtkCellRenderer *r; + + c = addColumn(t, name); + + p = uiprivNew(struct buttonColumnParams); + p->t = t; + p->m = t->model; + p->modelColumn = buttonModelColumn; + p->clickableColumn = buttonClickableModelColumn; + + r = uiprivNewCellRendererButton(); + gtk_tree_view_column_pack_start(c, r, TRUE); + gtk_tree_view_column_set_cell_data_func(c, r, buttonColumnDataFunc, p, NULL); + g_signal_connect(r, "clicked", G_CALLBACK(buttonColumnClicked), p); + g_ptr_array_add(t->columnParams, p); +} + +uiUnixControlAllDefaultsExceptDestroy(uiTable) + +static void uiTableDestroy(uiControl *c) +{ + uiTable *t = uiTable(c); + guint i; + + for (i = 0; i < t->columnParams->len; i++) + uiprivFree(g_ptr_array_index(t->columnParams, i)); + g_ptr_array_free(t->columnParams, TRUE); + if (g_hash_table_size(t->indeterminatePositions) != 0) + g_source_remove(t->indeterminateTimer); + g_hash_table_destroy(t->indeterminatePositions); + g_object_unref(t->widget); + uiFreeControl(uiControl(t)); +} + +uiTable *uiNewTable(uiTableParams *p) +{ + uiTable *t; + + uiUnixNewControl(uiTable, t); + + t->model = p->Model; + t->columnParams = g_ptr_array_new(); + t->backgroundColumn = p->RowBackgroundColorModelColumn; + + t->widget = gtk_scrolled_window_new(NULL, NULL); + t->scontainer = GTK_CONTAINER(t->widget); + t->sw = GTK_SCROLLED_WINDOW(t->widget); + gtk_scrolled_window_set_shadow_type(t->sw, GTK_SHADOW_IN); + + t->treeWidget = gtk_tree_view_new_with_model(GTK_TREE_MODEL(t->model)); + t->tv = GTK_TREE_VIEW(t->treeWidget); + // TODO set up t->tv + + gtk_container_add(t->scontainer, t->treeWidget); + // and make the tree view visible; only the scrolled window's visibility is controlled by libui + gtk_widget_show(t->treeWidget); + + t->indeterminatePositions = g_hash_table_new_full(rowcolHash, rowcolEqual, + uiprivFree, uiprivFree); + + return t; +} diff --git a/unix/table.h b/unix/table.h new file mode 100644 index 00000000..af7e954a --- /dev/null +++ b/unix/table.h @@ -0,0 +1,19 @@ +// 4 june 2018 +#include "../common/table.h" + +// tablemodel.c +#define uiTableModelType (uiTableModel_get_type()) +#define uiTableModel(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), uiTableModelType, uiTableModel)) +#define isuiTableModel(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), uiTableModelType)) +#define uiTableModelClass(class) (G_TYPE_CHECK_CLASS_CAST((class), uiTableModelType, uiTableModelClass)) +#define isuiTableModelClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), uiTableModel)) +#define getuiTableModelClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), uiTableModelType, uiTableModelClass)) +typedef struct uiTableModelClass uiTableModelClass; +struct uiTableModel { + GObject parent_instance; + uiTableModelHandler *mh; +}; +struct uiTableModelClass { + GObjectClass parent_class; +}; +extern GType uiTableModel_get_type(void); diff --git a/unix/tablemodel.c b/unix/tablemodel.c new file mode 100644 index 00000000..ee957b58 --- /dev/null +++ b/unix/tablemodel.c @@ -0,0 +1,288 @@ +// 26 june 2016 +#include "uipriv_unix.h" +#include "table.h" + +static void uiTableModel_gtk_tree_model_interface_init(GtkTreeModelIface *iface); + +G_DEFINE_TYPE_WITH_CODE(uiTableModel, uiTableModel, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(GTK_TYPE_TREE_MODEL, uiTableModel_gtk_tree_model_interface_init)) + +static void uiTableModel_init(uiTableModel *m) +{ + // nothing to do +} + +static void uiTableModel_dispose(GObject *obj) +{ + G_OBJECT_CLASS(uiTableModel_parent_class)->dispose(obj); +} + +static void uiTableModel_finalize(GObject *obj) +{ + G_OBJECT_CLASS(uiTableModel_parent_class)->finalize(obj); +} + +static GtkTreeModelFlags uiTableModel_get_flags(GtkTreeModel *mm) +{ + return GTK_TREE_MODEL_LIST_ONLY; +} + +static gint uiTableModel_get_n_columns(GtkTreeModel *mm) +{ + uiTableModel *m = uiTableModel(mm); + + return uiprivTableModelNumColumns(m); +} + +static GType uiTableModel_get_column_type(GtkTreeModel *mm, gint index) +{ + uiTableModel *m = uiTableModel(mm); + + switch (uiprivTableModelColumnType(m, index)) { + case uiTableValueTypeString: + return G_TYPE_STRING; + case uiTableValueTypeImage: + return G_TYPE_POINTER; + case uiTableValueTypeInt: + return G_TYPE_INT; + case uiTableValueTypeColor: + return GDK_TYPE_RGBA; + } + // TODO + return G_TYPE_INVALID; +} + +#define STAMP_GOOD 0x1234 +#define STAMP_BAD 0x5678 + +static gboolean uiTableModel_get_iter(GtkTreeModel *mm, GtkTreeIter *iter, GtkTreePath *path) +{ + uiTableModel *m = uiTableModel(mm); + gint row; + + if (gtk_tree_path_get_depth(path) != 1) + goto bad; + row = gtk_tree_path_get_indices(path)[0]; + if (row < 0) + goto bad; + if (row >= uiprivTableModelNumRows(m)) + goto bad; + iter->stamp = STAMP_GOOD; + iter->user_data = GINT_TO_POINTER(row); + return TRUE; +bad: + iter->stamp = STAMP_BAD; + return FALSE; +} + +// GtkListStore returns NULL on error; let's do that too +static GtkTreePath *uiTableModel_get_path(GtkTreeModel *mm, GtkTreeIter *iter) +{ + gint row; + + if (iter->stamp != STAMP_GOOD) + return NULL; + row = GPOINTER_TO_INT(iter->user_data); + return gtk_tree_path_new_from_indices(row, -1); +} + +// GtkListStore leaves value empty on failure; let's do the same +static void uiTableModel_get_value(GtkTreeModel *mm, GtkTreeIter *iter, gint column, GValue *value) +{ + uiTableModel *m = uiTableModel(mm); + gint row; + uiTableValue *tvalue; + double r, g, b, a; + GdkRGBA rgba; + + if (iter->stamp != STAMP_GOOD) + return; + row = GPOINTER_TO_INT(iter->user_data); + tvalue = uiprivTableModelCellValue(m, row, column); + switch (uiprivTableModelColumnType(m, column)) { + case uiTableValueTypeString: + g_value_init(value, G_TYPE_STRING); + g_value_set_string(value, uiTableValueString(tvalue)); + uiFreeTableValue(tvalue); + return; + case uiTableValueTypeImage: + g_value_init(value, G_TYPE_POINTER); + g_value_set_pointer(value, uiTableValueImage(tvalue)); + uiFreeTableValue(tvalue); + return; + case uiTableValueTypeInt: + g_value_init(value, G_TYPE_INT); + g_value_set_int(value, uiTableValueInt(tvalue)); + uiFreeTableValue(tvalue); + return; + case uiTableValueTypeColor: + g_value_init(value, GDK_TYPE_RGBA); + if (tvalue == NULL) { + g_value_set_boxed(value, NULL); + return; + } + uiTableValueColor(tvalue, &r, &g, &b, &a); + uiFreeTableValue(tvalue); + rgba.red = r; + rgba.green = g; + rgba.blue = b; + rgba.alpha = a; + g_value_set_boxed(value, &rgba); + return; + } + // TODO +} + +static gboolean uiTableModel_iter_next(GtkTreeModel *mm, GtkTreeIter *iter) +{ + uiTableModel *m = uiTableModel(mm); + gint row; + + if (iter->stamp != STAMP_GOOD) + return FALSE; + row = GPOINTER_TO_INT(iter->user_data); + row++; + if (row >= uiprivTableModelNumRows(m)) { + iter->stamp = STAMP_BAD; + return FALSE; + } + iter->user_data = GINT_TO_POINTER(row); + return TRUE; +} + +static gboolean uiTableModel_iter_previous(GtkTreeModel *mm, GtkTreeIter *iter) +{ + gint row; + + if (iter->stamp != STAMP_GOOD) + return FALSE; + row = GPOINTER_TO_INT(iter->user_data); + row--; + if (row < 0) { + iter->stamp = STAMP_BAD; + return FALSE; + } + iter->user_data = GINT_TO_POINTER(row); + return TRUE; +} + +static gboolean uiTableModel_iter_children(GtkTreeModel *mm, GtkTreeIter *iter, GtkTreeIter *parent) +{ + return gtk_tree_model_iter_nth_child(mm, iter, parent, 0); +} + +static gboolean uiTableModel_iter_has_child(GtkTreeModel *mm, GtkTreeIter *iter) +{ + return FALSE; +} + +static gint uiTableModel_iter_n_children(GtkTreeModel *mm, GtkTreeIter *iter) +{ + uiTableModel *m = uiTableModel(mm); + + if (iter != NULL) + return 0; + return uiprivTableModelNumRows(m); +} + +static gboolean uiTableModel_iter_nth_child(GtkTreeModel *mm, GtkTreeIter *iter, GtkTreeIter *parent, gint n) +{ + uiTableModel *m = uiTableModel(mm); + + if (iter->stamp != STAMP_GOOD) + return FALSE; + if (parent != NULL) + goto bad; + if (n < 0) + goto bad; + if (n >= uiprivTableModelNumRows(m)) + goto bad; + iter->stamp = STAMP_GOOD; + iter->user_data = GINT_TO_POINTER(n); + return TRUE; +bad: + iter->stamp = STAMP_BAD; + return FALSE; +} + +gboolean uiTableModel_iter_parent(GtkTreeModel *mm, GtkTreeIter *iter, GtkTreeIter *child) +{ + iter->stamp = STAMP_BAD; + return FALSE; +} + +static void uiTableModel_class_init(uiTableModelClass *class) +{ + G_OBJECT_CLASS(class)->dispose = uiTableModel_dispose; + G_OBJECT_CLASS(class)->finalize = uiTableModel_finalize; +} + +static void uiTableModel_gtk_tree_model_interface_init(GtkTreeModelIface *iface) +{ + iface->get_flags = uiTableModel_get_flags; + iface->get_n_columns = uiTableModel_get_n_columns; + iface->get_column_type = uiTableModel_get_column_type; + iface->get_iter = uiTableModel_get_iter; + iface->get_path = uiTableModel_get_path; + iface->get_value = uiTableModel_get_value; + iface->iter_next = uiTableModel_iter_next; + iface->iter_previous = uiTableModel_iter_previous; + iface->iter_children = uiTableModel_iter_children; + iface->iter_has_child = uiTableModel_iter_has_child; + iface->iter_n_children = uiTableModel_iter_n_children; + iface->iter_nth_child = uiTableModel_iter_nth_child; + iface->iter_parent = uiTableModel_iter_parent; + // don't specify ref_node() or unref_node() +} + +uiTableModel *uiNewTableModel(uiTableModelHandler *mh) +{ + uiTableModel *m; + + m = uiTableModel(g_object_new(uiTableModelType, NULL)); + m->mh = mh; + return m; +} + +void uiFreeTableModel(uiTableModel *m) +{ + g_object_unref(m); +} + +void uiTableModelRowInserted(uiTableModel *m, int newIndex) +{ + GtkTreePath *path; + GtkTreeIter iter; + + path = gtk_tree_path_new_from_indices(newIndex, -1); + iter.stamp = STAMP_GOOD; + iter.user_data = GINT_TO_POINTER(newIndex); + gtk_tree_model_row_inserted(GTK_TREE_MODEL(m), path, &iter); + gtk_tree_path_free(path); +} + +void uiTableModelRowChanged(uiTableModel *m, int index) +{ + GtkTreePath *path; + GtkTreeIter iter; + + path = gtk_tree_path_new_from_indices(index, -1); + iter.stamp = STAMP_GOOD; + iter.user_data = GINT_TO_POINTER(index); + gtk_tree_model_row_changed(GTK_TREE_MODEL(m), path, &iter); + gtk_tree_path_free(path); +} + +void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) +{ + GtkTreePath *path; + + path = gtk_tree_path_new_from_indices(oldIndex, -1); + gtk_tree_model_row_deleted(GTK_TREE_MODEL(m), path); + gtk_tree_path_free(path); +} + +uiTableModelHandler *uiprivTableModelHandler(uiTableModel *m) +{ + return m->mh; +} diff --git a/unix/text.c b/unix/text.c index ad92738d..9a2a9dc0 100644 --- a/unix/text.c +++ b/unix/text.c @@ -10,3 +10,8 @@ void uiFreeText(char *t) { g_free(t); } + +int uiprivStricmp(const char *a, const char *b) +{ + return strcasecmp(a, b); +} diff --git a/unix/uipriv_unix.h b/unix/uipriv_unix.h index 6a260830..de604ced 100644 --- a/unix/uipriv_unix.h +++ b/unix/uipriv_unix.h @@ -1,48 +1,61 @@ // 22 april 2015 -#define GLIB_VERSION_MIN_REQUIRED GLIB_VERSION_2_32 -#define GLIB_VERSION_MAX_ALLOWED GLIB_VERSION_2_32 -#define GDK_VERSION_MIN_REQUIRED GDK_VERSION_3_4 -#define GDK_VERSION_MAX_ALLOWED GDK_VERSION_3_4 -// TODO make this unnecessary -#define _GNU_SOURCE +#define GLIB_VERSION_MIN_REQUIRED GLIB_VERSION_2_40 +#define GLIB_VERSION_MAX_ALLOWED GLIB_VERSION_2_40 +#define GDK_VERSION_MIN_REQUIRED GDK_VERSION_3_10 +#define GDK_VERSION_MAX_ALLOWED GDK_VERSION_3_10 #include #include -#include // see drawtext.c +#include // see future.c #include +#include +#include +#include #include "../ui.h" #include "../ui_unix.h" #include "../common/uipriv.h" -#define gtkXMargin 12 -#define gtkYMargin 12 -#define gtkXPadding 12 -#define gtkYPadding 6 +#define uiprivGTKXMargin 12 +#define uiprivGTKYMargin 12 +#define uiprivGTKXPadding 12 +#define uiprivGTKYPadding 6 // menu.c -extern GtkWidget *makeMenubar(uiWindow *); -extern void freeMenubar(GtkWidget *); -extern void uninitMenus(void); +extern GtkWidget *uiprivMakeMenubar(uiWindow *); +extern void uiprivFreeMenubar(GtkWidget *); +extern void uiprivUninitMenus(void); // alloc.c -extern void initAlloc(void); -extern void uninitAlloc(void); +extern void uiprivInitAlloc(void); +extern void uiprivUninitAlloc(void); // util.c -extern void setMargined(GtkContainer *, int); +extern void uiprivSetMargined(GtkContainer *, int); // child.c -extern struct child *newChild(uiControl *child, uiControl *parent, GtkContainer *parentContainer); -extern struct child *newChildWithBox(uiControl *child, uiControl *parent, GtkContainer *parentContainer, int margined); -extern void childRemove(struct child *c); -extern void childDestroy(struct child *c); -extern GtkWidget *childWidget(struct child *c); -extern int childFlag(struct child *c); -extern void childSetFlag(struct child *c, int flag); -extern GtkWidget *childBox(struct child *c); -extern void childSetMargined(struct child *c, int margined); +typedef struct uiprivChild uiprivChild; +extern uiprivChild *uiprivNewChild(uiControl *child, uiControl *parent, GtkContainer *parentContainer); +extern uiprivChild *uiprivNewChildWithBox(uiControl *child, uiControl *parent, GtkContainer *parentContainer, int margined); +extern void uiprivChildRemove(uiprivChild *c); +extern void uiprivChildDestroy(uiprivChild *c); +extern GtkWidget *uiprivChildWidget(uiprivChild *c); +extern int uiprivChildFlag(uiprivChild *c); +extern void uiprivChildSetFlag(uiprivChild *c, int flag); +extern GtkWidget *uiprivChildBox(uiprivChild *c); +extern void uiprivChildSetMargined(uiprivChild *c, int margined); // draw.c -extern uiDrawContext *newContext(cairo_t *); -extern void freeContext(uiDrawContext *); -extern uiDrawTextFont *mkTextFont(PangoFont *f, gboolean add); -extern PangoFont *pangoDescToPangoFont(PangoFontDescription *pdesc); +extern uiDrawContext *uiprivNewContext(cairo_t *cr, GtkStyleContext *style); +extern void uiprivFreeContext(uiDrawContext *); + +// image.c +extern cairo_surface_t *uiprivImageAppropriateSurface(uiImage *i, GtkWidget *w); + +// cellrendererbutton.c +extern GtkCellRenderer *uiprivNewCellRendererButton(void); + +// future.c +extern void uiprivLoadFutures(void); +extern PangoAttribute *uiprivFUTURE_pango_attr_font_features_new(const gchar *features); +extern PangoAttribute *uiprivFUTURE_pango_attr_foreground_alpha_new(guint16 alpha); +extern PangoAttribute *uiprivFUTURE_pango_attr_background_alpha_new(guint16 alpha); +extern gboolean uiprivFUTURE_gtk_widget_path_iter_set_object_name(GtkWidgetPath *path, gint pos, const char *name); diff --git a/unix/util.c b/unix/util.c index 86d89a3e..f3929ccb 100644 --- a/unix/util.c +++ b/unix/util.c @@ -1,21 +1,10 @@ // 18 april 2015 #include "uipriv_unix.h" -void complain(const char *fmt, ...) -{ - va_list ap; - char *msg; - - va_start(ap, fmt); - msg = g_strdup_vprintf(fmt, ap); - va_end(ap); - g_error("[libui] %s\n", msg); -} - -void setMargined(GtkContainer *c, int margined) +void uiprivSetMargined(GtkContainer *c, int margined) { if (margined) - gtk_container_set_border_width(c, gtkXMargin); + gtk_container_set_border_width(c, uiprivGTKXMargin); else gtk_container_set_border_width(c, 0); } diff --git a/unix/window.c b/unix/window.c index ba2c9700..c5ba2038 100644 --- a/unix/window.c +++ b/unix/window.c @@ -12,13 +12,19 @@ struct uiWindow { GtkContainer *vboxContainer; GtkBox *vbox; + GtkWidget *childHolderWidget; + GtkContainer *childHolderContainer; + GtkWidget *menubar; - struct child *child; + uiControl *child; int margined; int (*onClosing)(uiWindow *, void *); void *onClosingData; + void (*onContentSizeChanged)(uiWindow *, void *); + void *onContentSizeChangedData; + gboolean fullscreen; }; static gboolean onClosing(GtkWidget *win, GdkEvent *e, gpointer data) @@ -32,11 +38,24 @@ static gboolean onClosing(GtkWidget *win, GdkEvent *e, gpointer data) return TRUE; } +static void onSizeAllocate(GtkWidget *widget, GdkRectangle *allocation, gpointer data) +{ + uiWindow *w = uiWindow(data); + + // TODO deal with spurious size-allocates + (*(w->onContentSizeChanged))(w, w->onContentSizeChangedData); +} + static int defaultOnClosing(uiWindow *w, void *data) { return 0; } +static void defaultOnPositionContentSizeChanged(uiWindow *w, void *data) +{ + // do nothing +} + static void uiWindowDestroy(uiControl *c) { uiWindow *w = uiWindow(c); @@ -44,22 +63,33 @@ static void uiWindowDestroy(uiControl *c) // first hide ourselves gtk_widget_hide(w->widget); // now destroy the child - if (w->child != NULL) - childDestroy(w->child); + if (w->child != NULL) { + uiControlSetParent(w->child, NULL); + uiUnixControlSetContainer(uiUnixControl(w->child), w->childHolderContainer, TRUE); + uiControlDestroy(w->child); + } // now destroy the menus, if any if (w->menubar != NULL) - freeMenubar(w->menubar); + uiprivFreeMenubar(w->menubar); + gtk_widget_destroy(w->childHolderWidget); gtk_widget_destroy(w->vboxWidget); // and finally free ourselves - g_object_unref(w->widget); + // use gtk_widget_destroy() instead of g_object_unref() because GTK+ has internal references (see #165) + gtk_widget_destroy(w->widget); uiFreeControl(uiControl(w)); } uiUnixControlDefaultHandle(uiWindow) -// TODO? -uiUnixControlDefaultParent(uiWindow) -uiUnixControlDefaultSetParent(uiWindow) -// end TODO + +uiControl *uiWindowParent(uiControl *c) +{ + return NULL; +} + +void uiWindowSetParent(uiControl *c, uiControl *parent) +{ + uiUserBugCannotSetParentOnToplevel("uiWindow"); +} static int uiWindowToplevel(uiControl *c) { @@ -95,22 +125,96 @@ void uiWindowSetTitle(uiWindow *w, const char *title) gtk_window_set_title(w->window, title); } +void uiWindowContentSize(uiWindow *w, int *width, int *height) +{ + GtkAllocation allocation; + + gtk_widget_get_allocation(w->childHolderWidget, &allocation); + *width = allocation.width; + *height = allocation.height; +} + +void uiWindowSetContentSize(uiWindow *w, int width, int height) +{ + GtkAllocation childAlloc; + gint winWidth, winHeight; + + // we need to resize the child holder widget to the given size + // we can't resize that without running the event loop + // but we can do gtk_window_set_size() + // so how do we deal with the differences in sizes? + // simple arithmetic, of course! + + // from what I can tell, the return from gtk_widget_get_allocation(w->window) and gtk_window_get_size(w->window) will be the same + // this is not affected by Wayland and not affected by GTK+ builtin CSD + // so we can safely juse use them to get the real window size! + // since we're using gtk_window_resize(), use the latter + gtk_window_get_size(w->window, &winWidth, &winHeight); + + // now get the child holder widget's current allocation + gtk_widget_get_allocation(w->childHolderWidget, &childAlloc); + // and punch that out of the window size + winWidth -= childAlloc.width; + winHeight -= childAlloc.height; + + // now we just need to add the new size back in + winWidth += width; + winHeight += height; + // and set it + // this will not move the window in my tests, so we're good + gtk_window_resize(w->window, winWidth, winHeight); +} + +int uiWindowFullscreen(uiWindow *w) +{ + return w->fullscreen; +} + +// TODO use window-state-event to track +// TODO does this send an extra size changed? +// TODO what behavior do we want? +void uiWindowSetFullscreen(uiWindow *w, int fullscreen) +{ + w->fullscreen = fullscreen; + if (w->fullscreen) + gtk_window_fullscreen(w->window); + else + gtk_window_unfullscreen(w->window); +} + +void uiWindowOnContentSizeChanged(uiWindow *w, void (*f)(uiWindow *, void *), void *data) +{ + w->onContentSizeChanged = f; + w->onContentSizeChangedData = data; +} + void uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *, void *), void *data) { w->onClosing = f; w->onClosingData = data; } +int uiWindowBorderless(uiWindow *w) +{ + return gtk_window_get_decorated(w->window) == FALSE; +} + +void uiWindowSetBorderless(uiWindow *w, int borderless) +{ + gtk_window_set_decorated(w->window, borderless == 0); +} + +// TODO save and restore expands and aligns void uiWindowSetChild(uiWindow *w, uiControl *child) { - if (w->child != NULL) - childRemove(w->child); - w->child = newChildWithBox(child, uiControl(w), w->vboxContainer, w->margined); if (w->child != NULL) { - gtk_widget_set_hexpand(childBox(w->child), TRUE); - gtk_widget_set_halign(childBox(w->child), GTK_ALIGN_FILL); - gtk_widget_set_vexpand(childBox(w->child), TRUE); - gtk_widget_set_valign(childBox(w->child), GTK_ALIGN_FILL); + uiControlSetParent(w->child, NULL); + uiUnixControlSetContainer(uiUnixControl(w->child), w->childHolderContainer, TRUE); + } + w->child = child; + if (w->child != NULL) { + uiControlSetParent(w->child, uiControl(w)); + uiUnixControlSetContainer(uiUnixControl(w->child), w->childHolderContainer, FALSE); } } @@ -122,8 +226,7 @@ int uiWindowMargined(uiWindow *w) void uiWindowSetMargined(uiWindow *w, int margined) { w->margined = margined; - if (w->child != NULL) - childSetMargined(w->child, w->margined); + uiprivSetMargined(w->childHolderContainer, w->margined); } uiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar) @@ -147,16 +250,30 @@ uiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar) gtk_container_add(w->container, w->vboxWidget); if (hasMenubar) { - w->menubar = makeMenubar(uiWindow(w)); + w->menubar = uiprivMakeMenubar(uiWindow(w)); gtk_container_add(w->vboxContainer, w->menubar); } + w->childHolderWidget = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + w->childHolderContainer = GTK_CONTAINER(w->childHolderWidget); + gtk_widget_set_hexpand(w->childHolderWidget, TRUE); + gtk_widget_set_halign(w->childHolderWidget, GTK_ALIGN_FILL); + gtk_widget_set_vexpand(w->childHolderWidget, TRUE); + gtk_widget_set_valign(w->childHolderWidget, GTK_ALIGN_FILL); + gtk_container_add(w->vboxContainer, w->childHolderWidget); + // show everything in the vbox, but not the GtkWindow itself gtk_widget_show_all(w->vboxWidget); - // and connect our OnClosing() event + // and connect our events g_signal_connect(w->widget, "delete-event", G_CALLBACK(onClosing), w); + g_signal_connect(w->childHolderWidget, "size-allocate", G_CALLBACK(onSizeAllocate), w); uiWindowOnClosing(w, defaultOnClosing, NULL); + uiWindowOnContentSizeChanged(w, defaultOnPositionContentSizeChanged, NULL); + + // normally it's SetParent() that does this, but we can't call SetParent() on a uiWindow + // TODO we really need to clean this up, especially since see uiWindowDestroy() above + g_object_ref(w->widget); return w; } diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt new file mode 100644 index 00000000..ac9af411 --- /dev/null +++ b/windows/CMakeLists.txt @@ -0,0 +1,94 @@ +# 3 june 2016 + +list(APPEND _LIBUI_SOURCES + windows/alloc.cpp + windows/area.cpp + windows/areadraw.cpp + windows/areaevents.cpp + windows/areascroll.cpp + windows/areautil.cpp + windows/attrstr.cpp + windows/box.cpp + windows/button.cpp + windows/checkbox.cpp + windows/colorbutton.cpp + windows/colordialog.cpp + windows/combobox.cpp + windows/container.cpp + windows/control.cpp + windows/d2dscratch.cpp + windows/datetimepicker.cpp + windows/debug.cpp + windows/draw.cpp + windows/drawmatrix.cpp + windows/drawpath.cpp + windows/drawtext.cpp + windows/dwrite.cpp + windows/editablecombo.cpp + windows/entry.cpp + windows/events.cpp + windows/fontbutton.cpp + windows/fontdialog.cpp + windows/fontmatch.cpp + windows/form.cpp + windows/graphemes.cpp + windows/grid.cpp + windows/group.cpp + windows/image.cpp + windows/init.cpp + windows/label.cpp + windows/main.cpp + windows/menu.cpp + windows/multilineentry.cpp + windows/opentype.cpp + windows/parent.cpp + windows/progressbar.cpp + windows/radiobuttons.cpp + windows/separator.cpp + windows/sizing.cpp + windows/slider.cpp + windows/spinbox.cpp + windows/stddialogs.cpp + windows/tab.cpp + windows/table.cpp + windows/tabledispinfo.cpp + windows/tabledraw.cpp + windows/tableediting.cpp + windows/tablemetrics.cpp + windows/tabpage.cpp + windows/text.cpp + windows/utf16.cpp + windows/utilwin.cpp + windows/window.cpp + windows/winpublic.cpp + windows/winutil.cpp +) +# resources.rc only contains the libui manifest. +# For a DLL, we have to include this directly, so we do so. +# Windows won't link resources in static libraries, so including this would have no effect. +# In those cases, we just need them to include the manifest with the executable (or link it directly into the output executable themselves); they can also customize the manifest as they see fit (assuming nothing breaks in the process). +# TODO make sure this gets added to both binary-only archives and install rules in this case +if(BUILD_SHARED_LIBS) + list(APPEND _LIBUI_SOURCES + windows/resources.rc + ) +endif() +set(_LIBUI_SOURCES ${_LIBUI_SOURCES} PARENT_SCOPE) + +list(APPEND _LIBUI_INCLUDEDIRS + windows +) +set(_LIBUI_INCLUDEDIRS _LIBUI_INCLUDEDIRS PARENT_SCOPE) + +# TODO prune this list +set(_LIBUI_LIBS + user32 kernel32 gdi32 comctl32 uxtheme msimg32 comdlg32 d2d1 dwrite ole32 oleaut32 oleacc uuid windowscodecs +PARENT_SCOPE) + +if(NOT MSVC) + if(BUILD_SHARED_LIBS) + message(FATAL_ERROR + "Sorry, but libui for Windows can currently only be built as a static library with MinGW. You will need to either build as a static library or switch to MSVC." + ) + endif() +endif() diff --git a/windows/GNUfiles.mk b/windows/GNUfiles.mk deleted file mode 100644 index 312b3b42..00000000 --- a/windows/GNUfiles.mk +++ /dev/null @@ -1,78 +0,0 @@ -# 22 april 2015 - -CXXFILES += \ - windows/alloc.cpp \ - windows/area.cpp \ - windows/areadraw.cpp \ - windows/areaevents.cpp \ - windows/areascroll.cpp \ - windows/areautil.cpp \ - windows/box.cpp \ - windows/button.cpp \ - windows/checkbox.cpp \ - windows/combobox.cpp \ - windows/container.cpp \ - windows/control.cpp \ - windows/d2dscratch.cpp \ - windows/datetimepicker.cpp \ - windows/debug.cpp \ - windows/draw.cpp \ - windows/drawmatrix.cpp \ - windows/drawpath.cpp \ - windows/drawtext.cpp \ - windows/dwrite.cpp \ - windows/entry.cpp \ - windows/events.cpp \ - windows/fontbutton.cpp \ - windows/fontdialog.cpp \ - windows/group.cpp \ - windows/init.cpp \ - windows/label.cpp \ - windows/main.cpp \ - windows/menu.cpp \ - windows/multilineentry.cpp \ - windows/parent.cpp \ - windows/progressbar.cpp \ - windows/radiobuttons.cpp \ - windows/separator.cpp \ - windows/sizing.cpp \ - windows/slider.cpp \ - windows/spinbox.cpp \ - windows/stddialogs.cpp \ - windows/tab.cpp \ - windows/tabpage.cpp \ - windows/text.cpp \ - windows/utf16.cpp \ - windows/utilwin.cpp \ - windows/window.cpp \ - windows/winpublic.cpp \ - windows/winutil.cpp - -HFILES += \ - windows/_uipriv_migrate.hpp \ - windows/area.hpp \ - windows/compilerver.hpp \ - windows/draw.hpp \ - windows/resources.hpp \ - windows/uipriv_windows.hpp \ - windows/winapi.hpp - -RCFILES += \ - windows/resources.rc - -# TODO split into a separate file or put in GNUmakefile.libui somehow? - -# flags for the Windows API -# TODO prune this list -LDFLAGS += \ - user32.lib kernel32.lib gdi32.lib comctl32.lib uxtheme.lib msimg32.lib comdlg32.lib d2d1.lib dwrite.lib ole32.lib oleaut32.lib oleacc.lib uuid.lib - -# flags for building a shared library -LDFLAGS += \ - -dll - -# TODO flags for warning on undefined symbols - -# no need for a soname - -# TODO .def file diff --git a/windows/GNUinstall.mk b/windows/GNUinstall.mk deleted file mode 100644 index 1d783c01..00000000 --- a/windows/GNUinstall.mk +++ /dev/null @@ -1,3 +0,0 @@ -install: - @echo "No install for windows !" - @exit 1 diff --git a/windows/GNUosspecific.mk b/windows/GNUosspecific.mk deleted file mode 100644 index a4da078f..00000000 --- a/windows/GNUosspecific.mk +++ /dev/null @@ -1,8 +0,0 @@ -# 16 october 2015 - -EXESUFFIX = .exe -LIBSUFFIX = .dll -OSHSUFFIX = .h -TOOLCHAIN = msvc - -USESSONAME = 0 diff --git a/windows/_rc2bin/build.bat b/windows/_rc2bin/build.bat new file mode 100644 index 00000000..5aaccf4c --- /dev/null +++ b/windows/_rc2bin/build.bat @@ -0,0 +1,10 @@ +@rem 2 may 2018 +@echo off + +cl /nologo /TP /GR /EHsc /MDd /Ob0 /Od /RTC1 /W4 /wd4100 /bigobj /RTC1 /RTCs /RTCu /FS -c main.cpp +if errorlevel 1 goto out +rc -foresources.res resources.rc +if errorlevel 1 goto out +link /nologo main.obj resources.res /out:main.exe /LARGEADDRESSAWARE /NOLOGO /INCREMENTAL:NO /MANIFEST:NO /debug user32.lib kernel32.lib gdi32.lib comctl32.lib uxtheme.lib msimg32.lib comdlg32.lib d2d1.lib dwrite.lib ole32.lib oleaut32.lib oleacc.lib uuid.lib + +:out diff --git a/windows/_rc2bin/libui.manifest b/windows/_rc2bin/libui.manifest new file mode 100644 index 00000000..8beb6cfc --- /dev/null +++ b/windows/_rc2bin/libui.manifest @@ -0,0 +1,31 @@ + + + +Your application description here. + + + + + + + + + + + + + + + diff --git a/windows/_rc2bin/main.cpp b/windows/_rc2bin/main.cpp new file mode 100644 index 00000000..2143fc80 --- /dev/null +++ b/windows/_rc2bin/main.cpp @@ -0,0 +1,69 @@ +// 2 may 2018 +#include "winapi.hpp" +#include +#include +#include "resources.hpp" + +// TODO make sure there are no CRs in the output + +void die(const char *f, const char *constname) +{ + DWORD le; + + le = GetLastError(); + fprintf(stderr, "error calling %s for %s: %I32d\n", f, constname, le); + exit(1); +} + +void dumpResource(const char *constname, const WCHAR *name, const WCHAR *type) +{ + HRSRC hrsrc; + HGLOBAL res; + uint8_t *b, *bp; + DWORD i, n; + DWORD j; + + hrsrc = FindResourceW(NULL, name, type); + if (hrsrc == NULL) + die("FindResourceW()", constname); + n = SizeofResource(NULL, hrsrc); + if (n == 0) + die("SizeofResource()", constname); + res = LoadResource(NULL, hrsrc); + if (res == NULL) + die("LoadResource()", constname); + b = (uint8_t *) LockResource(res); + if (b == NULL) + die("LockResource()", constname); + + printf("static const uint8_t %s[] = {\n", constname); + bp = b; + j = 0; + for (i = 0; i < n; i++) { + if (j == 0) + printf("\t"); + printf("0x%02I32X,", (uint32_t) (*bp)); + bp++; + if (j == 7) { + printf("\n"); + j = 0; + } else { + printf(" "); + j++; + } + } + if (j != 0) + printf("\n"); + printf("};\n"); + printf("static_assert(ARRAYSIZE(%s) == %I32d, \"wrong size for resource %s\");\n", constname, n, constname); + printf("\n"); +} + +int main(void) +{ +#define d(c, t) dumpResource(#c, MAKEINTRESOURCEW(c), t) + d(rcTabPageDialog, RT_DIALOG); + d(rcFontDialog, RT_DIALOG); + d(rcColorDialog, RT_DIALOG); + return 0; +} diff --git a/windows/_rc2bin/resources.hpp b/windows/_rc2bin/resources.hpp new file mode 100644 index 00000000..4ae54725 --- /dev/null +++ b/windows/_rc2bin/resources.hpp @@ -0,0 +1,37 @@ +// 30 may 2015 + +#define rcTabPageDialog 29000 +#define rcFontDialog 29001 +#define rcColorDialog 29002 + +// TODO normalize these + +#define rcFontFamilyCombobox 1000 +#define rcFontStyleCombobox 1001 +#define rcFontSizeCombobox 1002 +#define rcFontSamplePlacement 1003 + +#define rcColorSVChooser 1100 +#define rcColorHSlider 1101 +#define rcPreview 1102 +#define rcOpacitySlider 1103 +#define rcH 1104 +#define rcS 1105 +#define rcV 1106 +#define rcRDouble 1107 +#define rcRInt 1108 +#define rcGDouble 1109 +#define rcGInt 1110 +#define rcBDouble 1111 +#define rcBInt 1112 +#define rcADouble 1113 +#define rcAInt 1114 +#define rcHex 1115 +#define rcHLabel 1116 +#define rcSLabel 1117 +#define rcVLabel 1118 +#define rcRLabel 1119 +#define rcGLabel 1120 +#define rcBLabel 1121 +#define rcALabel 1122 +#define rcHexLabel 1123 diff --git a/windows/_rc2bin/resources.rc b/windows/_rc2bin/resources.rc new file mode 100644 index 00000000..989dfc91 --- /dev/null +++ b/windows/_rc2bin/resources.rc @@ -0,0 +1,96 @@ +// 30 may 2015 +#include "winapi.hpp" +#include "resources.hpp" + +// this is a UTF-8 file +#pragma code_page(65001) + +// this is the Common Controls 6 manifest +// we only define it in a shared build; static builds have to include the appropriate parts of the manifest in the output executable +// LONGTERM set up the string values here +#ifndef _UI_STATIC +ISOLATIONAWARE_MANIFEST_RESOURCE_ID RT_MANIFEST "libui.manifest" +#endif + +// this is the dialog template used by tab pages; see windows/tabpage.c for details +rcTabPageDialog DIALOGEX 0, 0, 100, 100 +STYLE DS_CONTROL | WS_CHILD | WS_VISIBLE +EXSTYLE WS_EX_CONTROLPARENT +BEGIN + // nothing +END + +// this is for our custom DirectWrite-based font dialog (see fontdialog.cpp) +// this is based on the "New Font Dialog with Syslink" in Microsoft's font.dlg +// LONGTERM look at localization +// LONGTERM make it look tighter and nicer like the real one, including the actual heights of the font family and style comboboxes +rcFontDialog DIALOGEX 13, 54, 243, 200 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_3DLOOK +CAPTION "Font" +FONT 9, "Segoe UI" +BEGIN + LTEXT "&Font:", -1, 7, 7, 98, 9 + COMBOBOX rcFontFamilyCombobox, 7, 16, 98, 76, + CBS_SIMPLE | CBS_AUTOHSCROLL | CBS_DISABLENOSCROLL | + CBS_SORT | WS_VSCROLL | WS_TABSTOP | CBS_HASSTRINGS + + LTEXT "Font st&yle:", -1, 114, 7, 74, 9 + COMBOBOX rcFontStyleCombobox, 114, 16, 74, 76, + CBS_SIMPLE | CBS_AUTOHSCROLL | CBS_DISABLENOSCROLL | + WS_VSCROLL | WS_TABSTOP | CBS_HASSTRINGS + + LTEXT "&Size:", -1, 198, 7, 36, 9 + COMBOBOX rcFontSizeCombobox, 198, 16, 36, 76, + CBS_SIMPLE | CBS_AUTOHSCROLL | CBS_DISABLENOSCROLL | + CBS_SORT | WS_VSCROLL | WS_TABSTOP | CBS_HASSTRINGS + + GROUPBOX "Sample", -1, 7, 97, 227, 70, WS_GROUP + CTEXT "AaBbYyZz", rcFontSamplePlacement, 9, 106, 224, 60, SS_NOPREFIX | NOT WS_VISIBLE + + DEFPUSHBUTTON "OK", IDOK, 141, 181, 45, 14, WS_GROUP + PUSHBUTTON "Cancel", IDCANCEL, 190, 181, 45, 14, WS_GROUP +END + +rcColorDialog DIALOGEX 13, 54, 344, 209 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_3DLOOK +CAPTION "Color" +FONT 9, "Segoe UI" +BEGIN + // this size should be big enough to get at least 256x256 on font sizes >= 8 pt + CTEXT "AaBbYyZz", rcColorSVChooser, 7, 7, 195, 195, SS_NOPREFIX | SS_BLACKRECT + + // width is the suggested slider height since this is vertical + CTEXT "AaBbYyZz", rcColorHSlider, 206, 7, 15, 195, SS_NOPREFIX | SS_BLACKRECT + + LTEXT "Preview:", -1, 230, 7, 107, 9, SS_NOPREFIX + CTEXT "AaBbYyZz", rcPreview, 230, 16, 107, 20, SS_NOPREFIX | SS_BLACKRECT + + LTEXT "Opacity:", -1, 230, 45, 107, 9, SS_NOPREFIX + CTEXT "AaBbYyZz", rcOpacitySlider, 230, 54, 107, 15, SS_NOPREFIX | SS_BLACKRECT + + LTEXT "&H:", rcHLabel, 230, 81, 8, 8 + EDITTEXT rcH, 238, 78, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE + LTEXT "&S:", rcSLabel, 230, 95, 8, 8 + EDITTEXT rcS, 238, 92, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE + LTEXT "&V:", rcVLabel, 230, 109, 8, 8 + EDITTEXT rcV, 238, 106, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE + + LTEXT "&R:", rcRLabel, 277, 81, 8, 8 + EDITTEXT rcRDouble, 285, 78, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE + EDITTEXT rcRInt, 315, 78, 20, 14, ES_LEFT | ES_AUTOHSCROLL | ES_NUMBER | WS_TABSTOP, WS_EX_CLIENTEDGE + LTEXT "&G:", rcGLabel, 277, 95, 8, 8 + EDITTEXT rcGDouble, 285, 92, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE + EDITTEXT rcGInt, 315, 92, 20, 14, ES_LEFT | ES_AUTOHSCROLL | ES_NUMBER | WS_TABSTOP, WS_EX_CLIENTEDGE + LTEXT "&B:", rcBLabel, 277, 109, 8, 8 + EDITTEXT rcBDouble, 285, 106, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE + EDITTEXT rcBInt, 315, 106, 20, 14, ES_LEFT | ES_AUTOHSCROLL | ES_NUMBER | WS_TABSTOP, WS_EX_CLIENTEDGE + LTEXT "&A:", rcALabel, 277, 123, 8, 8 + EDITTEXT rcADouble, 285, 120, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE + EDITTEXT rcAInt, 315, 120, 20, 14, ES_LEFT | ES_AUTOHSCROLL | ES_NUMBER | WS_TABSTOP, WS_EX_CLIENTEDGE + + LTEXT "He&x:", rcHexLabel, 269, 146, 16, 8 + EDITTEXT rcHex, 285, 143, 50, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE + + DEFPUSHBUTTON "OK", IDOK, 243, 188, 45, 14, WS_GROUP + PUSHBUTTON "Cancel", IDCANCEL, 292, 188, 45, 14, WS_GROUP +END diff --git a/windows/_rc2bin/winapi.hpp b/windows/_rc2bin/winapi.hpp new file mode 100644 index 00000000..4f24f607 --- /dev/null +++ b/windows/_rc2bin/winapi.hpp @@ -0,0 +1,60 @@ +// 31 may 2015 +#define UNICODE +#define _UNICODE +#define STRICT +#define STRICT_TYPED_ITEMIDS + +// see https://github.com/golang/go/issues/9916#issuecomment-74812211 +// TODO get rid of this +#define INITGUID + +// for the manifest +#ifndef _UI_STATIC +#define ISOLATION_AWARE_ENABLED 1 +#endif + +// get Windows version right; right now Windows Vista +// unless otherwise stated, all values from Microsoft's sdkddkver.h +// TODO is all of this necessary? how is NTDDI_VERSION used? +// TODO plaform update sp2 +#define WINVER 0x0600 /* from Microsoft's winnls.h */ +#define _WIN32_WINNT 0x0600 +#define _WIN32_WINDOWS 0x0600 /* from Microsoft's pdh.h */ +#define _WIN32_IE 0x0700 +#define NTDDI_VERSION 0x06000000 + +// The MinGW-w64 header has an unverified IDWriteTypography definition. +// TODO I can confirm this myself, but I don't know how long it will take for them to note my adjustments... Either way, I have to confirm this myself. +// TODO change the check from _MSC_VER to a MinGW-w64-specific check +// TODO keep track of what else is guarded by this +#ifndef _MSC_VER +#define __MINGW_USE_BROKEN_INTERFACE +#endif + +#include + +// Microsoft's resource compiler will segfault if we feed it headers it was not designed to handle +#ifndef RC_INVOKED +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#endif diff --git a/windows/_uipriv_migrate.hpp b/windows/_uipriv_migrate.hpp index 13d3670e..96a67089 100644 --- a/windows/_uipriv_migrate.hpp +++ b/windows/_uipriv_migrate.hpp @@ -12,50 +12,3 @@ extern void uninitDraw(void); extern ID2D1HwndRenderTarget *makeHWNDRenderTarget(HWND hwnd); extern uiDrawContext *newContext(ID2D1RenderTarget *); extern void freeContext(uiDrawContext *); - -// dwrite.cpp -#ifdef __cplusplus -extern IDWriteFactory *dwfactory; -#endif -extern HRESULT initDrawText(void); -extern void uninitDrawText(void); -#ifdef __cplusplus -struct fontCollection { - IDWriteFontCollection *fonts; - WCHAR userLocale[LOCALE_NAME_MAX_LENGTH]; - int userLocaleSuccess; -}; -extern fontCollection *loadFontCollection(void); -extern WCHAR *fontCollectionFamilyName(fontCollection *fc, IDWriteFontFamily *family); -extern void fontCollectionFree(fontCollection *fc); -extern WCHAR *fontCollectionCorrectString(fontCollection *fc, IDWriteLocalizedStrings *names); -#endif - -// drawtext.cpp -#ifdef __cplusplus -extern uiDrawTextFont *mkTextFont(IDWriteFont *df, BOOL addRef, WCHAR *family, BOOL copyFamily, double size); -struct dwriteAttr { - uiDrawTextWeight weight; - uiDrawTextItalic italic; - uiDrawTextStretch stretch; - DWRITE_FONT_WEIGHT dweight; - DWRITE_FONT_STYLE ditalic; - DWRITE_FONT_STRETCH dstretch; -}; -extern void attrToDWriteAttr(struct dwriteAttr *attr); -extern void dwriteAttrToAttr(struct dwriteAttr *attr); -#endif - -// fontdialog.cpp -#ifdef __cplusplus -struct fontDialogParams { - IDWriteFont *font; - double size; - WCHAR *familyName; - WCHAR *styleName; -}; -extern BOOL showFontDialog(HWND parent, struct fontDialogParams *params); -extern void loadInitialFontDialogParams(struct fontDialogParams *params); -extern void destroyFontDialogParams(struct fontDialogParams *params); -extern WCHAR *fontDialogParamsToString(struct fontDialogParams *params); -#endif diff --git a/windows/alloc.cpp b/windows/alloc.cpp index ed1d4ee1..321cca03 100644 --- a/windows/alloc.cpp +++ b/windows/alloc.cpp @@ -13,25 +13,21 @@ void initAlloc(void) void uninitAlloc(void) { - BOOL hasEntry; + std::ostringstream oss; + std::string ossstr; // keep alive, just to be safe - hasEntry = FALSE; - for (const auto &alloc : heap) { - if (!hasEntry) { - fprintf(stderr, "[libui] leaked allocations:\n"); - hasEntry = TRUE; - } - fprintf(stderr, "[libui] %p %s\n", - alloc.first, - types[alloc.second]); - } - if (hasEntry) - complain("either you left something around or there's a bug in libui"); + if (heap.size() == 0) + return; + for (const auto &alloc : heap) + // note the void * cast; otherwise it'll be treated as a string + oss << (void *) (alloc.first) << " " << types[alloc.second] << "\n"; + ossstr = oss.str(); + uiprivUserBug("Some data was leaked; either you left a uiControl lying around or there's a bug in libui itself. Leaked data:\n%s", ossstr.c_str()); } #define rawBytes(pa) (&((*pa)[0])) -void *uiAlloc(size_t size, const char *type) +void *uiprivAlloc(size_t size, const char *type) { byteArray *out; @@ -41,26 +37,27 @@ void *uiAlloc(size_t size, const char *type) return rawBytes(out); } -void *uiRealloc(void *_p, size_t size, const char *type) +void *uiprivRealloc(void *_p, size_t size, const char *type) { uint8_t *p = (uint8_t *) _p; byteArray *arr; if (p == NULL) - return uiAlloc(size, type); + return uiprivAlloc(size, type); arr = heap[p]; + // TODO does this fill in? arr->resize(size, 0); heap.erase(p); heap[rawBytes(arr)] = arr; return rawBytes(arr); } -void uiFree(void *_p) +void uiprivFree(void *_p) { uint8_t *p = (uint8_t *) _p; if (p == NULL) - complain("attempt to uiFree(NULL); there's a bug somewhere"); + uiprivImplBug("attempt to uiprivFree(NULL)"); types.erase(heap[p]); delete heap[p]; heap.erase(p); diff --git a/windows/area.cpp b/windows/area.cpp index 2d301855..0042fccc 100644 --- a/windows/area.cpp +++ b/windows/area.cpp @@ -2,6 +2,8 @@ #include "uipriv_windows.hpp" #include "area.hpp" +// TODO handle WM_DESTROY/WM_NCDESTROY +// TODO same for other Direct2D stuff static LRESULT CALLBACK areaWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { uiArea *a; @@ -51,7 +53,7 @@ static LRESULT CALLBACK areaWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM uiWindowsControlAllDefaults(uiArea) -static void uiAreaMinimumSize(uiWindowsControl *c, intmax_t *width, intmax_t *height) +static void uiAreaMinimumSize(uiWindowsControl *c, int *width, int *height) { // TODO *width = 0; @@ -69,7 +71,8 @@ ATOM registerAreaClass(HICON hDefaultIcon, HCURSOR hDefaultCursor) wc.hIcon = hDefaultIcon; wc.hCursor = hDefaultCursor; wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1); - // TODO specify CS_HREDRAW/CS_VREDRAW in addition to or instead of calling InvalidateRect(NULL) in WM_WINDOWPOSCHANGED above, or not at all? + // this is just to be safe; see the InvalidateRect() call in the WM_WINDOWPOSCHANGED handler for more details + wc.style = CS_HREDRAW | CS_VREDRAW; return RegisterClassW(&wc); } @@ -79,7 +82,7 @@ void unregisterArea(void) logLastError(L"error unregistering uiArea window class"); } -void uiAreaSetSize(uiArea *a, intmax_t width, intmax_t height) +void uiAreaSetSize(uiArea *a, int width, int height) { a->scrollWidth = width; a->scrollHeight = height; @@ -89,8 +92,7 @@ void uiAreaSetSize(uiArea *a, intmax_t width, intmax_t height) void uiAreaQueueRedrawAll(uiArea *a) { // don't erase the background; we do that ourselves in doPaint() - if (InvalidateRect(a->hwnd, NULL, FALSE) == 0) - logLastError(L"error queueing uiArea redraw"); + invalidateRect(a->hwnd, NULL, FALSE); } void uiAreaScrollTo(uiArea *a, double x, double y, double width, double height) @@ -98,6 +100,66 @@ void uiAreaScrollTo(uiArea *a, double x, double y, double width, double height) // TODO } +void uiAreaBeginUserWindowMove(uiArea *a) +{ + HWND toplevel; + + // TODO restrict execution + ReleaseCapture(); // TODO use properly and reset internal data structures + toplevel = parentToplevel(a->hwnd); + if (toplevel == NULL) { + // TODO + return; + } + // see http://stackoverflow.com/questions/40249940/how-do-i-initiate-a-user-mouse-driven-move-or-resize-for-custom-window-borders-o#40250654 + SendMessageW(toplevel, WM_SYSCOMMAND, + SC_MOVE | 2, 0); +} + +void uiAreaBeginUserWindowResize(uiArea *a, uiWindowResizeEdge edge) +{ + HWND toplevel; + WPARAM wParam; + + // TODO restrict execution + ReleaseCapture(); // TODO use properly and reset internal data structures + toplevel = parentToplevel(a->hwnd); + if (toplevel == NULL) { + // TODO + return; + } + // see http://stackoverflow.com/questions/40249940/how-do-i-initiate-a-user-mouse-driven-move-or-resize-for-custom-window-borders-o#40250654 + wParam = SC_SIZE; + switch (edge) { + case uiWindowResizeEdgeLeft: + wParam |= 1; + break; + case uiWindowResizeEdgeTop: + wParam |= 3; + break; + case uiWindowResizeEdgeRight: + wParam |= 2; + break; + case uiWindowResizeEdgeBottom: + wParam |= 6; + break; + case uiWindowResizeEdgeTopLeft: + wParam |= 4; + break; + case uiWindowResizeEdgeTopRight: + wParam |= 5; + break; + case uiWindowResizeEdgeBottomLeft: + wParam |= 7; + break; + case uiWindowResizeEdgeBottomRight: + wParam |= 8; + break; + } + SendMessageW(toplevel, WM_SYSCOMMAND, + wParam, 0); +} + uiArea *uiNewArea(uiAreaHandler *ah) { uiArea *a; @@ -106,7 +168,7 @@ uiArea *uiNewArea(uiAreaHandler *ah) a->ah = ah; a->scrolling = FALSE; - clickCounterReset(&(a->cc)); + uiprivClickCounterReset(&(a->cc)); // a->hwnd is assigned in areaWndProc() uiWindowsEnsureCreateControlHWND(0, @@ -118,7 +180,7 @@ uiArea *uiNewArea(uiAreaHandler *ah) return a; } -uiArea *uiNewScrollingArea(uiAreaHandler *ah, intmax_t width, intmax_t height) +uiArea *uiNewScrollingArea(uiAreaHandler *ah, int width, int height) { uiArea *a; @@ -128,7 +190,7 @@ uiArea *uiNewScrollingArea(uiAreaHandler *ah, intmax_t width, intmax_t height) a->scrolling = TRUE; a->scrollWidth = width; a->scrollHeight = height; - clickCounterReset(&(a->cc)); + uiprivClickCounterReset(&(a->cc)); // a->hwnd is assigned in areaWndProc() uiWindowsEnsureCreateControlHWND(0, diff --git a/windows/area.hpp b/windows/area.hpp index 6b82532d..dfc2bc58 100644 --- a/windows/area.hpp +++ b/windows/area.hpp @@ -11,14 +11,14 @@ struct uiArea { uiAreaHandler *ah; BOOL scrolling; - intmax_t scrollWidth; - intmax_t scrollHeight; - intmax_t hscrollpos; - intmax_t vscrollpos; + int scrollWidth; + int scrollHeight; + int hscrollpos; + int vscrollpos; int hwheelCarry; int vwheelCarry; - clickCounter cc; + uiprivClickCounter cc; BOOL capturing; BOOL inside; diff --git a/windows/areadraw.cpp b/windows/areadraw.cpp index 90a8fe28..7b3dc696 100644 --- a/windows/areadraw.cpp +++ b/windows/areadraw.cpp @@ -44,6 +44,7 @@ static HRESULT doPaint(uiArea *a, ID2D1RenderTarget *rt, RECT *clip) bgcolor.r = ((float) GetRValue(bgcolorref)) / 255.0; // due to utter apathy on Microsoft's part, GetGValue() does not work with MSVC's Run-Time Error Checks // it has not worked since 2008 and they have *never* fixed it + // TODO now that -RTCc has just been deprecated entirely, should we switch back? bgcolor.g = ((float) ((BYTE) ((bgcolorref & 0xFF00) >> 8))) / 255.0; bgcolor.b = ((float) GetBValue(bgcolorref)) / 255.0; bgcolor.a = 1.0; @@ -71,7 +72,7 @@ static void onWM_PAINT(uiArea *a) clip.right = 0; clip.bottom = 0; } - hr = doPaint(a, (ID2D1RenderTarget *) (a->rt), &clip); + hr = doPaint(a, a->rt, &clip); switch (hr) { case S_OK: if (ValidateRect(a->hwnd, NULL) == 0) @@ -90,12 +91,18 @@ static void onWM_PAINT(uiArea *a) } } -static void onWM_PRINTCLIENT(uiArea *a) +static void onWM_PRINTCLIENT(uiArea *a, HDC dc) { + ID2D1DCRenderTarget *rt; RECT client; + HRESULT hr; uiWindowsEnsureGetClientRect(a->hwnd, &client); -//TODO doPaint(a, (HDC) wParam, &client); + rt = makeHDCRenderTarget(dc, &client); + hr = doPaint(a, rt, &client); + if (hr != S_OK) + logHRESULT(L"error printing uiArea client area", hr); + rt->Release(); } BOOL areaDoDraw(uiArea *a, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *lResult) @@ -106,7 +113,7 @@ BOOL areaDoDraw(uiArea *a, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *lRe *lResult = 0; return TRUE; case WM_PRINTCLIENT: - onWM_PRINTCLIENT(a); + onWM_PRINTCLIENT(a, (HDC) wParam); *lResult = 0; return TRUE; } @@ -126,6 +133,5 @@ void areaDrawOnResize(uiArea *a, RECT *newClient) // according to Rick Brewster, we must always redraw the entire client area after calling ID2D1RenderTarget::Resize() (see http://stackoverflow.com/a/33222983/3408572) // we used to have a uiAreaHandler.RedrawOnResize() method to decide this; now you know why we don't anymore - if (InvalidateRect(a->hwnd, NULL, TRUE) == 0) - logLastError(L"error redrawing area on resize"); + invalidateRect(a->hwnd, NULL, TRUE); } diff --git a/windows/areaevents.cpp b/windows/areaevents.cpp index 386b49f8..c7014ecb 100644 --- a/windows/areaevents.cpp +++ b/windows/areaevents.cpp @@ -2,6 +2,8 @@ #include "uipriv_windows.hpp" #include "area.hpp" +// TODO https://github.com/Microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/multimedia/DirectWrite/PadWrite/TextEditor.cpp notes on explicit RTL handling under MirrorXCoordinate(); also in areadraw.cpp too? + static uiModifiers getModifiers(void) { uiModifiers m = 0; @@ -73,10 +75,10 @@ static void capture(uiArea *a, BOOL capturing) logLastError(L"error releasing capture on drag"); } -static void areaMouseEvent(uiArea *a, uintmax_t down, uintmax_t up, WPARAM wParam, LPARAM lParam) +static void areaMouseEvent(uiArea *a, int down, int up, WPARAM wParam, LPARAM lParam) { uiAreaMouseEvent me; - uintmax_t button; + int button; POINT clientpt; RECT client; BOOL inClient; @@ -90,11 +92,11 @@ static void areaMouseEvent(uiArea *a, uintmax_t down, uintmax_t up, WPARAM wPar if (inClient && !a->inside) { a->inside = TRUE; (*(a->ah->MouseCrossed))(a->ah, a, 0); - clickCounterReset(&(a->cc)); + uiprivClickCounterReset(&(a->cc)); } else if (!inClient && a->inside) { a->inside = FALSE; (*(a->ah->MouseCrossed))(a->ah, a, 1); - clickCounterReset(&(a->cc)); + uiprivClickCounterReset(&(a->cc)); } } @@ -117,7 +119,8 @@ static void areaMouseEvent(uiArea *a, uintmax_t down, uintmax_t up, WPARAM wPar if (me.Down != 0) // GetMessageTime() returns LONG and GetDoubleClckTime() returns UINT, which are int32 and uint32, respectively, but we don't need to worry about the signedness because for the same bit widths and two's complement arithmetic, s1-s2 == u1-u2 if bits(s1)==bits(s2) and bits(u1)==bits(u2) (and Windows requires two's complement: http://blogs.msdn.com/b/oldnewthing/archive/2005/05/27/422551.aspx) // signedness isn't much of an issue for these calls anyway because http://stackoverflow.com/questions/24022225/what-are-the-sign-extension-rules-for-calling-windows-api-functions-stdcall-t and that we're only using unsigned values (think back to how you (didn't) handle signedness in assembly language) AND because of the above AND because the statistics below (time interval and width/height) really don't make sense if negative - me.Count = clickCounterClick(&(a->cc), me.Down, + // GetSystemMetrics() returns int, which is int32 + me.Count = uiprivClickCounterClick(&(a->cc), me.Down, me.X, me.Y, GetMessageTime(), GetDoubleClickTime(), GetSystemMetrics(SM_CXDOUBLECLK) / 2, @@ -161,7 +164,7 @@ static void onMouseEntered(uiArea *a) track(a, TRUE); (*(a->ah->MouseCrossed))(a->ah, a, 0); // TODO figure out why we did this to begin with; either we do it on both GTK+ and Windows or not at all - clickCounterReset(&(a->cc)); + uiprivClickCounterReset(&(a->cc)); } // TODO genericize it so that it can be called above @@ -171,7 +174,7 @@ static void onMouseLeft(uiArea *a) a->inside = FALSE; (*(a->ah->MouseCrossed))(a->ah, a, 1); // TODO figure out why we did this to begin with; either we do it on both GTK+ and Windows or not at all - clickCounterReset(&(a->cc)); + uiprivClickCounterReset(&(a->cc)); } // we use VK_SNAPSHOT as a sentinel because libui will never support the print screen key; that key belongs to the user @@ -297,7 +300,7 @@ static int areaKeyEvent(uiArea *a, int up, WPARAM wParam, LPARAM lParam) } // and finally everything else - if (fromScancode((lParam >> 16) & 0xFF, &ke)) + if (uiprivFromScancode((lParam >> 16) & 0xFF, &ke)) goto keyFound; // not a supported key, assume unhandled @@ -322,7 +325,7 @@ BOOL areaDoEvents(uiArea *a, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *l switch (uMsg) { case WM_ACTIVATE: // don't keep the double-click timer running if the user switched programs in between clicks - clickCounterReset(&(a->cc)); + uiprivClickCounterReset(&(a->cc)); *lResult = 0; return TRUE; case WM_MOUSEMOVE: diff --git a/windows/areascroll.cpp b/windows/areascroll.cpp index 782755a9..f18d0ad8 100644 --- a/windows/areascroll.cpp +++ b/windows/areascroll.cpp @@ -12,14 +12,14 @@ // - error if these are called without scrollbars? struct scrollParams { - intmax_t *pos; - intmax_t pagesize; - intmax_t length; + int *pos; + int pagesize; + int length; int *wheelCarry; UINT wheelSPIAction; }; -static void scrollto(uiArea *a, int which, struct scrollParams *p, intmax_t pos) +static void scrollto(uiArea *a, int which, struct scrollParams *p, int pos) { SCROLLINFO si; @@ -33,8 +33,7 @@ static void scrollto(uiArea *a, int which, struct scrollParams *p, intmax_t pos) // Direct2D doesn't have a method for scrolling the existing contents of a render target. // We'll have to just invalidate everything and hope for the best. - if (InvalidateRect(a->hwnd, NULL, FALSE) == 0) - logLastError(L"error invalidating uiArea after scrolling"); + invalidateRect(a->hwnd, NULL, FALSE); *(p->pos) = pos; @@ -49,14 +48,14 @@ static void scrollto(uiArea *a, int which, struct scrollParams *p, intmax_t pos) SetScrollInfo(a->hwnd, which, &si, TRUE); } -static void scrollby(uiArea *a, int which, struct scrollParams *p, intmax_t delta) +static void scrollby(uiArea *a, int which, struct scrollParams *p, int delta) { scrollto(a, which, p, *(p->pos) + delta); } static void scroll(uiArea *a, int which, struct scrollParams *p, WPARAM wParam, LPARAM lParam) { - intmax_t pos; + int pos; SCROLLINFO si; pos = *(p->pos); @@ -135,7 +134,7 @@ static void hscrollParams(uiArea *a, struct scrollParams *p) p->wheelSPIAction = SPI_GETWHEELSCROLLCHARS; } -static void hscrollto(uiArea *a, intmax_t pos) +static void hscrollto(uiArea *a, int pos) { struct scrollParams p; @@ -143,7 +142,7 @@ static void hscrollto(uiArea *a, intmax_t pos) scrollto(a, SB_HORZ, &p, pos); } -static void hscrollby(uiArea *a, intmax_t delta) +static void hscrollby(uiArea *a, int delta) { struct scrollParams p; @@ -180,7 +179,7 @@ static void vscrollParams(uiArea *a, struct scrollParams *p) p->wheelSPIAction = SPI_GETWHEELSCROLLLINES; } -static void vscrollto(uiArea *a, intmax_t pos) +static void vscrollto(uiArea *a, int pos) { struct scrollParams p; @@ -188,7 +187,7 @@ static void vscrollto(uiArea *a, intmax_t pos) scrollto(a, SB_VERT, &p, pos); } -static void vscrollby(uiArea *a, intmax_t delta) +static void vscrollby(uiArea *a, int delta) { struct scrollParams p; diff --git a/windows/areautil.cpp b/windows/areautil.cpp index 8d2d7fc8..9dc72fbb 100644 --- a/windows/areautil.cpp +++ b/windows/areautil.cpp @@ -11,7 +11,7 @@ void loadAreaSize(uiArea *a, ID2D1RenderTarget *rt, double *width, double *heigh if (!a->scrolling) { if (rt == NULL) rt = a->rt; - size = rt->GetSize(); + size = realGetSize(rt); *width = size.width; *height = size.height; } diff --git a/windows/attrstr.cpp b/windows/attrstr.cpp new file mode 100644 index 00000000..740ac43e --- /dev/null +++ b/windows/attrstr.cpp @@ -0,0 +1,427 @@ +// 12 february 2017 +#include "uipriv_windows.hpp" +#include "attrstr.hpp" + +// TODO this whole file needs cleanup + +// yep, even when it supports C++11, it doesn't support C++11 +// we require MSVC 2013; this was added in MSVC 2015 (https://msdn.microsoft.com/en-us/library/wfa0edys.aspx) +#ifdef _MSC_VER +#if _MSC_VER < 1900 +#define noexcept +#endif +#endif + +// we need to collect all the background parameters and add them all at once +// TODO consider having background parameters in the drawing effects +// TODO contextual alternates override ligatures? +// TODO rename this struct to something that isn't exclusively foreach-ing? +struct foreachParams { + const uint16_t *s; + size_t len; + IDWriteTextLayout *layout; + std::vector *backgroundParams; +}; + +static std::hash doubleHash; + +// we need to combine color and underline style into one unit for IDWriteLayout::SetDrawingEffect() +// we also want to combine identical effects, which DirectWrite doesn't seem to provide a way to do +// we can at least try to goad it into doing so if we can deduplicate effects once they're all computed +// so what we do is use this class to store in-progress effects, much like uiprivCombinedFontAttr on the OS X code +// we then deduplicate them later while converting them into a form suitable for drawing with; see applyEffectsAttributes() below +class combinedEffectsAttr : public IUnknown { + ULONG refcount; + uiAttribute *colorAttr; + uiAttribute *underlineAttr; + uiAttribute *underlineColorAttr; + + void setAttribute(uiAttribute *a) + { + if (a == NULL) + return; + switch (uiAttributeGetType(a)) { + case uiAttributeTypeColor: + if (this->colorAttr != NULL) + uiprivAttributeRelease(this->colorAttr); + this->colorAttr = uiprivAttributeRetain(a); + break; + case uiAttributeTypeUnderline: + if (this->underlineAttr != NULL) + uiprivAttributeRelease(this->underlineAttr); + this->underlineAttr = uiprivAttributeRetain(a); + break; + case uiAttributeTypeUnderlineColor: + if (this->underlineAttr != NULL) + uiprivAttributeRelease(this->underlineAttr); + this->underlineColorAttr = uiprivAttributeRetain(a); + break; + } + } + + // this is needed by applyEffectsAttributes() below + // TODO doesn't uiprivAttributeEqual() already do this; if it doesn't, make it so; if (or when) it does, fix all platforms to avoid this extra check + static bool attrEqual(uiAttribute *a, uiAttribute *b) + { + if (a == NULL && b == NULL) + return true; + if (a == NULL || b == NULL) + return false; + return uiprivAttributeEqual(a, b); + } +public: + combinedEffectsAttr(uiAttribute *a) + { + this->refcount = 1; + this->colorAttr = NULL; + this->underlineAttr = NULL; + this->underlineColorAttr = NULL; + this->setAttribute(a); + } + + ~combinedEffectsAttr() + { + if (this->colorAttr != NULL) + uiprivAttributeRelease(this->colorAttr); + if (this->underlineAttr != NULL) + uiprivAttributeRelease(this->underlineAttr); + if (this->underlineColorAttr != NULL) + uiprivAttributeRelease(this->underlineColorAttr); + } + + // IUnknown + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) + { + if (ppvObject == NULL) + return E_POINTER; + if (riid == IID_IUnknown) { + this->AddRef(); + *ppvObject = this; + return S_OK; + } + *ppvObject = NULL; + return E_NOINTERFACE; + } + + virtual ULONG STDMETHODCALLTYPE AddRef(void) + { + this->refcount++; + return this->refcount; + } + + virtual ULONG STDMETHODCALLTYPE Release(void) + { + this->refcount--; + if (this->refcount == 0) { + delete this; + return 0; + } + return this->refcount; + } + + combinedEffectsAttr *cloneWith(uiAttribute *a) + { + combinedEffectsAttr *b; + + b = new combinedEffectsAttr(this->colorAttr); + b->setAttribute(this->underlineAttr); + b->setAttribute(this->underlineColorAttr); + b->setAttribute(a); + return b; + } + + // and these are also needed by applyEffectsAttributes() below + size_t hash(void) const noexcept + { + size_t ret = 0; + double r, g, b, a; + uiUnderlineColor colorType; + + if (this->colorAttr != NULL) { + uiAttributeColor(this->colorAttr, &r, &g, &b, &a); + ret ^= doubleHash(r); + ret ^= doubleHash(g); + ret ^= doubleHash(b); + ret ^= doubleHash(a); + } + if (this->underlineAttr != NULL) + ret ^= (size_t) uiAttributeUnderline(this->underlineAttr); + if (this->underlineColorAttr != NULL) { + uiAttributeUnderlineColor(this->underlineColorAttr, &colorType, &r, &g, &b, &a); + ret ^= (size_t) colorType; + ret ^= doubleHash(r); + ret ^= doubleHash(g); + ret ^= doubleHash(b); + ret ^= doubleHash(a); + } + return ret; + } + + bool equals(const combinedEffectsAttr *b) const + { + if (b == NULL) + return false; + return combinedEffectsAttr::attrEqual(this->colorAttr, b->colorAttr) && + combinedEffectsAttr::attrEqual(this->underlineAttr, b->underlineAttr) && + combinedEffectsAttr::attrEqual(this->underlineColorAttr, b->underlineColorAttr); + } + + drawingEffectsAttr *toDrawingEffectsAttr(void) + { + drawingEffectsAttr *dea; + double r, g, b, a; + uiUnderlineColor colorType; + + dea = new drawingEffectsAttr; + if (this->colorAttr != NULL) { + uiAttributeColor(this->colorAttr, &r, &g, &b, &a); + dea->setColor(r, g, b, a); + } + if (this->underlineAttr != NULL) + dea->setUnderline(uiAttributeUnderline(this->underlineAttr)); + if (this->underlineColorAttr != NULL) { + uiAttributeUnderlineColor(this->underlineColorAttr, &colorType, &r, &g, &b, &a); + // TODO see if Microsoft has any standard colors for these + switch (colorType) { + case uiUnderlineColorSpelling: + // TODO consider using the GtkTextView style property error-underline-color here if Microsoft has no preference + r = 1.0; + g = 0.0; + b = 0.0; + a = 1.0; + break; + case uiUnderlineColorGrammar: + r = 0.0; + g = 1.0; + b = 0.0; + a = 1.0; + break; + case uiUnderlineColorAuxiliary: + r = 0.0; + g = 0.0; + b = 1.0; + a = 1.0; + break; + } + dea->setUnderlineColor(r, g, b, a); + } + return dea; + } +}; + +// also needed by applyEffectsAttributes() below +// TODO provide all the fields of std::hash and std::equal_to? +class applyEffectsHash { +public: + typedef combinedEffectsAttr *ceaptr; + size_t operator()(applyEffectsHash::ceaptr const &cea) const noexcept + { + return cea->hash(); + } +}; + +class applyEffectsEqualTo { +public: + typedef combinedEffectsAttr *ceaptr; + bool operator()(const applyEffectsEqualTo::ceaptr &a, const applyEffectsEqualTo::ceaptr &b) const + { + return a->equals(b); + } +}; + +static HRESULT addEffectAttributeToRange(struct foreachParams *p, size_t start, size_t end, uiAttribute *attr) +{ + IUnknown *u; + combinedEffectsAttr *cea; + DWRITE_TEXT_RANGE range; + size_t diff; + HRESULT hr; + + while (start < end) { + hr = p->layout->GetDrawingEffect(start, &u, &range); + if (hr != S_OK) + return hr; + cea = (combinedEffectsAttr *) u; + if (cea == NULL) + cea = new combinedEffectsAttr(attr); + else + cea = cea->cloneWith(attr); + // clamp range within [start, end) + if (range.startPosition < start) { + diff = start - range.startPosition; + range.startPosition = start; + range.length -= diff; + } + if ((range.startPosition + range.length) > end) + range.length = end - range.startPosition; + hr = p->layout->SetDrawingEffect(cea, range); + // SetDrawingEffect will AddRef(), so Release() our copy + // (and we're abandoning early if that failed, so this will make sure things are cleaned up in that case) + cea->Release(); + if (hr != S_OK) + return hr; + start += range.length; + } + return S_OK; +} + +static void addBackgroundParams(struct foreachParams *p, size_t start, size_t end, const uiAttribute *attr) +{ + struct drawTextBackgroundParams *params; + + params = uiprivNew(struct drawTextBackgroundParams); + params->start = start; + params->end = end; + uiAttributeColor(attr, &(params->r), &(params->g), &(params->b), &(params->a)); + p->backgroundParams->push_back(params); +} + +static uiForEach processAttribute(const uiAttributedString *s, const uiAttribute *attr, size_t start, size_t end, void *data) +{ + struct foreachParams *p = (struct foreachParams *) data; + DWRITE_TEXT_RANGE range; + WCHAR *wfamily; + BOOL hasUnderline; + IDWriteTypography *dt; + HRESULT hr; + + start = uiprivAttributedStringUTF8ToUTF16(s, start); + end = uiprivAttributedStringUTF8ToUTF16(s, end); + range.startPosition = start; + range.length = end - start; + switch (uiAttributeGetType(attr)) { + case uiAttributeTypeFamily: + wfamily = toUTF16(uiAttributeFamily(attr)); + hr = p->layout->SetFontFamilyName(wfamily, range); + if (hr != S_OK) + logHRESULT(L"error applying family name attribute", hr); + uiprivFree(wfamily); + break; + case uiAttributeTypeSize: + hr = p->layout->SetFontSize( +// TODO unify with fontmatch.cpp and/or attrstr.hpp +#define pointSizeToDWriteSize(size) (size * (96.0 / 72.0)) + pointSizeToDWriteSize(uiAttributeSize(attr)), + range); + if (hr != S_OK) + logHRESULT(L"error applying size attribute", hr); + break; + case uiAttributeTypeWeight: + hr = p->layout->SetFontWeight( + uiprivWeightToDWriteWeight(uiAttributeWeight(attr)), + range); + if (hr != S_OK) + logHRESULT(L"error applying weight attribute", hr); + break; + case uiAttributeTypeItalic: + hr = p->layout->SetFontStyle( + uiprivItalicToDWriteStyle(uiAttributeItalic(attr)), + range); + if (hr != S_OK) + logHRESULT(L"error applying italic attribute", hr); + break; + case uiAttributeTypeStretch: + hr = p->layout->SetFontStretch( + uiprivStretchToDWriteStretch(uiAttributeStretch(attr)), + range); + if (hr != S_OK) + logHRESULT(L"error applying stretch attribute", hr); + break; + case uiAttributeTypeUnderline: + // mark that we have an underline; otherwise, DirectWrite will never call our custom renderer's DrawUnderline() method + hasUnderline = FALSE; + if (uiAttributeUnderline(attr) != uiUnderlineNone) + hasUnderline = TRUE; + hr = p->layout->SetUnderline(hasUnderline, range); + if (hr != S_OK) + logHRESULT(L"error applying underline attribute", hr); + // and fall through to set the underline style through the drawing effect + case uiAttributeTypeColor: + case uiAttributeTypeUnderlineColor: + // TODO const-correct this properly + hr = addEffectAttributeToRange(p, start, end, (uiAttribute *) attr); + if (hr != S_OK) + logHRESULT(L"error applying effect (color, underline, or underline color) attribute", hr); + break; + case uiAttributeTypeBackground: + addBackgroundParams(p, start, end, attr); + break; + case uiAttributeTypeFeatures: + // only generate an attribute if not NULL + // TODO do we still need to do this or not... + if (uiAttributeFeatures(attr) == NULL) + break; + dt = uiprivOpenTypeFeaturesToIDWriteTypography(uiAttributeFeatures(attr)); + hr = p->layout->SetTypography(dt, range); + if (hr != S_OK) + logHRESULT(L"error applying features attribute", hr); + dt->Release(); + break; + } + return uiForEachContinue; +} + +static HRESULT applyEffectsAttributes(struct foreachParams *p) +{ + IUnknown *u; + combinedEffectsAttr *cea; + drawingEffectsAttr *dea; + DWRITE_TEXT_RANGE range; + // here's the magic: this std::unordered_map will deduplicate all of our combinedEffectsAttrs, mapping all identical ones to a single drawingEffectsAttr + // because drawingEffectsAttr is the *actual* drawing effect we want for rendering, we also replace the combinedEffectsAttrs with them in the IDWriteTextLayout at the same time + // note the use of our custom hash and equal_to implementations + std::unordered_map effects; + HRESULT hr; + + // go through, replacing every combinedEffectsAttr with the proper drawingEffectsAttr + range.startPosition = 0; + // and in case this while loop never runs, make hr valid to start with + hr = S_OK; + while (range.startPosition < p->len) { + hr = p->layout->GetDrawingEffect(range.startPosition, &u, &range); + if (hr != S_OK) + // note that we are breaking instead of returning; this allows us to clean up on failure + break; + cea = (combinedEffectsAttr *) u; + if (cea != NULL) { + auto diter = effects.find(cea); + if (diter != effects.end()) + dea = diter->second; + else { + dea = cea->toDrawingEffectsAttr(); + effects.insert({cea, dea}); + } + hr = p->layout->SetDrawingEffect(dea, range); + // don't release dea; we need the reference that's inside the map + // (we don't take extra references on lookup, so this will be fine) + if (hr != S_OK) + break; + } + range.startPosition += range.length; + } + + // and clean up, finally destroying the combinedEffectAttrs too + // we do this in the case of failure as well, to make sure everything is properly cleaned up + for (auto iter = effects.begin(); iter != effects.end(); iter++) { + iter->first->Release(); + iter->second->Release(); + } + return hr; +} + +void uiprivAttributedStringApplyAttributesToDWriteTextLayout(uiDrawTextLayoutParams *p, IDWriteTextLayout *layout, std::vector **backgroundParams) +{ + struct foreachParams fep; + HRESULT hr; + + fep.s = uiprivAttributedStringUTF16String(p->String); + fep.len = uiprivAttributedStringUTF16Len(p->String); + fep.layout = layout; + fep.backgroundParams = new std::vector; + uiAttributedStringForEachAttribute(p->String, processAttribute, &fep); + hr = applyEffectsAttributes(&fep); + if (hr != S_OK) + logHRESULT(L"error applying effects attributes", hr); + *backgroundParams = fep.backgroundParams; +} diff --git a/windows/attrstr.hpp b/windows/attrstr.hpp new file mode 100644 index 00000000..bd522ca1 --- /dev/null +++ b/windows/attrstr.hpp @@ -0,0 +1,85 @@ +// 11 march 2018 +#include "../common/attrstr.h" + +// dwrite.cpp +extern IDWriteFactory *dwfactory; +extern HRESULT uiprivInitDrawText(void); +extern void uiprivUninitDrawText(void); +struct fontCollection { + IDWriteFontCollection *fonts; + WCHAR userLocale[LOCALE_NAME_MAX_LENGTH]; + int userLocaleSuccess; +}; +extern fontCollection *uiprivLoadFontCollection(void); +extern WCHAR *uiprivFontCollectionFamilyName(fontCollection *fc, IDWriteFontFamily *family); +extern void uiprivFontCollectionFree(fontCollection *fc); +extern WCHAR *uiprivFontCollectionCorrectString(fontCollection *fc, IDWriteLocalizedStrings *names); + +// opentype.cpp +extern IDWriteTypography *uiprivOpenTypeFeaturesToIDWriteTypography(const uiOpenTypeFeatures *otf); + +// fontmatch.cpp +extern DWRITE_FONT_WEIGHT uiprivWeightToDWriteWeight(uiTextWeight w); +extern DWRITE_FONT_STYLE uiprivItalicToDWriteStyle(uiTextItalic i); +extern DWRITE_FONT_STRETCH uiprivStretchToDWriteStretch(uiTextStretch s); +extern void uiprivFontDescriptorFromIDWriteFont(IDWriteFont *font, uiFontDescriptor *uidesc); + +// attrstr.cpp +// TODO +struct drawTextBackgroundParams; +extern void uiprivAttributedStringApplyAttributesToDWriteTextLayout(uiDrawTextLayoutParams *p, IDWriteTextLayout *layout, std::vector **backgroundFuncs); + +// drawtext.cpp +class drawingEffectsAttr : public IUnknown { + ULONG refcount; + + bool hasColor; + double r; + double g; + double b; + double a; + + bool hasUnderline; + uiUnderline u; + + bool hasUnderlineColor; + double ur; + double ug; + double ub; + double ua; +public: + drawingEffectsAttr(void); + + // IUnknown + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject); + virtual ULONG STDMETHODCALLTYPE AddRef(void); + virtual ULONG STDMETHODCALLTYPE Release(void); + + void setColor(double r, double g, double b, double a); + void setUnderline(uiUnderline u); + void setUnderlineColor(double r, double g, double b, double a); + HRESULT mkColorBrush(ID2D1RenderTarget *rt, ID2D1SolidColorBrush **b); + HRESULT underline(uiUnderline *u); + HRESULT mkUnderlineBrush(ID2D1RenderTarget *rt, ID2D1SolidColorBrush **b); +}; +// TODO figure out where this type should *really* go in all the headers... +struct drawTextBackgroundParams { + size_t start; + size_t end; + double r; + double g; + double b; + double a; +}; + +// fontdialog.cpp +struct fontDialogParams { + IDWriteFont *font; + double size; + WCHAR *familyName; + WCHAR *styleName; +}; +extern BOOL uiprivShowFontDialog(HWND parent, struct fontDialogParams *params); +extern void uiprivLoadInitialFontDialogParams(struct fontDialogParams *params); +extern void uiprivDestroyFontDialogParams(struct fontDialogParams *params); +extern WCHAR *uiprivFontDialogParamsToString(struct fontDialogParams *params); diff --git a/windows/box.cpp b/windows/box.cpp index 01413ebe..9567954b 100644 --- a/windows/box.cpp +++ b/windows/box.cpp @@ -4,8 +4,8 @@ struct boxChild { uiControl *c; int stretchy; - intmax_t width; - intmax_t height; + int width; + int height; }; struct uiBox { @@ -31,12 +31,13 @@ static void boxPadding(uiBox *b, int *xpadding, int *ypadding) static void boxRelayout(uiBox *b) { RECT r; - intmax_t x, y, width, height; + int x, y, width, height; int xpadding, ypadding; - uintmax_t nStretchy; - intmax_t stretchywid, stretchyht; - uintmax_t i; - intmax_t minimumWidth, minimumHeight; + int nStretchy; + int stretchywid, stretchyht; + int i; + int minimumWidth, minimumHeight; + int nVisible; uiWindowsSizing *d; if (b->controls->size() == 0) @@ -51,21 +52,16 @@ static void boxRelayout(uiBox *b) // -1) get this Box's padding boxPadding(b, &xpadding, &ypadding); - // 0) inset the available rect by the needed padding - // TODO this is incorrect if any controls are hidden - if (b->vertical) - height -= (b->controls->size() - 1) * ypadding; - else - width -= (b->controls->size() - 1) * xpadding; - // 1) get width and height of non-stretchy controls // this will tell us how much space will be left for stretchy controls stretchywid = width; stretchyht = height; nStretchy = 0; + nVisible = 0; for (struct boxChild &bc : *(b->controls)) { if (!uiControlVisible(bc.c)) continue; + nVisible++; if (bc.stretchy) { nStretchy++; continue; @@ -81,24 +77,35 @@ static void boxRelayout(uiBox *b) stretchywid -= minimumWidth; } } + if (nVisible == 0) // nothing to do + return; - // 2) now get the size of stretchy controls - if (nStretchy != 0) + // 2) now inset the available rect by the needed padding + if (b->vertical) { + height -= (nVisible - 1) * ypadding; + stretchyht -= (nVisible - 1) * ypadding; + } else { + width -= (nVisible - 1) * xpadding; + stretchywid -= (nVisible - 1) * xpadding; + } + + // 3) now get the size of stretchy controls + if (nStretchy != 0) { if (b->vertical) stretchyht /= nStretchy; else stretchywid /= nStretchy; - // TODO put this in the above if - for (struct boxChild &bc : *(b->controls)) { - if (!uiControlVisible(bc.c)) - continue; - if (bc.stretchy) { - bc.width = stretchywid; - bc.height = stretchyht; + for (struct boxChild &bc : *(b->controls)) { + if (!uiControlVisible(bc.c)) + continue; + if (bc.stretchy) { + bc.width = stretchywid; + bc.height = stretchyht; + } } } - // 3) now we can position controls + // 4) now we can position controls // first, make relative to the top-left corner of the container x = 0; y = 0; @@ -149,16 +156,17 @@ static void uiBoxSyncEnableState(uiWindowsControl *c, int enabled) uiWindowsControlDefaultSetParentHWND(uiBox) -static void uiBoxMinimumSize(uiWindowsControl *c, intmax_t *width, intmax_t *height) +static void uiBoxMinimumSize(uiWindowsControl *c, int *width, int *height) { uiBox *b = uiBox(c); int xpadding, ypadding; - uintmax_t nStretchy; + int nStretchy; // these two contain the largest minimum width and height of all stretchy controls in the box // all stretchy controls will use this value to determine the final minimum size - intmax_t maxStretchyWidth, maxStretchyHeight; - uintmax_t i; - intmax_t minimumWidth, minimumHeight; + int maxStretchyWidth, maxStretchyHeight; + int i; + int minimumWidth, minimumHeight; + int nVisible; uiWindowsSizing sizing; *width = 0; @@ -169,21 +177,16 @@ static void uiBoxMinimumSize(uiWindowsControl *c, intmax_t *width, intmax_t *hei // 0) get this Box's padding boxPadding(b, &xpadding, &ypadding); - // 1) initialize the desired rect with the needed padding - // TODO this is wrong if any controls are hidden - if (b->vertical) - *height = (b->controls->size() - 1) * ypadding; - else - *width = (b->controls->size() - 1) * xpadding; - - // 2) add in the size of non-stretchy controls and get (but not add in) the largest widths and heights of stretchy controls + // 1) add in the size of non-stretchy controls and get (but not add in) the largest widths and heights of stretchy controls // we still add in like direction of stretchy controls nStretchy = 0; maxStretchyWidth = 0; maxStretchyHeight = 0; + nVisible = 0; for (const struct boxChild &bc : *(b->controls)) { if (!uiControlVisible(bc.c)) continue; + nVisible++; uiWindowsControlMinimumSize(uiWindowsControl(bc.c), &minimumWidth, &minimumHeight); if (bc.stretchy) { nStretchy++; @@ -204,6 +207,14 @@ static void uiBoxMinimumSize(uiWindowsControl *c, intmax_t *width, intmax_t *hei *height = minimumHeight; } } + if (nVisible == 0) // just return 0x0 + return; + + // 2) now outset the desired rect with the needed padding + if (b->vertical) + *height += (nVisible - 1) * ypadding; + else + *width += (nVisible - 1) * xpadding; // 3) and now we can add in stretchy controls if (b->vertical) @@ -226,11 +237,16 @@ static void uiBoxMinimumSizeChanged(uiWindowsControl *c) uiWindowsControlDefaultLayoutRect(uiBox) uiWindowsControlDefaultAssignControlIDZOrder(uiBox) +static void uiBoxChildVisibilityChanged(uiWindowsControl *c) +{ + // TODO eliminate the redundancy + uiWindowsControlMinimumSizeChanged(c); +} + static void boxArrangeChildren(uiBox *b) { LONG_PTR controlID; HWND insertAfter; - uintmax_t i; controlID = 100; insertAfter = NULL; @@ -251,7 +267,7 @@ void uiBoxAppend(uiBox *b, uiControl *c, int stretchy) uiWindowsControlMinimumSizeChanged(uiWindowsControl(b)); } -void uiBoxDelete(uiBox *b, uintmax_t index) +void uiBoxDelete(uiBox *b, int index) { uiControl *c; diff --git a/windows/button.cpp b/windows/button.cpp index 057d8763..d8913ec7 100644 --- a/windows/button.cpp +++ b/windows/button.cpp @@ -33,7 +33,7 @@ uiWindowsControlAllDefaultsExceptDestroy(uiButton) // from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing #define buttonHeight 14 -static void uiButtonMinimumSize(uiWindowsControl *c, intmax_t *width, intmax_t *height) +static void uiButtonMinimumSize(uiWindowsControl *c, int *width, int *height) { uiButton *b = uiButton(c); SIZE size; @@ -95,7 +95,7 @@ uiButton *uiNewButton(const char *text) BS_PUSHBUTTON | WS_TABSTOP, hInstance, NULL, TRUE); - uiFree(wtext); + uiprivFree(wtext); uiWindowsRegisterWM_COMMANDHandler(b->hwnd, onWM_COMMAND, uiControl(b)); uiButtonOnClicked(b, defaultOnClicked, NULL); diff --git a/windows/checkbox.cpp b/windows/checkbox.cpp index 7c30c113..3d8c92e3 100644 --- a/windows/checkbox.cpp +++ b/windows/checkbox.cpp @@ -43,7 +43,7 @@ uiWindowsControlAllDefaultsExceptDestroy(uiCheckbox) // from http://msdn.microsoft.com/en-us/library/windows/desktop/bb226818%28v=vs.85%29.aspx #define checkboxXFromLeftOfBoxToLeftOfLabel 12 -static void uiCheckboxMinimumSize(uiWindowsControl *cc, intmax_t *width, intmax_t *height) +static void uiCheckboxMinimumSize(uiWindowsControl *cc, int *width, int *height) { uiCheckbox *c = uiCheckbox(cc); uiWindowsSizing sizing; @@ -108,7 +108,7 @@ uiCheckbox *uiNewCheckbox(const char *text) BS_CHECKBOX | WS_TABSTOP, hInstance, NULL, TRUE); - uiFree(wtext); + uiprivFree(wtext); uiWindowsRegisterWM_COMMANDHandler(c->hwnd, onWM_COMMAND, uiControl(c)); uiCheckboxOnToggled(c, defaultOnToggled, NULL); diff --git a/windows/colorbutton.cpp b/windows/colorbutton.cpp new file mode 100644 index 00000000..c1ba6954 --- /dev/null +++ b/windows/colorbutton.cpp @@ -0,0 +1,192 @@ +// 16 may 2016 +#include "uipriv_windows.hpp" + +struct uiColorButton { + uiWindowsControl c; + HWND hwnd; + double r; + double g; + double b; + double a; + void (*onChanged)(uiColorButton *, void *); + void *onChangedData; +}; + +static void uiColorButtonDestroy(uiControl *c) +{ + uiColorButton *b = uiColorButton(c); + + uiWindowsUnregisterWM_COMMANDHandler(b->hwnd); + uiWindowsUnregisterWM_NOTIFYHandler(b->hwnd); + uiWindowsEnsureDestroyWindow(b->hwnd); + uiFreeControl(uiControl(b)); +} + +static BOOL onWM_COMMAND(uiControl *c, HWND hwnd, WORD code, LRESULT *lResult) +{ + uiColorButton *b = uiColorButton(c); + HWND parent; + struct colorDialogRGBA rgba; + + if (code != BN_CLICKED) + return FALSE; + + parent = parentToplevel(b->hwnd); + rgba.r = b->r; + rgba.g = b->g; + rgba.b = b->b; + rgba.a = b->a; + if (showColorDialog(parent, &rgba)) { + b->r = rgba.r; + b->g = rgba.g; + b->b = rgba.b; + b->a = rgba.a; + invalidateRect(b->hwnd, NULL, TRUE); + (*(b->onChanged))(b, b->onChangedData); + } + + *lResult = 0; + return TRUE; +} + +static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) +{ + uiColorButton *b = uiColorButton(c); + NMCUSTOMDRAW *nm = (NMCUSTOMDRAW *) nmhdr; + RECT client; + ID2D1DCRenderTarget *rt; + D2D1_RECT_F r; + D2D1_COLOR_F color; + D2D1_BRUSH_PROPERTIES bprop; + ID2D1SolidColorBrush *brush; + uiWindowsSizing sizing; + int x, y; + HRESULT hr; + + if (nmhdr->code != NM_CUSTOMDRAW) + return FALSE; + // and allow the button to draw its background + if (nm->dwDrawStage != CDDS_PREPAINT) + return FALSE; + + uiWindowsEnsureGetClientRect(b->hwnd, &client); + rt = makeHDCRenderTarget(nm->hdc, &client); + rt->BeginDraw(); + + uiWindowsGetSizing(b->hwnd, &sizing); + x = 3; // should be enough + y = 3; + uiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y); + r.left = client.left + x; + r.top = client.top + y; + r.right = client.right - x; + r.bottom = client.bottom - y; + + color.r = b->r; + color.g = b->g; + color.b = b->b; + color.a = b->a; + ZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES)); + bprop.opacity = 1.0; + bprop.transform._11 = 1; + bprop.transform._22 = 1; + hr = rt->CreateSolidColorBrush(&color, &bprop, &brush); + if (hr != S_OK) + logHRESULT(L"error creating brush for color button", hr); + rt->FillRectangle(&r, brush); + brush->Release(); + + hr = rt->EndDraw(NULL, NULL); + if (hr != S_OK) + logHRESULT(L"error drawing color on color button", hr); + rt->Release(); + + // skip default processing (don't draw text) + *lResult = CDRF_SKIPDEFAULT; + return TRUE; +} + +uiWindowsControlAllDefaultsExceptDestroy(uiColorButton) + +// from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing +#define buttonHeight 14 + +// TODO check widths +static void uiColorButtonMinimumSize(uiWindowsControl *c, int *width, int *height) +{ + uiColorButton *b = uiColorButton(c); + SIZE size; + uiWindowsSizing sizing; + int y; + + // try the comctl32 version 6 way + size.cx = 0; // explicitly ask for ideal size + size.cy = 0; + if (SendMessageW(b->hwnd, BCM_GETIDEALSIZE, 0, (LPARAM) (&size)) != FALSE) { + *width = size.cx; + *height = size.cy; + return; + } + + // that didn't work; fall back to using Microsoft's metrics + // Microsoft says to use a fixed width for all buttons; this isn't good enough + // use the text width instead, with some edge padding + *width = uiWindowsWindowTextWidth(b->hwnd) + (2 * GetSystemMetrics(SM_CXEDGE)); + y = buttonHeight; + uiWindowsGetSizing(b->hwnd, &sizing); + uiWindowsSizingDlgUnitsToPixels(&sizing, NULL, &y); + *height = y; +} + +static void defaultOnChanged(uiColorButton *b, void *data) +{ + // do nothing +} + +void uiColorButtonColor(uiColorButton *b, double *r, double *g, double *bl, double *a) +{ + *r = b->r; + *g = b->g; + *bl = b->b; + *a = b->a; +} + +void uiColorButtonSetColor(uiColorButton *b, double r, double g, double bl, double a) +{ + b->r = r; + b->g = g; + b->b = bl; + b->a = a; + invalidateRect(b->hwnd, NULL, TRUE); +} + +void uiColorButtonOnChanged(uiColorButton *b, void (*f)(uiColorButton *, void *), void *data) +{ + b->onChanged = f; + b->onChangedData = data; +} + +uiColorButton *uiNewColorButton(void) +{ + uiColorButton *b; + + uiWindowsNewControl(uiColorButton, b); + + // initial color is black + b->r = 0.0; + b->g = 0.0; + b->b = 0.0; + b->a = 1.0; + + b->hwnd = uiWindowsEnsureCreateControlHWND(0, + L"button", L" ", // TODO; can't use "" TODO + BS_PUSHBUTTON | WS_TABSTOP, + hInstance, NULL, + TRUE); + + uiWindowsRegisterWM_COMMANDHandler(b->hwnd, onWM_COMMAND, uiControl(b)); + uiWindowsRegisterWM_NOTIFYHandler(b->hwnd, onWM_NOTIFY, uiControl(b)); + uiColorButtonOnChanged(b, defaultOnChanged, NULL); + + return b; +} diff --git a/windows/colordialog.cpp b/windows/colordialog.cpp new file mode 100644 index 00000000..d7030a4f --- /dev/null +++ b/windows/colordialog.cpp @@ -0,0 +1,1457 @@ +// 16 may 2016 +#include "uipriv_windows.hpp" + +// TODO should the d2dscratch programs capture mouse? + +struct colorDialog { + HWND hwnd; + + HWND svChooser; + HWND hSlider; + HWND preview; + HWND opacitySlider; + HWND editH; + HWND editS; + HWND editV; + HWND editRDouble, editRInt; + HWND editGDouble, editGInt; + HWND editBDouble, editBInt; + HWND editADouble, editAInt; + HWND editHex; + + double h; + double s; + double v; + double a; + struct colorDialogRGBA *out; + + BOOL updating; +}; + +// both of these are from the wikipedia page on HSV +// TODO what to do about negative h? +static void rgb2HSV(double r, double g, double b, double *h, double *s, double *v) +{ + double M, m; + int whichmax; + double c; + + M = r; + whichmax = 0; + if (M < g) { + M = g; + whichmax = 1; + } + if (M < b) { + M = b; + whichmax = 2; + } + m = r; + if (m > g) + m = g; + if (m > b) + m = b; + c = M - m; + + if (c == 0) + *h = 0; + else { + switch (whichmax) { + case 0: + *h = ((g - b) / c); + *h = fmod(*h, 6); + break; + case 1: + *h = ((b - r) / c) + 2; + break; + case 2: + *h = ((r - g) / c) + 4; + break; + } + *h /= 6; // put in range [0,1) + } + + *v = M; + + if (c == 0) + *s = 0; + else + *s = c / *v; +} + +// TODO negative R values? +static void hsv2RGB(double h, double s, double v, double *r, double *g, double *b) +{ + double c; + double hPrime; + int h60; + double x; + double m; + double c1, c2; + + c = v * s; + hPrime = h * 6; + h60 = (int) hPrime; // equivalent to splitting into 60° chunks + x = c * (1.0 - fabs(fmod(hPrime, 2) - 1.0)); + m = v - c; + switch (h60) { + case 0: + *r = c + m; + *g = x + m; + *b = m; + return; + case 1: + *r = x + m; + *g = c + m; + *b = m; + return; + case 2: + *r = m; + *g = c + m; + *b = x + m; + return; + case 3: + *r = m; + *g = x + m; + *b = c + m; + return; + case 4: + *r = x + m; + *g = m; + *b = c + m; + return; + case 5: + *r = c + m; + *g = m; + *b = x + m; + return; + } + // TODO +} + +#define hexd L"0123456789ABCDEF" + +static void rgba2Hex(uint8_t r, uint8_t g, uint8_t b, uint8_t a, WCHAR *buf) +{ + buf[0] = L'#'; + buf[1] = hexd[(a >> 4) & 0xF]; + buf[2] = hexd[a & 0xF]; + buf[3] = hexd[(r >> 4) & 0xF]; + buf[4] = hexd[r & 0xF]; + buf[5] = hexd[(g >> 4) & 0xF]; + buf[6] = hexd[g & 0xF]; + buf[7] = hexd[(b >> 4) & 0xF]; + buf[8] = hexd[b & 0xF]; + buf[9] = L'\0'; +} + +static int convHexDigit(WCHAR c) +{ + if (c >= L'0' && c <= L'9') + return c - L'0'; + if (c >= L'A' && c <= L'F') + return c - L'A' + 0xA; + if (c >= L'a' && c <= L'f') + return c - L'a' + 0xA; + return -1; +} + +// TODO allow #NNN shorthand +static BOOL hex2RGBA(WCHAR *buf, double *r, double *g, double *b, double *a) +{ + uint8_t component; + int i; + + if (*buf == L'#') + buf++; + + component = 0; + i = convHexDigit(*buf++); + if (i < 0) + return FALSE; + component |= ((uint8_t) i) << 4; + i = convHexDigit(*buf++); + if (i < 0) + return FALSE; + component |= ((uint8_t) i); + *a = ((double) component) / 255; + + component = 0; + i = convHexDigit(*buf++); + if (i < 0) + return FALSE; + component |= ((uint8_t) i) << 4; + i = convHexDigit(*buf++); + if (i < 0) + return FALSE; + component |= ((uint8_t) i); + *r = ((double) component) / 255; + + component = 0; + i = convHexDigit(*buf++); + if (i < 0) + return FALSE; + component |= ((uint8_t) i) << 4; + i = convHexDigit(*buf++); + if (i < 0) + return FALSE; + component |= ((uint8_t) i); + *g = ((double) component) / 255; + + if (*buf == L'\0') { // #NNNNNN syntax + *b = *g; + *g = *r; + *r = *a; + *a = 1; + return TRUE; + } + + component = 0; + i = convHexDigit(*buf++); + if (i < 0) + return FALSE; + component |= ((uint8_t) i) << 4; + i = convHexDigit(*buf++); + if (i < 0) + return FALSE; + component |= ((uint8_t) i); + *b = ((double) component) / 255; + + return *buf == L'\0'; +} + +static void updateDouble(HWND hwnd, double d, HWND whichChanged) +{ + WCHAR *str; + + if (whichChanged == hwnd) + return; + str = ftoutf16(d); + setWindowText(hwnd, str); + uiprivFree(str); +} + +static void updateDialog(struct colorDialog *c, HWND whichChanged) +{ + double r, g, b; + uint8_t rb, gb, bb, ab; + WCHAR *str; + WCHAR hexbuf[16]; // more than enough + + c->updating = TRUE; + + updateDouble(c->editH, c->h, whichChanged); + updateDouble(c->editS, c->s, whichChanged); + updateDouble(c->editV, c->v, whichChanged); + + hsv2RGB(c->h, c->s, c->v, &r, &g, &b); + + updateDouble(c->editRDouble, r, whichChanged); + updateDouble(c->editGDouble, g, whichChanged); + updateDouble(c->editBDouble, b, whichChanged); + updateDouble(c->editADouble, c->a, whichChanged); + + rb = (uint8_t) (r * 255); + gb = (uint8_t) (g * 255); + bb = (uint8_t) (b * 255); + ab = (uint8_t) (c->a * 255); + + if (whichChanged != c->editRInt) { + str = itoutf16(rb); + setWindowText(c->editRInt, str); + uiprivFree(str); + } + if (whichChanged != c->editGInt) { + str = itoutf16(gb); + setWindowText(c->editGInt, str); + uiprivFree(str); + } + if (whichChanged != c->editBInt) { + str = itoutf16(bb); + setWindowText(c->editBInt, str); + uiprivFree(str); + } + if (whichChanged != c->editAInt) { + str = itoutf16(ab); + setWindowText(c->editAInt, str); + uiprivFree(str); + } + + if (whichChanged != c->editHex) { + rgba2Hex(rb, gb, bb, ab, hexbuf); + setWindowText(c->editHex, hexbuf); + } + + // TODO TRUE? + invalidateRect(c->svChooser, NULL, TRUE); + invalidateRect(c->hSlider, NULL, TRUE); + invalidateRect(c->preview, NULL, TRUE); + invalidateRect(c->opacitySlider, NULL, TRUE); + + c->updating = FALSE; +} + +// this imitates http://blogs.msdn.com/b/wpfsdk/archive/2006/10/26/uncommon-dialogs--font-chooser-and-color-picker-dialogs.aspx +static void drawGrid(ID2D1RenderTarget *rt, D2D1_RECT_F *fillRect) +{ + D2D1_SIZE_F size; + D2D1_PIXEL_FORMAT pformat; + ID2D1BitmapRenderTarget *brt; + D2D1_COLOR_F color; + D2D1_BRUSH_PROPERTIES bprop; + ID2D1SolidColorBrush *brush; + D2D1_RECT_F rect; + ID2D1Bitmap *bitmap; + D2D1_BITMAP_BRUSH_PROPERTIES bbp; + ID2D1BitmapBrush *bb; + HRESULT hr; + + // mind the divisions; they represent the fact the original uses a viewport + size.width = 100 / 10; + size.height = 100 / 10; + // yay more ABI bugs +#ifdef _MSC_VER + pformat = rt->GetPixelFormat(); +#else + { + typedef D2D1_PIXEL_FORMAT *(__stdcall ID2D1RenderTarget::* GetPixelFormatF)(D2D1_PIXEL_FORMAT *); + GetPixelFormatF gpf; + + gpf = (GetPixelFormatF) (&(rt->GetPixelFormat)); + (rt->*gpf)(&pformat); + } +#endif + hr = rt->CreateCompatibleRenderTarget(&size, NULL, + &pformat, D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE, + &brt); + if (hr != S_OK) + logHRESULT(L"error creating render target for grid", hr); + + brt->BeginDraw(); + + color.r = 1.0; + color.g = 1.0; + color.b = 1.0; + color.a = 1.0; + brt->Clear(&color); + + color = D2D1::ColorF(D2D1::ColorF::LightGray, 1.0); + ZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES)); + bprop.opacity = 1.0; + bprop.transform._11 = 1; + bprop.transform._22 = 1; + hr = brt->CreateSolidColorBrush(&color, &bprop, &brush); + if (hr != S_OK) + logHRESULT(L"error creating brush for grid", hr); + rect.left = 0; + rect.top = 0; + rect.right = 50 / 10; + rect.bottom = 50 / 10; + brt->FillRectangle(&rect, brush); + rect.left = 50 / 10; + rect.top = 50 / 10; + rect.right = 100 / 10; + rect.bottom = 100 / 10; + brt->FillRectangle(&rect, brush); + brush->Release(); + + hr = brt->EndDraw(NULL, NULL); + if (hr != S_OK) + logHRESULT(L"error finalizing render target for grid", hr); + hr = brt->GetBitmap(&bitmap); + if (hr != S_OK) + logHRESULT(L"error getting bitmap for grid", hr); + brt->Release(); + + ZeroMemory(&bbp, sizeof (D2D1_BITMAP_BRUSH_PROPERTIES)); + bbp.extendModeX = D2D1_EXTEND_MODE_WRAP; + bbp.extendModeY = D2D1_EXTEND_MODE_WRAP; + bbp.interpolationMode = D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR; + hr = rt->CreateBitmapBrush(bitmap, &bbp, &bprop, &bb); + if (hr != S_OK) + logHRESULT(L"error creating bitmap brush for grid", hr); + rt->FillRectangle(fillRect, bb); + bb->Release(); + bitmap->Release(); +} + +// this interesting approach comes from http://blogs.msdn.com/b/wpfsdk/archive/2006/10/26/uncommon-dialogs--font-chooser-and-color-picker-dialogs.aspx +static void drawSVChooser(struct colorDialog *c, ID2D1RenderTarget *rt) +{ + D2D1_SIZE_F size; + D2D1_RECT_F rect; + double rTop, gTop, bTop; + D2D1_GRADIENT_STOP stops[2]; + ID2D1GradientStopCollection *collection; + D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES lprop; + D2D1_BRUSH_PROPERTIES bprop; + ID2D1LinearGradientBrush *brush; + ID2D1LinearGradientBrush *opacity; + ID2D1Layer *layer; + D2D1_LAYER_PARAMETERS layerparams; + D2D1_ELLIPSE mparam; + D2D1_COLOR_F mcolor; + ID2D1SolidColorBrush *markerBrush; + HRESULT hr; + + size = realGetSize(rt); + rect.left = 0; + rect.top = 0; + rect.right = size.width; + rect.bottom = size.height; + + drawGrid(rt, &rect); + + // first, draw a vertical gradient from the current hue at max S/V to black + // the source example draws it upside down; let's do so too just to be safe + hsv2RGB(c->h, 1.0, 1.0, &rTop, &gTop, &bTop); + stops[0].position = 0; + stops[0].color.r = 0.0; + stops[0].color.g = 0.0; + stops[0].color.b = 0.0; + stops[0].color.a = 1.0; + stops[1].position = 1; + stops[1].color.r = rTop; + stops[1].color.g = gTop; + stops[1].color.b = bTop; + stops[1].color.a = 1.0; + hr = rt->CreateGradientStopCollection(stops, 2, + D2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP, + &collection); + if (hr != S_OK) + logHRESULT(L"error making gradient stop collection for first gradient in SV chooser", hr); + ZeroMemory(&lprop, sizeof (D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES)); + lprop.startPoint.x = size.width / 2; + lprop.startPoint.y = size.height; + lprop.endPoint.x = size.width / 2; + lprop.endPoint.y = 0; + // TODO decide what to do about the duplication of this + ZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES)); + bprop.opacity = c->a; // note this part; we also use it below for the layer + bprop.transform._11 = 1; + bprop.transform._22 = 1; + hr = rt->CreateLinearGradientBrush(&lprop, &bprop, + collection, &brush); + if (hr != S_OK) + logHRESULT(L"error making gradient brush for first gradient in SV chooser", hr); + rt->FillRectangle(&rect, brush); + brush->Release(); + collection->Release(); + + // second, create an opacity mask for the third step: a horizontal gradientthat goes from opaque to translucent + stops[0].position = 0; + stops[0].color.r = 0.0; + stops[0].color.g = 0.0; + stops[0].color.b = 0.0; + stops[0].color.a = 1.0; + stops[1].position = 1; + stops[1].color.r = 0.0; + stops[1].color.g = 0.0; + stops[1].color.b = 0.0; + stops[1].color.a = 0.0; + hr = rt->CreateGradientStopCollection(stops, 2, + D2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP, + &collection); + if (hr != S_OK) + logHRESULT(L"error making gradient stop collection for opacity mask gradient in SV chooser", hr); + ZeroMemory(&lprop, sizeof (D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES)); + lprop.startPoint.x = 0; + lprop.startPoint.y = size.height / 2; + lprop.endPoint.x = size.width; + lprop.endPoint.y = size.height / 2; + ZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES)); + bprop.opacity = 1.0; + bprop.transform._11 = 1; + bprop.transform._22 = 1; + hr = rt->CreateLinearGradientBrush(&lprop, &bprop, + collection, &opacity); + if (hr != S_OK) + logHRESULT(L"error making gradient brush for opacity mask gradient in SV chooser", hr); + collection->Release(); + + // finally, make a vertical gradient from white at the top to black at the bottom (right side up this time) and with the previous opacity mask + stops[0].position = 0; + stops[0].color.r = 1.0; + stops[0].color.g = 1.0; + stops[0].color.b = 1.0; + stops[0].color.a = 1.0; + stops[1].position = 1; + stops[1].color.r = 0.0; + stops[1].color.g = 0.0; + stops[1].color.b = 0.0; + stops[1].color.a = 1.0; + hr = rt->CreateGradientStopCollection(stops, 2, + D2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP, + &collection); + if (hr != S_OK) + logHRESULT(L"error making gradient stop collection for second gradient in SV chooser", hr); + ZeroMemory(&lprop, sizeof (D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES)); + lprop.startPoint.x = size.width / 2; + lprop.startPoint.y = 0; + lprop.endPoint.x = size.width / 2; + lprop.endPoint.y = size.height; + ZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES)); + bprop.opacity = 1.0; + bprop.transform._11 = 1; + bprop.transform._22 = 1; + hr = rt->CreateLinearGradientBrush(&lprop, &bprop, + collection, &brush); + if (hr != S_OK) + logHRESULT(L"error making gradient brush for second gradient in SV chooser", hr); + // oh but wait we can't use FillRectangle() with an opacity mask + // and we can't use FillGeometry() with both an opacity mask and a non-bitmap + // layers it is! + hr = rt->CreateLayer(&size, &layer); + if (hr != S_OK) + logHRESULT(L"error making layer for second gradient in SV chooser", hr); + ZeroMemory(&layerparams, sizeof (D2D1_LAYER_PARAMETERS)); + layerparams.contentBounds = rect; + // TODO make sure these are right + layerparams.geometricMask = NULL; + layerparams.maskAntialiasMode = D2D1_ANTIALIAS_MODE_PER_PRIMITIVE; + layerparams.maskTransform._11 = 1; + layerparams.maskTransform._22 = 1; + layerparams.opacity = c->a; // here's the other use of c->a to note + layerparams.opacityBrush = opacity; + layerparams.layerOptions = D2D1_LAYER_OPTIONS_NONE; + rt->PushLayer(&layerparams, layer); + rt->FillRectangle(&rect, brush); + rt->PopLayer(); + layer->Release(); + brush->Release(); + collection->Release(); + opacity->Release(); + + // and now we just draw the marker + ZeroMemory(&mparam, sizeof (D2D1_ELLIPSE)); + mparam.point.x = c->s * size.width; + mparam.point.y = (1 - c->v) * size.height; + mparam.radiusX = 7; + mparam.radiusY = 7; + // TODO make the color contrast? + mcolor.r = 1.0; + mcolor.g = 1.0; + mcolor.b = 1.0; + mcolor.a = 1.0; + bprop.opacity = 1.0; // the marker should always be opaque + hr = rt->CreateSolidColorBrush(&mcolor, &bprop, &markerBrush); + if (hr != S_OK) + logHRESULT(L"error creating brush for SV chooser marker", hr); + rt->DrawEllipse(&mparam, markerBrush, 2, NULL); + markerBrush->Release(); +} + +static LRESULT CALLBACK svChooserSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) +{ + ID2D1RenderTarget *rt; + struct colorDialog *c; + D2D1_POINT_2F *pos; + D2D1_SIZE_F *size; + + c = (struct colorDialog *) dwRefData; + switch (uMsg) { + case msgD2DScratchPaint: + rt = (ID2D1RenderTarget *) lParam; + drawSVChooser(c, rt); + return 0; + case msgD2DScratchLButtonDown: + pos = (D2D1_POINT_2F *) wParam; + size = (D2D1_SIZE_F *) lParam; + c->s = pos->x / size->width; + c->v = 1 - (pos->y / size->height); + updateDialog(c, NULL); + return 0; + case WM_NCDESTROY: + if (RemoveWindowSubclass(hwnd, svChooserSubProc, uIdSubclass) == FALSE) + logLastError(L"error removing color dialog SV chooser subclass"); + break; + } + return DefSubclassProc(hwnd, uMsg, wParam, lParam); +} + +static void drawArrow(ID2D1RenderTarget *rt, D2D1_POINT_2F center, double hypot) +{ + double leg; + D2D1_RECT_F rect; + D2D1_MATRIX_3X2_F oldtf, rotate; + D2D1_COLOR_F color; + D2D1_BRUSH_PROPERTIES bprop; + ID2D1SolidColorBrush *brush; + HRESULT hr; + + // to avoid needing a geometry, this will just be a rotated square + // compute the length of each side; the diagonal of the square is 2 * offset to gradient + // a^2 + a^2 = c^2 -> 2a^2 = c^2 + // a = sqrt(c^2/2) + hypot *= hypot; + hypot /= 2; + leg = sqrt(hypot); + rect.left = center.x - leg; + rect.top = center.y - leg; + rect.right = center.x + leg; + rect.bottom = center.y + leg; + + // now we need to rotate the render target 45° (either way works) about the center point + rt->GetTransform(&oldtf); + rotate = oldtf * D2D1::Matrix3x2F::Rotation(45, center); + rt->SetTransform(&rotate); + + // and draw + color.r = 0.0; + color.g = 0.0; + color.b = 0.0; + color.a = 1.0; + ZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES)); + bprop.opacity = 1.0; + bprop.transform._11 = 1; + bprop.transform._22 = 1; + hr = rt->CreateSolidColorBrush(&color, &bprop, &brush); + if (hr != S_OK) + logHRESULT(L"error creating brush for arrow", hr); + rt->FillRectangle(&rect, brush); + brush->Release(); + + // clean up + rt->SetTransform(&oldtf); +} + +// the gradient stuff also comes from http://blogs.msdn.com/b/wpfsdk/archive/2006/10/26/uncommon-dialogs--font-chooser-and-color-picker-dialogs.aspx +#define nStops (30) +#define degPerStop (360 / nStops) +#define stopIncr (1.0 / ((double) nStops)) + +static void drawHSlider(struct colorDialog *c, ID2D1RenderTarget *rt) +{ + D2D1_SIZE_F size; + D2D1_RECT_F rect; + D2D1_GRADIENT_STOP stops[nStops]; + double r, g, b; + int i; + double h; + ID2D1GradientStopCollection *collection; + D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES lprop; + D2D1_BRUSH_PROPERTIES bprop; + ID2D1LinearGradientBrush *brush; + double hypot; + D2D1_POINT_2F center; + HRESULT hr; + + size = realGetSize(rt); + rect.left = size.width / 6; // leftmost sixth for arrow + rect.top = 0; + rect.right = size.width; + rect.bottom = size.height; + + for (i = 0; i < nStops; i++) { + h = ((double) (i * degPerStop)) / 360.0; + if (i == (nStops - 1)) + h = 0; + hsv2RGB(h, 1.0, 1.0, &r, &g, &b); + stops[i].position = ((double) i) * stopIncr; + stops[i].color.r = r; + stops[i].color.g = g; + stops[i].color.b = b; + stops[i].color.a = 1.0; + } + // and pin the last one + stops[i - 1].position = 1.0; + + hr = rt->CreateGradientStopCollection(stops, nStops, + // note that in this case this gamma is explicitly specified by the original + D2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP, + &collection); + if (hr != S_OK) + logHRESULT(L"error creating stop collection for H slider gradient", hr); + ZeroMemory(&lprop, sizeof (D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES)); + lprop.startPoint.x = (rect.right - rect.left) / 2; + lprop.startPoint.y = 0; + lprop.endPoint.x = (rect.right - rect.left) / 2; + lprop.endPoint.y = size.height; + ZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES)); + bprop.opacity = 1.0; + bprop.transform._11 = 1; + bprop.transform._22 = 1; + hr = rt->CreateLinearGradientBrush(&lprop, &bprop, + collection, &brush); + if (hr != S_OK) + logHRESULT(L"error creating gradient brush for H slider", hr); + rt->FillRectangle(&rect, brush); + brush->Release(); + collection->Release(); + + // now draw a black arrow + center.x = 0; + center.y = c->h * size.height; + hypot = rect.left; + drawArrow(rt, center, hypot); +} + +static LRESULT CALLBACK hSliderSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) +{ + ID2D1RenderTarget *rt; + struct colorDialog *c; + D2D1_POINT_2F *pos; + D2D1_SIZE_F *size; + + c = (struct colorDialog *) dwRefData; + switch (uMsg) { + case msgD2DScratchPaint: + rt = (ID2D1RenderTarget *) lParam; + drawHSlider(c, rt); + return 0; + case msgD2DScratchLButtonDown: + pos = (D2D1_POINT_2F *) wParam; + size = (D2D1_SIZE_F *) lParam; + c->h = pos->y / size->height; + updateDialog(c, NULL); + return 0; + case WM_NCDESTROY: + if (RemoveWindowSubclass(hwnd, hSliderSubProc, uIdSubclass) == FALSE) + logLastError(L"error removing color dialog H slider subclass"); + break; + } + return DefSubclassProc(hwnd, uMsg, wParam, lParam); +} + +static void drawPreview(struct colorDialog *c, ID2D1RenderTarget *rt) +{ + D2D1_SIZE_F size; + D2D1_RECT_F rect; + double r, g, b; + D2D1_COLOR_F color; + D2D1_BRUSH_PROPERTIES bprop; + ID2D1SolidColorBrush *brush; + HRESULT hr; + + size = realGetSize(rt); + rect.left = 0; + rect.top = 0; + rect.right = size.width; + rect.bottom = size.height; + + drawGrid(rt, &rect); + + hsv2RGB(c->h, c->s, c->v, &r, &g, &b); + color.r = r; + color.g = g; + color.b = b; + color.a = c->a; + ZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES)); + bprop.opacity = 1.0; + bprop.transform._11 = 1; + bprop.transform._22 = 1; + hr = rt->CreateSolidColorBrush(&color, &bprop, &brush); + if (hr != S_OK) + logHRESULT(L"error creating brush for preview", hr); + rt->FillRectangle(&rect, brush); + brush->Release(); +} + +static LRESULT CALLBACK previewSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) +{ + ID2D1RenderTarget *rt; + struct colorDialog *c; + + c = (struct colorDialog *) dwRefData; + switch (uMsg) { + case msgD2DScratchPaint: + rt = (ID2D1RenderTarget *) lParam; + drawPreview(c, rt); + return 0; + case WM_NCDESTROY: + if (RemoveWindowSubclass(hwnd, previewSubProc, uIdSubclass) == FALSE) + logLastError(L"error removing color dialog previewer subclass"); + break; + } + return DefSubclassProc(hwnd, uMsg, wParam, lParam); +} + +// once again, this is based on the Microsoft sample above +static void drawOpacitySlider(struct colorDialog *c, ID2D1RenderTarget *rt) +{ + D2D1_SIZE_F size; + D2D1_RECT_F rect; + D2D1_GRADIENT_STOP stops[2]; + ID2D1GradientStopCollection *collection; + D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES lprop; + D2D1_BRUSH_PROPERTIES bprop; + ID2D1LinearGradientBrush *brush; + double hypot; + D2D1_POINT_2F center; + HRESULT hr; + + size = realGetSize(rt); + rect.left = 0; + rect.top = 0; + rect.right = size.width; + rect.bottom = size.height * (5.0 / 6.0); // bottommost sixth for arrow + + drawGrid(rt, &rect); + + stops[0].position = 0.0; + stops[0].color.r = 0.0; + stops[0].color.g = 0.0; + stops[0].color.b = 0.0; + stops[0].color.a = 1.0; + stops[1].position = 1.0; + stops[1].color.r = 1.0; // this is the XAML color Transparent, as in the source + stops[1].color.g = 1.0; + stops[1].color.b = 1.0; + stops[1].color.a = 0.0; + hr = rt->CreateGradientStopCollection(stops, 2, + // note that in this case this gamma is explicitly specified by the original + D2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP, + &collection); + if (hr != S_OK) + logHRESULT(L"error creating stop collection for opacity slider gradient", hr); + ZeroMemory(&lprop, sizeof (D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES)); + lprop.startPoint.x = 0; + lprop.startPoint.y = (rect.bottom - rect.top) / 2; + lprop.endPoint.x = size.width; + lprop.endPoint.y = (rect.bottom - rect.top) / 2; + ZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES)); + bprop.opacity = 1.0; + bprop.transform._11 = 1; + bprop.transform._22 = 1; + hr = rt->CreateLinearGradientBrush(&lprop, &bprop, + collection, &brush); + if (hr != S_OK) + logHRESULT(L"error creating gradient brush for opacity slider", hr); + rt->FillRectangle(&rect, brush); + brush->Release(); + collection->Release(); + + // now draw a black arrow + center.x = (1 - c->a) * size.width; + center.y = size.height; + hypot = size.height - rect.bottom; + drawArrow(rt, center, hypot); +} + +static LRESULT CALLBACK opacitySliderSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) +{ + ID2D1RenderTarget *rt; + struct colorDialog *c; + D2D1_POINT_2F *pos; + D2D1_SIZE_F *size; + + c = (struct colorDialog *) dwRefData; + switch (uMsg) { + case msgD2DScratchPaint: + rt = (ID2D1RenderTarget *) lParam; + drawOpacitySlider(c, rt); + return 0; + case msgD2DScratchLButtonDown: + pos = (D2D1_POINT_2F *) wParam; + size = (D2D1_SIZE_F *) lParam; + c->a = 1 - (pos->x / size->width); + updateDialog(c, NULL); + return 0; + case WM_NCDESTROY: + if (RemoveWindowSubclass(hwnd, opacitySliderSubProc, uIdSubclass) == FALSE) + logLastError(L"error removing color dialog opacity slider subclass"); + break; + } + return DefSubclassProc(hwnd, uMsg, wParam, lParam); +} + +// TODO extract into d2dscratch.cpp, use in font dialog +HWND replaceWithD2DScratch(HWND parent, int id, SUBCLASSPROC subproc, void *data) +{ + HWND replace; + RECT r; + + replace = getDlgItem(parent, id); + uiWindowsEnsureGetWindowRect(replace, &r); + mapWindowRect(NULL, parent, &r); + uiWindowsEnsureDestroyWindow(replace); + return newD2DScratch(parent, &r, (HMENU) id, subproc, (DWORD_PTR) data); + // TODO preserve Z-order +} + +// a few issues: +// - some controls are positioned wrong; see http://stackoverflow.com/questions/37263267/why-are-some-of-my-controls-positioned-slightly-off-in-a-dialog-template-in-a-re +// - labels are too low; need to adjust them by the font's internal leading +// fixupControlPositions() and the following helper routines fix that for us + +static LONG offsetTo(HWND a, HWND b) +{ + RECT ra, rb; + + uiWindowsEnsureGetWindowRect(a, &ra); + uiWindowsEnsureGetWindowRect(b, &rb); + return rb.top - ra.bottom; +} + +static void moveWindowsUp(struct colorDialog *c, LONG by, ...) +{ + va_list ap; + HWND cur; + RECT r; + + va_start(ap, by); + for (;;) { + cur = va_arg(ap, HWND); + if (cur == NULL) + break; + uiWindowsEnsureGetWindowRect(cur, &r); + mapWindowRect(NULL, c->hwnd, &r); + r.top -= by; + r.bottom -= by; + // TODO this isn't technically during a resize + uiWindowsEnsureMoveWindowDuringResize(cur, + r.left, r.top, + r.right - r.left, r.bottom - r.top); + } + va_end(ap); +} + +static void fixupControlPositions(struct colorDialog *c) +{ + HWND labelH; + HWND labelS; + HWND labelV; + HWND labelR; + HWND labelG; + HWND labelB; + HWND labelA; + HWND labelHex; + LONG offset; + uiWindowsSizing sizing; + + labelH = getDlgItem(c->hwnd, rcHLabel); + labelS = getDlgItem(c->hwnd, rcSLabel); + labelV = getDlgItem(c->hwnd, rcVLabel); + labelR = getDlgItem(c->hwnd, rcRLabel); + labelG = getDlgItem(c->hwnd, rcGLabel); + labelB = getDlgItem(c->hwnd, rcBLabel); + labelA = getDlgItem(c->hwnd, rcALabel); + labelHex = getDlgItem(c->hwnd, rcHexLabel); + + offset = offsetTo(c->editH, c->editS); + moveWindowsUp(c, offset, + labelS, c->editS, + labelG, c->editGDouble, c->editGInt, + NULL); + offset = offsetTo(c->editS, c->editV); + moveWindowsUp(c, offset, + labelV, c->editV, + labelB, c->editBDouble, c->editBInt, + NULL); + offset = offsetTo(c->editBDouble, c->editADouble); + moveWindowsUp(c, offset, + labelA, c->editADouble, c->editAInt, + NULL); + + getSizing(c->hwnd, &sizing, (HFONT) SendMessageW(labelH, WM_GETFONT, 0, 0)); + offset = sizing.InternalLeading; + moveWindowsUp(c, offset, + labelH, labelS, labelV, + labelR, labelG, labelB, labelA, + labelHex, + NULL); +} + +static struct colorDialog *beginColorDialog(HWND hwnd, LPARAM lParam) +{ + struct colorDialog *c; + + c = uiprivNew(struct colorDialog); + c->hwnd = hwnd; + c->out = (struct colorDialogRGBA *) lParam; + // load initial values now + rgb2HSV(c->out->r, c->out->g, c->out->b, &(c->h), &(c->s), &(c->v)); + c->a = c->out->a; + + // TODO set up d2dscratches + + // TODO prefix all these with rcColor instead of just rc + c->editH = getDlgItem(c->hwnd, rcH); + c->editS = getDlgItem(c->hwnd, rcS); + c->editV = getDlgItem(c->hwnd, rcV); + c->editRDouble = getDlgItem(c->hwnd, rcRDouble); + c->editRInt = getDlgItem(c->hwnd, rcRInt); + c->editGDouble = getDlgItem(c->hwnd, rcGDouble); + c->editGInt = getDlgItem(c->hwnd, rcGInt); + c->editBDouble = getDlgItem(c->hwnd, rcBDouble); + c->editBInt = getDlgItem(c->hwnd, rcBInt); + c->editADouble = getDlgItem(c->hwnd, rcADouble); + c->editAInt = getDlgItem(c->hwnd, rcAInt); + c->editHex = getDlgItem(c->hwnd, rcHex); + + c->svChooser = replaceWithD2DScratch(c->hwnd, rcColorSVChooser, svChooserSubProc, c); + c->hSlider = replaceWithD2DScratch(c->hwnd, rcColorHSlider, hSliderSubProc, c); + c->preview = replaceWithD2DScratch(c->hwnd, rcPreview, previewSubProc, c); + c->opacitySlider = replaceWithD2DScratch(c->hwnd, rcOpacitySlider, opacitySliderSubProc, c); + + fixupControlPositions(c); + + // and get the ball rolling + updateDialog(c, NULL); + return c; +} + +static void endColorDialog(struct colorDialog *c, INT_PTR code) +{ + if (EndDialog(c->hwnd, code) == 0) + logLastError(L"error ending color dialog"); + uiprivFree(c); +} + +// TODO make this void on the font dialog too +static void tryFinishDialog(struct colorDialog *c, WPARAM wParam) +{ + // cancelling + if (LOWORD(wParam) != IDOK) { + endColorDialog(c, 1); + return; + } + + // OK + hsv2RGB(c->h, c->s, c->v, &(c->out->r), &(c->out->g), &(c->out->b)); + c->out->a = c->a; + endColorDialog(c, 2); +} + +static double editDouble(HWND hwnd) +{ + WCHAR *s; + double d; + + s = windowText(hwnd); + d = _wtof(s); + uiprivFree(s); + return d; +} + +static void hChanged(struct colorDialog *c) +{ + double h; + + h = editDouble(c->editH); + if (h < 0 || h >= 1.0) // note the >= + return; + c->h = h; + updateDialog(c, c->editH); +} + +static void sChanged(struct colorDialog *c) +{ + double s; + + s = editDouble(c->editS); + if (s < 0 || s > 1) + return; + c->s = s; + updateDialog(c, c->editS); +} + +static void vChanged(struct colorDialog *c) +{ + double v; + + v = editDouble(c->editV); + if (v < 0 || v > 1) + return; + c->v = v; + updateDialog(c, c->editV); +} + +static void rDoubleChanged(struct colorDialog *c) +{ + double r, g, b; + + hsv2RGB(c->h, c->s, c->v, &r, &g, &b); + r = editDouble(c->editRDouble); + if (r < 0 || r > 1) + return; + rgb2HSV(r, g, b, &(c->h), &(c->s), &(c->v)); + updateDialog(c, c->editRDouble); +} + +static void gDoubleChanged(struct colorDialog *c) +{ + double r, g, b; + + hsv2RGB(c->h, c->s, c->v, &r, &g, &b); + g = editDouble(c->editGDouble); + if (g < 0 || g > 1) + return; + rgb2HSV(r, g, b, &(c->h), &(c->s), &(c->v)); + updateDialog(c, c->editGDouble); +} + +static void bDoubleChanged(struct colorDialog *c) +{ + double r, g, b; + + hsv2RGB(c->h, c->s, c->v, &r, &g, &b); + b = editDouble(c->editBDouble); + if (b < 0 || b > 1) + return; + rgb2HSV(r, g, b, &(c->h), &(c->s), &(c->v)); + updateDialog(c, c->editBDouble); +} + +static void aDoubleChanged(struct colorDialog *c) +{ + double a; + + a = editDouble(c->editADouble); + if (a < 0 || a > 1) + return; + c->a = a; + updateDialog(c, c->editADouble); +} + +static int editInt(HWND hwnd) +{ + WCHAR *s; + int i; + + s = windowText(hwnd); + i = _wtoi(s); + uiprivFree(s); + return i; +} + +static void rIntChanged(struct colorDialog *c) +{ + double r, g, b; + int i; + + hsv2RGB(c->h, c->s, c->v, &r, &g, &b); + i = editInt(c->editRInt); + if (i < 0 || i > 255) + return; + r = ((double) i) / 255.0; + rgb2HSV(r, g, b, &(c->h), &(c->s), &(c->v)); + updateDialog(c, c->editRInt); +} + +static void gIntChanged(struct colorDialog *c) +{ + double r, g, b; + int i; + + hsv2RGB(c->h, c->s, c->v, &r, &g, &b); + i = editInt(c->editGInt); + if (i < 0 || i > 255) + return; + g = ((double) i) / 255.0; + rgb2HSV(r, g, b, &(c->h), &(c->s), &(c->v)); + updateDialog(c, c->editGInt); +} + +static void bIntChanged(struct colorDialog *c) +{ + double r, g, b; + int i; + + hsv2RGB(c->h, c->s, c->v, &r, &g, &b); + i = editInt(c->editBInt); + if (i < 0 || i > 255) + return; + b = ((double) i) / 255.0; + rgb2HSV(r, g, b, &(c->h), &(c->s), &(c->v)); + updateDialog(c, c->editBInt); +} + +static void aIntChanged(struct colorDialog *c) +{ + int a; + + a = editInt(c->editAInt); + if (a < 0 || a > 255) + return; + c->a = ((double) a) / 255; + updateDialog(c, c->editAInt); +} + +static void hexChanged(struct colorDialog *c) +{ + WCHAR *buf; + double r, g, b, a; + BOOL is; + + buf = windowText(c->editHex); + is = hex2RGBA(buf, &r, &g, &b, &a); + uiprivFree(buf); + if (!is) + return; + rgb2HSV(r, g, b, &(c->h), &(c->s), &(c->v)); + c->a = a; + updateDialog(c, c->editHex); +} + +// TODO change fontdialog to use this +// note that if we make this const, we get lots of weird compiler errors +static std::map changed = { + { rcH, hChanged }, + { rcS, sChanged }, + { rcV, vChanged }, + { rcRDouble, rDoubleChanged }, + { rcGDouble, gDoubleChanged }, + { rcBDouble, bDoubleChanged }, + { rcADouble, aDoubleChanged }, + { rcRInt, rIntChanged }, + { rcGInt, gIntChanged }, + { rcBInt, bIntChanged }, + { rcAInt, aIntChanged }, + { rcHex, hexChanged }, +}; + +static INT_PTR CALLBACK colorDialogDlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + struct colorDialog *c; + + c = (struct colorDialog *) GetWindowLongPtrW(hwnd, DWLP_USER); + if (c == NULL) { + if (uMsg == WM_INITDIALOG) { + c = beginColorDialog(hwnd, lParam); + SetWindowLongPtrW(hwnd, DWLP_USER, (LONG_PTR) c); + return TRUE; + } + return FALSE; + } + + switch (uMsg) { + case WM_COMMAND: + SetWindowLongPtrW(c->hwnd, DWLP_MSGRESULT, 0); // just in case + switch (LOWORD(wParam)) { + case IDOK: + case IDCANCEL: + if (HIWORD(wParam) != BN_CLICKED) + return FALSE; + tryFinishDialog(c, wParam); + return TRUE; + case rcH: + case rcS: + case rcV: + case rcRDouble: + case rcGDouble: + case rcBDouble: + case rcADouble: + case rcRInt: + case rcGInt: + case rcBInt: + case rcAInt: + case rcHex: + if (HIWORD(wParam) != EN_CHANGE) + return FALSE; + if (c->updating) // prevent infinite recursion during an update + return FALSE; + (*(changed[LOWORD(wParam)]))(c); + return TRUE; + } + return FALSE; + } + return FALSE; +} + +// because Windows doesn't really support resources in static libraries, we have to embed this directly; oh well +/* +rcColorDialog DIALOGEX 13, 54, 344, 209 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_3DLOOK +CAPTION "Color" +FONT 9, "Segoe UI" +BEGIN + // this size should be big enough to get at least 256x256 on font sizes >= 8 pt + CTEXT "AaBbYyZz", rcColorSVChooser, 7, 7, 195, 195, SS_NOPREFIX | SS_BLACKRECT + + // width is the suggested slider height since this is vertical + CTEXT "AaBbYyZz", rcColorHSlider, 206, 7, 15, 195, SS_NOPREFIX | SS_BLACKRECT + + LTEXT "Preview:", -1, 230, 7, 107, 9, SS_NOPREFIX + CTEXT "AaBbYyZz", rcPreview, 230, 16, 107, 20, SS_NOPREFIX | SS_BLACKRECT + + LTEXT "Opacity:", -1, 230, 45, 107, 9, SS_NOPREFIX + CTEXT "AaBbYyZz", rcOpacitySlider, 230, 54, 107, 15, SS_NOPREFIX | SS_BLACKRECT + + LTEXT "&H:", rcHLabel, 230, 81, 8, 8 + EDITTEXT rcH, 238, 78, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE + LTEXT "&S:", rcSLabel, 230, 95, 8, 8 + EDITTEXT rcS, 238, 92, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE + LTEXT "&V:", rcVLabel, 230, 109, 8, 8 + EDITTEXT rcV, 238, 106, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE + + LTEXT "&R:", rcRLabel, 277, 81, 8, 8 + EDITTEXT rcRDouble, 285, 78, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE + EDITTEXT rcRInt, 315, 78, 20, 14, ES_LEFT | ES_AUTOHSCROLL | ES_NUMBER | WS_TABSTOP, WS_EX_CLIENTEDGE + LTEXT "&G:", rcGLabel, 277, 95, 8, 8 + EDITTEXT rcGDouble, 285, 92, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE + EDITTEXT rcGInt, 315, 92, 20, 14, ES_LEFT | ES_AUTOHSCROLL | ES_NUMBER | WS_TABSTOP, WS_EX_CLIENTEDGE + LTEXT "&B:", rcBLabel, 277, 109, 8, 8 + EDITTEXT rcBDouble, 285, 106, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE + EDITTEXT rcBInt, 315, 106, 20, 14, ES_LEFT | ES_AUTOHSCROLL | ES_NUMBER | WS_TABSTOP, WS_EX_CLIENTEDGE + LTEXT "&A:", rcALabel, 277, 123, 8, 8 + EDITTEXT rcADouble, 285, 120, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE + EDITTEXT rcAInt, 315, 120, 20, 14, ES_LEFT | ES_AUTOHSCROLL | ES_NUMBER | WS_TABSTOP, WS_EX_CLIENTEDGE + + LTEXT "He&x:", rcHexLabel, 269, 146, 16, 8 + EDITTEXT rcHex, 285, 143, 50, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE + + DEFPUSHBUTTON "OK", IDOK, 243, 188, 45, 14, WS_GROUP + PUSHBUTTON "Cancel", IDCANCEL, 292, 188, 45, 14, WS_GROUP +END +*/ +static const uint8_t data_rcColorDialog[] = { + 0x01, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xC4, 0x00, 0xC8, 0x80, + 0x1C, 0x00, 0x0D, 0x00, 0x36, 0x00, 0x58, 0x01, + 0xD1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x00, + 0x6F, 0x00, 0x6C, 0x00, 0x6F, 0x00, 0x72, 0x00, + 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x53, 0x00, 0x65, 0x00, 0x67, 0x00, 0x6F, 0x00, + 0x65, 0x00, 0x20, 0x00, 0x55, 0x00, 0x49, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x85, 0x00, 0x02, 0x50, + 0x07, 0x00, 0x07, 0x00, 0xC3, 0x00, 0xC3, 0x00, + 0x4C, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x42, 0x00, 0x62, 0x00, + 0x59, 0x00, 0x79, 0x00, 0x5A, 0x00, 0x7A, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x85, 0x00, 0x02, 0x50, + 0xCE, 0x00, 0x07, 0x00, 0x0F, 0x00, 0xC3, 0x00, + 0x4D, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x42, 0x00, 0x62, 0x00, + 0x59, 0x00, 0x79, 0x00, 0x5A, 0x00, 0x7A, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x02, 0x50, + 0xE6, 0x00, 0x07, 0x00, 0x6B, 0x00, 0x09, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x82, 0x00, + 0x50, 0x00, 0x72, 0x00, 0x65, 0x00, 0x76, 0x00, + 0x69, 0x00, 0x65, 0x00, 0x77, 0x00, 0x3A, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x85, 0x00, 0x02, 0x50, + 0xE6, 0x00, 0x10, 0x00, 0x6B, 0x00, 0x14, 0x00, + 0x4E, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x42, 0x00, 0x62, 0x00, + 0x59, 0x00, 0x79, 0x00, 0x5A, 0x00, 0x7A, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x02, 0x50, + 0xE6, 0x00, 0x2D, 0x00, 0x6B, 0x00, 0x09, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x82, 0x00, + 0x4F, 0x00, 0x70, 0x00, 0x61, 0x00, 0x63, 0x00, + 0x69, 0x00, 0x74, 0x00, 0x79, 0x00, 0x3A, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x85, 0x00, 0x02, 0x50, + 0xE6, 0x00, 0x36, 0x00, 0x6B, 0x00, 0x0F, 0x00, + 0x4F, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x42, 0x00, 0x62, 0x00, + 0x59, 0x00, 0x79, 0x00, 0x5A, 0x00, 0x7A, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50, + 0xE6, 0x00, 0x51, 0x00, 0x08, 0x00, 0x08, 0x00, + 0x5C, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00, + 0x26, 0x00, 0x48, 0x00, 0x3A, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x80, 0x00, 0x81, 0x50, + 0xEE, 0x00, 0x4E, 0x00, 0x1E, 0x00, 0x0E, 0x00, + 0x50, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x81, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50, + 0xE6, 0x00, 0x5F, 0x00, 0x08, 0x00, 0x08, 0x00, + 0x5D, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00, + 0x26, 0x00, 0x53, 0x00, 0x3A, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x80, 0x00, 0x81, 0x50, + 0xEE, 0x00, 0x5C, 0x00, 0x1E, 0x00, 0x0E, 0x00, + 0x51, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x81, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50, + 0xE6, 0x00, 0x6D, 0x00, 0x08, 0x00, 0x08, 0x00, + 0x5E, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00, + 0x26, 0x00, 0x56, 0x00, 0x3A, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x80, 0x00, 0x81, 0x50, + 0xEE, 0x00, 0x6A, 0x00, 0x1E, 0x00, 0x0E, 0x00, + 0x52, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x81, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50, + 0x15, 0x01, 0x51, 0x00, 0x08, 0x00, 0x08, 0x00, + 0x5F, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00, + 0x26, 0x00, 0x52, 0x00, 0x3A, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x80, 0x00, 0x81, 0x50, + 0x1D, 0x01, 0x4E, 0x00, 0x1E, 0x00, 0x0E, 0x00, + 0x53, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x81, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x80, 0x20, 0x81, 0x50, + 0x3B, 0x01, 0x4E, 0x00, 0x14, 0x00, 0x0E, 0x00, + 0x54, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x81, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50, + 0x15, 0x01, 0x5F, 0x00, 0x08, 0x00, 0x08, 0x00, + 0x60, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00, + 0x26, 0x00, 0x47, 0x00, 0x3A, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x80, 0x00, 0x81, 0x50, + 0x1D, 0x01, 0x5C, 0x00, 0x1E, 0x00, 0x0E, 0x00, + 0x55, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x81, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x80, 0x20, 0x81, 0x50, + 0x3B, 0x01, 0x5C, 0x00, 0x14, 0x00, 0x0E, 0x00, + 0x56, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x81, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50, + 0x15, 0x01, 0x6D, 0x00, 0x08, 0x00, 0x08, 0x00, + 0x61, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00, + 0x26, 0x00, 0x42, 0x00, 0x3A, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x80, 0x00, 0x81, 0x50, + 0x1D, 0x01, 0x6A, 0x00, 0x1E, 0x00, 0x0E, 0x00, + 0x57, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x81, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x80, 0x20, 0x81, 0x50, + 0x3B, 0x01, 0x6A, 0x00, 0x14, 0x00, 0x0E, 0x00, + 0x58, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x81, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50, + 0x15, 0x01, 0x7B, 0x00, 0x08, 0x00, 0x08, 0x00, + 0x62, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00, + 0x26, 0x00, 0x41, 0x00, 0x3A, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x80, 0x00, 0x81, 0x50, + 0x1D, 0x01, 0x78, 0x00, 0x1E, 0x00, 0x0E, 0x00, + 0x59, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x81, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x80, 0x20, 0x81, 0x50, + 0x3B, 0x01, 0x78, 0x00, 0x14, 0x00, 0x0E, 0x00, + 0x5A, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x81, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50, + 0x0D, 0x01, 0x92, 0x00, 0x10, 0x00, 0x08, 0x00, + 0x63, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00, + 0x48, 0x00, 0x65, 0x00, 0x26, 0x00, 0x78, 0x00, + 0x3A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, + 0x80, 0x00, 0x81, 0x50, 0x1D, 0x01, 0x8F, 0x00, + 0x32, 0x00, 0x0E, 0x00, 0x5B, 0x04, 0x00, 0x00, + 0xFF, 0xFF, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x03, 0x50, 0xF3, 0x00, 0xBC, 0x00, + 0x2D, 0x00, 0x0E, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x80, 0x00, 0x4F, 0x00, 0x4B, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x50, + 0x24, 0x01, 0xBC, 0x00, 0x2D, 0x00, 0x0E, 0x00, + 0x02, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, + 0x43, 0x00, 0x61, 0x00, 0x6E, 0x00, 0x63, 0x00, + 0x65, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, +}; +static_assert(ARRAYSIZE(data_rcColorDialog) == 1144, "wrong size for resource rcColorDialog"); + +BOOL showColorDialog(HWND parent, struct colorDialogRGBA *c) +{ + switch (DialogBoxIndirectParamW(hInstance, (const DLGTEMPLATE *) data_rcColorDialog, parent, colorDialogDlgProc, (LPARAM) c)) { + case 1: // cancel + return FALSE; + case 2: // ok + // make the compiler happy by putting the return after the switch + break; + default: + logLastError(L"error running color dialog"); + } + return TRUE; +} diff --git a/windows/combobox.cpp b/windows/combobox.cpp index ff5be8a1..50f49dd7 100644 --- a/windows/combobox.cpp +++ b/windows/combobox.cpp @@ -1,9 +1,6 @@ // 20 may 2015 #include "uipriv_windows.hpp" -// TODO -// - is there extra space on the bottom? - // we as Common Controls 6 users don't need to worry about the height of comboboxes; see http://blogs.msdn.com/b/oldnewthing/archive/2006/03/10/548537.aspx struct uiCombobox { @@ -13,7 +10,6 @@ struct uiCombobox { void *onSelectedData; }; -// TODO: NOT triggered on entering text static BOOL onWM_COMMAND(uiControl *cc, HWND hwnd, WORD code, LRESULT *lResult) { uiCombobox *c = uiCombobox(cc); @@ -37,10 +33,10 @@ void uiComboboxDestroy(uiControl *cc) uiWindowsControlAllDefaultsExceptDestroy(uiCombobox) // from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing -#define comboboxWidth 107 /* this is actually the shorter progress bar width, but Microsoft only indicates as wide as necessary; TODO */ -#define comboboxHeight 14 +#define comboboxWidth 107 /* this is actually the shorter progress bar width, but Microsoft only indicates as wide as necessary; LONGTERM */ +#define comboboxHeight 14 /* LONGTERM: is this too high? */ -static void uiComboboxMinimumSize(uiWindowsControl *cc, intmax_t *width, intmax_t *height) +static void uiComboboxMinimumSize(uiWindowsControl *cc, int *width, int *height) { uiCombobox *c = uiCombobox(cc); uiWindowsSizing sizing; @@ -70,20 +66,20 @@ void uiComboboxAppend(uiCombobox *c, const char *text) logLastError(L"error appending item to uiCombobox"); else if (res == (LRESULT) CB_ERRSPACE) logLastError(L"memory exhausted appending item to uiCombobox"); - uiFree(wtext); + uiprivFree(wtext); } -intmax_t uiComboboxSelected(uiCombobox *c) +int uiComboboxSelected(uiCombobox *c) { LRESULT n; n = SendMessage(c->hwnd, CB_GETCURSEL, 0, 0); if (n == (LRESULT) CB_ERR) return -1; - return (intmax_t) n; + return n; } -void uiComboboxSetSelected(uiCombobox *c, intmax_t n) +void uiComboboxSetSelected(uiCombobox *c, int n) { // TODO error check SendMessageW(c->hwnd, CB_SETCURSEL, (WPARAM) n, 0); @@ -95,7 +91,7 @@ void uiComboboxOnSelected(uiCombobox *c, void (*f)(uiCombobox *c, void *data), v c->onSelectedData = data; } -static uiCombobox *finishNewCombobox(DWORD style) +uiCombobox *uiNewCombobox(void) { uiCombobox *c; @@ -103,7 +99,7 @@ static uiCombobox *finishNewCombobox(DWORD style) c->hwnd = uiWindowsEnsureCreateControlHWND(WS_EX_CLIENTEDGE, L"combobox", L"", - style | WS_TABSTOP, + CBS_DROPDOWNLIST | WS_TABSTOP, hInstance, NULL, TRUE); @@ -112,13 +108,3 @@ static uiCombobox *finishNewCombobox(DWORD style) return c; } - -uiCombobox *uiNewCombobox(void) -{ - return finishNewCombobox(CBS_DROPDOWNLIST); -} - -uiCombobox *uiNewEditableCombobox(void) -{ - return finishNewCombobox(CBS_DROPDOWN); -} diff --git a/windows/compilerver.hpp b/windows/compilerver.hpp index 535b5138..c902ec20 100644 --- a/windows/compilerver.hpp +++ b/windows/compilerver.hpp @@ -8,9 +8,88 @@ #endif #endif -// MinGW -#ifdef __MINGW32__ -#error At present, MinGW is not supported; see README.md for details. -#endif +// LONGTERM MinGW // other compilers can be added here as necessary + +/* TODO this should not be necessary, but I don't know + +here's @bcampbell's original comment: +// sanity check - make sure wchar_t is 16 bits (the assumption on windows) +// (MinGW-w64 gcc does seem to define a 16bit wchar_t, but you never know. Other windows gcc ports might not) + +here's what I got when I tried to investigate on irc.oftc.net/#mingw-w64: +{ +[08:45:20] andlabs, the c++ standard requires `wchar_t` to be a distinct type. On Windows you can simply `static_assert(sizeof(wchar_t) == sizeof(unsigned short) && alignof(wchar_t) == alignof(unsigned short), "");` then reinterpret_cast those pointers. +[09:22:16] lh_mouse: yes; that was the point of my question =P but whether that static_assert is always true is another question I have, because again, windows embeds the idea of wchar_t being UTF-16 throughout the API, but when I went to look, I found that the clang developers had a very heated debate about it :S +[09:22:28] and I couldn't find any concrete information other than the msvc docs +[09:23:04] Since Windows 2000 the NT kernel uses UTF-16. +[09:23:50] If you don't care about Windows 9x you can just pretend non-UTF-16 APIs didn't exist. +[09:24:04] that's not what I meant +[09:24:06] Actually long long long ago Windows used UCS2. +[09:24:15] I meant whether sizeof(wchar_t) must necessarily equal sizeof(uint16_t) +[09:24:18] and likewise for alignof +[09:24:27] for all windows compilers +[09:24:29] anyway afk +[09:24:31] Yes. That is what the ABI says. +[09:24:40] is there a source for that I can point at other people +[09:24:45] the ABI != on Windows +[09:26:00] okay I really need to afk now but I was about to ask what you meant +[09:26:06] and by source I meant URL +[09:49:18] andlabs: Sent 17 minutes ago: Here is what Microsoft people describe `wchar_t`: https://docs.microsoft.com/en-us/cpp/cpp/char-wchar-t-char16-t-char32-t +[09:49:19] andlabs: Sent 17 minutes ago: It is quite guaranteed: 'In the Microsoft compiler, it represents a 16-bit wide character used to store Unicode encoded as UTF-16LE, the native character type on Windows operating systems.' +[09:50:08] andlabs, If you build for cygwin then `wchar_t` is probably `int`, just like what it is on Linux. +[09:51:00] yes but that's still a compiler-specific reference; I still don't know hwere Microsoft keeps its ABI documentation, and I'm still wondering what you mean by "the ABI != on Windows" with regards to establishing that guarantee +[09:52:13] This is already the ABI documentation: https://docs.microsoft.com/en-us/cpp/cpp/char-wchar-t-char16-t-char32-t +[09:52:15] Title: char, wchar_t, char16_t, char32_t | Microsoft Docs (at docs.microsoft.com) +[09:52:19] It describes C++ types, +[09:53:09] oh, ok +[09:54:47] I assume by the != statement you mean code that doesn't make any windows API calls can theoretically be compiled any which way, right +[09:55:05] yes. think about MSYS and Cygwin. +[09:55:21] They have 8-byte `long` and 4-byte `wchar_t`. +[09:57:37] right, except the code I'm trying to compile does use the Windows API, so that wouldn't apply to me +[09:57:43] I assume +[09:57:53] it wouldn't. +[09:59:12] On Windows it is sometimes necessary to assume specific ABI definition. For example, when a callback function returning a `DWORD` is to be declared in a header, in order to prevent `#include`'ing windows.h, you can just write `unsigned long` there. +[09:59:32] This is guaranteed to work on Windows. Linux will say otherwise. +[10:00:41] We all know `#include ` in a public header lets the genie out of the bottle, doesn't it? +[10:04:24] the zombie of win32_lean_and_mean lives forever +[10:04:53] of course now we have stdint.h and cstdint (which took longer because lolC++03) which helps stuff +[10:06:19] no `uint32_t` is `unsigned int` while `DWORD` is `unsigned long` hence they are incompatible. :( +[10:06:39] in what sense +[10:06:55] a `unsigned int *` cannot be converted to `unsigned long *` implicitly. +[10:07:41] the C standard says they are distinct pointer types and are not compatible, although `unsigned int` and `unsigned long` might have the same bit representation and alignment requirement. +[10:08:04] oh +[10:08:22] casting would indeed make code compile, but I tend to keep away from them unless necessary. +[10:08:24] wel yeah, but we haven't left the world of windows-specific code yet +[10:08:38] my point was more we don't need to use names like DWORD anymore +[10:08:51] of course it's easier to do so +[10:09:04] just use `uint32_t`. +[10:09:44] I just tested GCC 8 today and noticed they had added a warning for casting between incompatible function pointer types. +[10:10:10] So casting from `unsigned (*)(void)` to `unsigned long (*)(void)` now results in a warning. +[10:10:43] With `-Werror` it is a hard error. This can be worked around by casting the operand to an intermediate result of `intptr_t`. +[10:10:59] ... not so serious. +[10:11:42] oh good I wonder what else will break :D +[10:12:19] though the docs for dlsym() tell you what you should do instead for systems that use libdl (cast the address of your destination variable to void**) +[10:13:23] POSIX requires casting from `void *` to function pointers to work explicitly (see the docs for `dlsym()`). I am not sure what GCC people think about it. +[10:13:45] yes that's what I just said =P it avoids the problem entirely +[10:13:49] C++ says this is 'conditionally supported' and it is not a warning or error there. +[10:13:50] dlsym already returns void* +[10:14:13] something like dlsym would require an ABI guarantee on the matter anyway +[10:14:16] by definition +[10:14:32] Casting is evil. Double casting is double evil. So I keep myself away from them. +[10:15:03] sadly this is C (and C++) =P +[10:15:25] for `*-w64-mingw32` targets it is safe to cast between `unsigned short`, `wchar_t` and `char16_t`. +[10:15:33] as well as pointers to them. +[10:16:30] you just need to `static_assert` it, so something naughty will not compile. +[12:36:14] actually I didn't notice that last message until just now +[12:36:23] I was asking because I was sitting here thinking such a static_assert was unnecessary +} +clang debate: http://clang-developers.42468.n3.nabble.com/Is-that-getting-wchar-t-to-be-32bit-on-win32-a-good-idea-for-compatible-with-Unix-world-by-implement-td4045412.html + +so I'm not sure what is correct, but I do need to find out +*/ +#include +#if WCHAR_MAX > 0xFFFF +#error unexpected: wchar_t larger than 16-bit on a Windows ABI build; contact andlabs with your build setup information +#endif diff --git a/windows/container.cpp b/windows/container.cpp index 31c93a23..9ec1e280 100644 --- a/windows/container.cpp +++ b/windows/container.cpp @@ -6,6 +6,8 @@ // - uiRadioButtons // - uiSpinbox // - uiTab +// - uiForm +// - uiGrid struct containerInit { uiWindowsControl *c; @@ -23,7 +25,7 @@ static LRESULT CALLBACK containerWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LP struct containerInit *init; uiWindowsControl *c; void (*onResize)(uiWindowsControl *); - intmax_t minwid, minht; + int minwid, minht; LRESULT lResult; if (handleParentMessages(hwnd, uMsg, wParam, lParam, &lResult) != FALSE) diff --git a/windows/control.cpp b/windows/control.cpp index 2afbbc27..ce953cf9 100644 --- a/windows/control.cpp +++ b/windows/control.cpp @@ -11,7 +11,7 @@ void uiWindowsControlSetParentHWND(uiWindowsControl *c, HWND parent) (*(c->SetParentHWND))(c, parent); } -void uiWindowsControlMinimumSize(uiWindowsControl *c, intmax_t *width, intmax_t *height) +void uiWindowsControlMinimumSize(uiWindowsControl *c, int *width, int *height) { (*(c->MinimumSize))(c, width, height); } @@ -21,6 +21,7 @@ void uiWindowsControlMinimumSizeChanged(uiWindowsControl *c) (*(c->MinimumSizeChanged))(c); } +// TODO get rid of this void uiWindowsControlLayoutRect(uiWindowsControl *c, RECT *r) { (*(c->LayoutRect))(c, r); @@ -31,6 +32,11 @@ void uiWindowsControlAssignControlIDZOrder(uiWindowsControl *c, LONG_PTR *contro (*(c->AssignControlIDZOrder))(c, controlID, insertAfter); } +void uiWindowsControlChildVisibilityChanged(uiWindowsControl *c) +{ + (*(c->ChildVisibilityChanged))(c); +} + HWND uiWindowsEnsureCreateControlHWND(DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle, HINSTANCE hInstance, LPVOID lpParam, BOOL useStandardControlFont) { HWND hwnd; @@ -87,7 +93,7 @@ void uiWindowsControlAssignSoleControlIDZOrder(uiWindowsControl *c) BOOL uiWindowsControlTooSmall(uiWindowsControl *c) { RECT r; - intmax_t width, height; + int width, height; uiWindowsControlLayoutRect(c, &r); uiWindowsControlMinimumSize(c, &width, &height); @@ -106,3 +112,10 @@ void uiWindowsControlContinueMinimumSizeChanged(uiWindowsControl *c) if (parent != NULL) uiWindowsControlMinimumSizeChanged(uiWindowsControl(parent)); } + +// TODO rename this nad the OS X this and hugging ones to NotifyChild +void uiWindowsControlNotifyVisibilityChanged(uiWindowsControl *c) +{ + // TODO we really need to figure this out; the duplication is a mess + uiWindowsControlContinueMinimumSizeChanged(c); +} diff --git a/windows/d2dscratch.cpp b/windows/d2dscratch.cpp index 06f49a41..6dc2ba5f 100644 --- a/windows/d2dscratch.cpp +++ b/windows/d2dscratch.cpp @@ -6,6 +6,10 @@ // - wParam - 0 // - lParam - ID2D1RenderTarget * // - lResult - 0 +// You can optionally also handle msgD2DScratchLButtonDown, which is sent when the left mouse button is either pressed for the first time or held while the mouse is moving. +// - wParam - position in DIPs, as D2D1_POINT_2F * +// - lParam - size of render target in DIPs, as D2D1_SIZE_F * +// - lResult - 0 // Other messages can also be handled here. // TODO allow resize @@ -26,6 +30,7 @@ static HRESULT d2dScratchDoPaint(HWND hwnd, ID2D1RenderTarget *rt) bgcolor.r = ((float) GetRValue(bgcolorref)) / 255.0; // due to utter apathy on Microsoft's part, GetGValue() does not work with MSVC's Run-Time Error Checks // it has not worked since 2008 and they have *never* fixed it + // TODO now that -RTCc has just been deprecated entirely, should we switch back? bgcolor.g = ((float) ((BYTE) ((bgcolorref & 0xFF00) >> 8))) / 255.0; bgcolor.b = ((float) GetBValue(bgcolorref)) / 255.0; bgcolor.a = 1.0; @@ -36,10 +41,32 @@ static HRESULT d2dScratchDoPaint(HWND hwnd, ID2D1RenderTarget *rt) return rt->EndDraw(NULL, NULL); } +static void d2dScratchDoLButtonDown(HWND hwnd, ID2D1RenderTarget *rt, LPARAM lParam) +{ + double xpix, ypix; + FLOAT dpix, dpiy; + D2D1_POINT_2F pos; + D2D1_SIZE_F size; + + xpix = (double) GET_X_LPARAM(lParam); + ypix = (double) GET_Y_LPARAM(lParam); + // these are in pixels; we need points + // TODO separate the function from areautil.cpp? + rt->GetDpi(&dpix, &dpiy); + pos.x = (xpix * 96) / dpix; + pos.y = (ypix * 96) / dpiy; + + size = realGetSize(rt); + + SendMessageW(hwnd, msgD2DScratchLButtonDown, (WPARAM) (&pos), (LPARAM) (&size)); +} + static LRESULT CALLBACK d2dScratchWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { LONG_PTR init; ID2D1HwndRenderTarget *rt; + ID2D1DCRenderTarget *dcrt; + RECT client; HRESULT hr; init = GetWindowLongPtrW(hwnd, 0); @@ -80,8 +107,21 @@ static LRESULT CALLBACK d2dScratchWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, L } return 0; case WM_PRINTCLIENT: - // TODO - break; + uiWindowsEnsureGetClientRect(hwnd, &client); + dcrt = makeHDCRenderTarget((HDC) wParam, &client); + hr = d2dScratchDoPaint(hwnd, dcrt); + if (hr != S_OK) + logHRESULT(L"error printing D2D scratch window client area", hr); + dcrt->Release(); + return 0; + case WM_LBUTTONDOWN: + d2dScratchDoLButtonDown(hwnd, rt, lParam); + return 0; + case WM_MOUSEMOVE: + // also send LButtonDowns when dragging + if ((wParam & MK_LBUTTON) != 0) + d2dScratchDoLButtonDown(hwnd, rt, lParam); + return 0; } return DefWindowProcW(hwnd, uMsg, wParam, lParam); } diff --git a/windows/datetimepicker.cpp b/windows/datetimepicker.cpp index b81cfe6f..32546cf8 100644 --- a/windows/datetimepicker.cpp +++ b/windows/datetimepicker.cpp @@ -4,6 +4,8 @@ struct uiDateTimePicker { uiWindowsControl c; HWND hwnd; + void (*onChanged)(uiDateTimePicker *, void *); + void *onChangedData; }; // utility functions @@ -19,7 +21,7 @@ static WCHAR *expandYear(WCHAR *dts, int n) int ny = 0; // allocate more than we need to be safe - out = (WCHAR *) uiAlloc((n * 3) * sizeof (WCHAR), "WCHAR[]"); + out = (WCHAR *) uiprivAlloc((n * 3) * sizeof (WCHAR), "WCHAR[]"); q = out; for (p = dts; *p != L'\0'; p++) { // first, if the current character is a y, increment the number of consecutive ys @@ -44,7 +46,7 @@ static WCHAR *expandYear(WCHAR *dts, int n) if (*p == L'\'') break; if (*p == L'\0') - complain("unterminated quote in system-provided locale date string in expandYear()"); + uiprivImplBug("unterminated quote in system-provided locale date string in expandYear()"); *q++ = *p; } // and fall through to copy the closing quote @@ -73,17 +75,17 @@ static void setDateTimeFormat(HWND hwnd) ndate = GLI(LOCALE_SSHORTDATE, NULL, 0); if (ndate == 0) logLastError(L"error getting date string length"); - date = (WCHAR *) uiAlloc(ndate * sizeof (WCHAR), "WCHAR[]"); + date = (WCHAR *) uiprivAlloc(ndate * sizeof (WCHAR), "WCHAR[]"); if (GLI(LOCALE_SSHORTDATE, date, ndate) == 0) logLastError(L"error geting date string"); unexpandedDate = date; // so we can free it date = expandYear(unexpandedDate, ndate); - uiFree(unexpandedDate); + uiprivFree(unexpandedDate); ntime = GLI(LOCALE_STIMEFORMAT, NULL, 0); if (ndate == 0) logLastError(L"error getting time string length"); - time = (WCHAR *) uiAlloc(ntime * sizeof (WCHAR), "WCHAR[]"); + time = (WCHAR *) uiprivAlloc(ntime * sizeof (WCHAR), "WCHAR[]"); if (GLI(LOCALE_STIMEFORMAT, time, ntime) == 0) logLastError(L"error geting time string"); @@ -91,9 +93,9 @@ static void setDateTimeFormat(HWND hwnd) if (SendMessageW(hwnd, DTM_SETFORMAT, 0, (LPARAM) datetime) == 0) logLastError(L"error applying format string to date/time picker"); - uiFree(datetime); - uiFree(time); - uiFree(date); + uiprivFree(datetime); + uiprivFree(time); + uiprivFree(date); } // control implementation @@ -103,6 +105,7 @@ static void uiDateTimePickerDestroy(uiControl *c) uiDateTimePicker *d = uiDateTimePicker(c); uiWindowsUnregisterReceiveWM_WININICHANGE(d->hwnd); + uiWindowsUnregisterWM_NOTIFYHandler(d->hwnd); uiWindowsEnsureDestroyWindow(d->hwnd); uiFreeControl(uiControl(d)); } @@ -113,7 +116,7 @@ uiWindowsControlAllDefaultsExceptDestroy(uiDateTimePicker) // from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing #define entryHeight 14 -static void uiDateTimePickerMinimumSize(uiWindowsControl *c, intmax_t *width, intmax_t *height) +static void uiDateTimePickerMinimumSize(uiWindowsControl *c, int *width, int *height) { uiDateTimePicker *d = uiDateTimePicker(c); SIZE s; @@ -131,6 +134,71 @@ static void uiDateTimePickerMinimumSize(uiWindowsControl *c, intmax_t *width, in *height = y; } +static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) +{ + uiDateTimePicker *d = uiDateTimePicker(c); + + if (nmhdr->code != DTN_DATETIMECHANGE) + return FALSE; + (*(d->onChanged))(d, d->onChangedData); + *lResult = 0; + return TRUE; +} + +static void fromSystemTime(SYSTEMTIME *systime, struct tm *time) +{ + ZeroMemory(time, sizeof (struct tm)); + time->tm_sec = systime->wSecond; + time->tm_min = systime->wMinute; + time->tm_hour = systime->wHour; + time->tm_mday = systime->wDay; + time->tm_mon = systime->wMonth - 1; + time->tm_year = systime->wYear - 1900; + time->tm_wday = systime->wDayOfWeek; + time->tm_isdst = -1; +} + +static void toSystemTime(const struct tm *time, SYSTEMTIME *systime) +{ + ZeroMemory(systime, sizeof (SYSTEMTIME)); + systime->wYear = time->tm_year + 1900; + systime->wMonth = time->tm_mon + 1; + systime->wDayOfWeek = time->tm_wday; + systime->wDay = time->tm_mday; + systime->wHour = time->tm_hour; + systime->wMinute = time->tm_min; + systime->wSecond = time->tm_sec; +} + +static void defaultOnChanged(uiDateTimePicker *d, void *data) +{ + // do nothing +} + +void uiDateTimePickerTime(uiDateTimePicker *d, struct tm *time) +{ + SYSTEMTIME systime; + + if (SendMessageW(d->hwnd, DTM_GETSYSTEMTIME, 0, (LPARAM) (&systime)) != GDT_VALID) + logLastError(L"error getting date and time"); + fromSystemTime(&systime, time); +} + +void uiDateTimePickerSetTime(uiDateTimePicker *d, const struct tm *time) +{ + SYSTEMTIME systime; + + toSystemTime(time, &systime); + if (SendMessageW(d->hwnd, DTM_SETSYSTEMTIME, GDT_VALID, (LPARAM) (&systime)) == 0) + logLastError(L"error setting date and time"); +} + +void uiDateTimePickerOnChanged(uiDateTimePicker *d, void (*f)(uiDateTimePicker *, void *), void *data) +{ + d->onChanged = f; + d->onChangedData = data; +} + static uiDateTimePicker *finishNewDateTimePicker(DWORD style) { uiDateTimePicker *d; @@ -147,6 +215,8 @@ static uiDateTimePicker *finishNewDateTimePicker(DWORD style) // for the standard styles, this is in the date-time picker itself // for our date/time mode, we do it in a subclass assigned in uiNewDateTimePicker() uiWindowsRegisterReceiveWM_WININICHANGE(d->hwnd); + uiWindowsRegisterWM_NOTIFYHandler(d->hwnd, onWM_NOTIFY, uiControl(d)); + uiDateTimePickerOnChanged(d, defaultOnChanged, NULL); return d; } diff --git a/windows/debug.cpp b/windows/debug.cpp index 7b0f35f8..bd512743 100644 --- a/windows/debug.cpp +++ b/windows/debug.cpp @@ -1,24 +1,13 @@ // 25 february 2015 #include "uipriv_windows.hpp" -// TODO -void complain(const char *format, ...) -{ - OutputDebugStringA(format); - DebugBreak(); - abort(); -} +// LONGTERM disable logging and stopping on no-debug builds -// TODO disable logging and stopping on no-debug builds - -// TODO are the newlines needed? static void printDebug(const WCHAR *msg) { OutputDebugStringW(msg); } -#define debugfmt L"%s:%s:%s()" - HRESULT _logLastError(debugargs, const WCHAR *s) { DWORD le; @@ -31,21 +20,13 @@ HRESULT _logLastError(debugargs, const WCHAR *s) useFormatted = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, le, 0, (LPWSTR) (&formatted), 0, NULL) != 0; if (!useFormatted) formatted = L"\n"; - msg = debugstrf(L"[libui] " debugfmt L" %s: GetLastError() == %I32u %s", + msg = strf(L"[libui] %s:%s:%s() %s: GetLastError() == %I32u %s", file, line, func, s, le, formatted); if (useFormatted) LocalFree(formatted); // ignore error - if (msg == NULL) { - printDebug(L"[libui] (debugstrf() failed; printing raw) "); - printDebug(file); - printDebug(func); - printDebug(s); - printDebug(L"\n"); - } else { - printDebug(msg); - uiFree(msg); - } + printDebug(msg); + uiprivFree(msg); DebugBreak(); SetLastError(le); @@ -59,7 +40,6 @@ HRESULT _logLastError(debugargs, const WCHAR *s) HRESULT _logHRESULT(debugargs, const WCHAR *s, HRESULT hr) { - DWORD le; WCHAR *msg; WCHAR *formatted; BOOL useFormatted; @@ -67,65 +47,38 @@ HRESULT _logHRESULT(debugargs, const WCHAR *s, HRESULT hr) useFormatted = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, hr, 0, (LPWSTR) (&formatted), 0, NULL) != 0; if (!useFormatted) formatted = L"\n"; - msg = debugstrf(L"[libui] " debugfmt L" %s: HRESULT == 0x%08I32X %s", + msg = strf(L"[libui] %s:%s:%s() %s: HRESULT == 0x%08I32X %s", file, line, func, s, hr, formatted); if (useFormatted) LocalFree(formatted); // ignore error - if (msg == NULL) { - printDebug(L"[libui] (debugstrf() failed; printing raw) "); - printDebug(file); - printDebug(func); - printDebug(s); - printDebug(L"\n"); - } else { - printDebug(msg); - uiFree(msg); - } + printDebug(msg); + uiprivFree(msg); DebugBreak(); return hr; } -#define implbugmsg L"either you have or libui has a bug in a control implementation; if libui does, contact andlabs" - -void _implbug(debugargs, const WCHAR *format, ...) +void uiprivRealBug(const char *file, const char *line, const char *func, const char *prefix, const char *format, va_list ap) { - va_list ap; - WCHAR *formatted; - WCHAR *full; - const WCHAR *onerr; + va_list ap2; + char *msg; + size_t n; + WCHAR *final; - va_start(ap, format); - formatted = debugvstrf(format, ap); - va_end(ap); - if (formatted == NULL) { - onerr = format; - goto bad; - } + va_copy(ap2, ap); + n = _vscprintf(format, ap2); + va_end(ap2); + n++; // terminating '\0' - full = debugstrf(L"[libui] " debugfmt L" " implbugmsg L" — %s\n", - file, line, func, - formatted); - if (full == NULL) { - onerr = formatted; - goto bad; - } + msg = (char *) uiprivAlloc(n * sizeof (char), "char[]"); + // includes terminating '\0' according to example in https://msdn.microsoft.com/en-us/library/xa1a1a6z.aspx + vsprintf_s(msg, n, format, ap); - printDebug(full); - uiFree(full); - uiFree(formatted); - goto after; + final = strf(L"[libui] %hs:%hs:%hs() %hs%hs\n", file, line, func, prefix, msg); + uiprivFree(msg); + printDebug(final); + uiprivFree(final); -bad: - printDebug(L"[libui] (debugstrf() failed; printing raw) "); - printDebug(implbugmsg); - printDebug(file); - printDebug(func); - printDebug(onerr); - printDebug(L"\n"); - -after: DebugBreak(); - abort(); } diff --git a/windows/draw.cpp b/windows/draw.cpp index 6e278fc5..a5e5033a 100644 --- a/windows/draw.cpp +++ b/windows/draw.cpp @@ -63,7 +63,31 @@ ID2D1HwndRenderTarget *makeHWNDRenderTarget(HWND hwnd) &hprops, &rt); if (hr != S_OK) - logHRESULT(L"error creating area HWND render target", hr); + logHRESULT(L"error creating HWND render target", hr); + return rt; +} + +ID2D1DCRenderTarget *makeHDCRenderTarget(HDC dc, RECT *r) +{ + D2D1_RENDER_TARGET_PROPERTIES props; + ID2D1DCRenderTarget *rt; + HRESULT hr; + + ZeroMemory(&props, sizeof (D2D1_RENDER_TARGET_PROPERTIES)); + props.type = D2D1_RENDER_TARGET_TYPE_DEFAULT; + props.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM; + props.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; + props.dpiX = GetDeviceCaps(dc, LOGPIXELSX); + props.dpiY = GetDeviceCaps(dc, LOGPIXELSY); + props.usage = D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE; + props.minLevel = D2D1_FEATURE_LEVEL_DEFAULT; + + hr = d2dfactory->CreateDCRenderTarget(&props, &rt); + if (hr != S_OK) + logHRESULT(L"error creating DC render target", hr); + hr = rt->BindDC(dc, r); + if (hr != S_OK) + logHRESULT(L"error binding DC to DC render target", hr); return rt; } @@ -83,7 +107,7 @@ uiDrawContext *newContext(ID2D1RenderTarget *rt) { uiDrawContext *c; - c = uiNew(uiDrawContext); + c = uiprivNew(uiDrawContext); c->rt = rt; c->states = new std::vector; resetTarget(c->rt); @@ -95,10 +119,10 @@ void freeContext(uiDrawContext *c) if (c->currentClip != NULL) c->currentClip->Release(); if (c->states->size() != 0) - // TODO userbug() - complain("unbalanced save/restore"); + // TODO do this on other platforms + uiprivUserBug("You did not balance uiDrawSave() and uiDrawRestore() calls."); delete c->states; - uiFree(c); + uiprivFree(c); } static ID2D1Brush *makeSolidBrush(uiDrawBrush *b, ID2D1RenderTarget *rt, D2D1_BRUSH_PROPERTIES *props) @@ -128,7 +152,7 @@ static ID2D1GradientStopCollection *mkstops(uiDrawBrush *b, ID2D1RenderTarget *r size_t i; HRESULT hr; - stops = (D2D1_GRADIENT_STOP *) uiAlloc(b->NumStops * sizeof (D2D1_GRADIENT_STOP), "D2D1_GRADIENT_STOP[]"); + stops = (D2D1_GRADIENT_STOP *) uiprivAlloc(b->NumStops * sizeof (D2D1_GRADIENT_STOP), "D2D1_GRADIENT_STOP[]"); for (i = 0; i < b->NumStops; i++) { stops[i].position = b->Stops[i].Pos; stops[i].color.r = b->Stops[i].R; @@ -146,7 +170,7 @@ static ID2D1GradientStopCollection *mkstops(uiDrawBrush *b, ID2D1RenderTarget *r if (hr != S_OK) logHRESULT(L"error creating stop collection", hr); - uiFree(stops); + uiprivFree(stops); return s; } @@ -228,7 +252,9 @@ static ID2D1Brush *makeBrush(uiDrawBrush *b, ID2D1RenderTarget *rt) // TODO } - complain("invalid brush type %d in makeBrush()", b->Type); + // TODO do this on all platforms + uiprivUserBug("Invalid brush type %d given to drawing operation.", b->Type); + // TODO dummy brush? return NULL; // make compiler happy } @@ -339,7 +365,7 @@ void uiDrawStroke(uiDrawContext *c, uiDrawPath *p, uiDrawBrush *b, uiDrawStrokeP // TODO be sure to formally document this if (sp->NumDashes != 0) { dsp.dashStyle = D2D1_DASH_STYLE_CUSTOM; - dashes = (FLOAT *) uiAlloc(sp->NumDashes * sizeof (FLOAT), "FLOAT[]"); + dashes = (FLOAT *) uiprivAlloc(sp->NumDashes * sizeof (FLOAT), "FLOAT[]"); for (i = 0; i < sp->NumDashes; i++) dashes[i] = sp->Dashes[i] / sp->Thickness; } @@ -352,7 +378,7 @@ void uiDrawStroke(uiDrawContext *c, uiDrawPath *p, uiDrawBrush *b, uiDrawStrokeP if (hr != S_OK) logHRESULT(L"error creating stroke style", hr); if (sp->NumDashes != 0) - uiFree(dashes); + uiprivFree(dashes); cliplayer = applyClip(c); c->rt->DrawGeometry( diff --git a/windows/draw.hpp b/windows/draw.hpp index b015791f..c271e4db 100644 --- a/windows/draw.hpp +++ b/windows/draw.hpp @@ -1,5 +1,7 @@ // 5 may 2016 +// TODO resolve overlap between this and the other hpp files (some functions leaked into uipriv_windows.hpp) + // draw.cpp extern ID2D1Factory *d2dfactory; struct uiDrawContext { diff --git a/windows/drawmatrix.cpp b/windows/drawmatrix.cpp index e2f29d1d..4ddc5e9a 100644 --- a/windows/drawmatrix.cpp +++ b/windows/drawmatrix.cpp @@ -22,11 +22,6 @@ static void d2m(D2D1_MATRIX_3X2_F *d, uiDrawMatrix *m) m->M32 = d->_32; } -void uiDrawMatrixSetIdentity(uiDrawMatrix *m) -{ - setIdentity(m); -} - void uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y) { D2D1_MATRIX_3X2_F dm; @@ -48,7 +43,7 @@ void uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x d2m(&dm, m); } -#define r2d(x) (x * (180.0 / M_PI)) +#define r2d(x) (x * (180.0 / uiPi)) void uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount) { @@ -118,5 +113,5 @@ void uiDrawMatrixTransformPoint(uiDrawMatrix *m, double *x, double *y) void uiDrawMatrixTransformSize(uiDrawMatrix *m, double *x, double *y) { - fallbackTransformSize(m, x, y); + uiprivFallbackTransformSize(m, x, y); } diff --git a/windows/drawpath.cpp b/windows/drawpath.cpp index 1ae2d8e5..34b15466 100644 --- a/windows/drawpath.cpp +++ b/windows/drawpath.cpp @@ -17,7 +17,7 @@ uiDrawPath *uiDrawNewPath(uiDrawFillMode fillmode) uiDrawPath *p; HRESULT hr; - p = uiNew(uiDrawPath); + p = uiprivNew(uiDrawPath); hr = d2dfactory->CreatePathGeometry(&(p->path)); if (hr != S_OK) logHRESULT(L"error creating path", hr); @@ -43,7 +43,7 @@ void uiDrawFreePath(uiDrawPath *p) // TODO close sink first? p->sink->Release(); p->path->Release(); - uiFree(p); + uiprivFree(p); } void uiDrawPathNewFigure(uiDrawPath *p, double x, double y) @@ -66,6 +66,7 @@ void uiDrawPathNewFigure(uiDrawPath *p, double x, double y) // That is to say, it's NOT THE SWEEP. // The sweep is defined by the start and end points and whether the arc is "large". // As a result, this design does not allow for full circles or ellipses with a single arc; they have to be simulated with two. +// TODO https://github.com/Microsoft/WinObjC/blob/develop/Frameworks/CoreGraphics/CGPath.mm#L313 struct arc { double xCenter; @@ -96,20 +97,20 @@ static void drawArc(uiDrawPath *p, struct arc *a, void (*startFunction)(uiDrawPa fullCircle = FALSE; // use the absolute value to tackle both ≥2π and ≤-2π at the same time absSweep = fabs(a->sweep); - if (absSweep > (2 * M_PI)) // this part is easy + if (absSweep > (2 * uiPi)) // this part is easy fullCircle = TRUE; else { double aerDiff; - aerDiff = fabs(absSweep - (2 * M_PI)); + aerDiff = fabs(absSweep - (2 * uiPi)); // if we got here then we know a->sweep is larger (or the same!) fullCircle = aerDiff <= absSweep * aerMax; } // TODO make sure this works right for the negative direction if (fullCircle) { - a->sweep = M_PI; + a->sweep = uiPi; drawArc(p, a, startFunction); - a->startAngle += M_PI; + a->startAngle += uiPi; drawArc(p, a, NULL); return; } @@ -144,13 +145,13 @@ static void drawArc(uiDrawPath *p, struct arc *a, void (*startFunction)(uiDrawPa as.sweepDirection = D2D1_SWEEP_DIRECTION_CLOCKWISE; // TODO explain the outer if if (!a->negative) - if (a->sweep > M_PI) + if (a->sweep > uiPi) as.arcSize = D2D1_ARC_SIZE_LARGE; else as.arcSize = D2D1_ARC_SIZE_SMALL; else // TODO especially this part - if (a->sweep > M_PI) + if (a->sweep > uiPi) as.arcSize = D2D1_ARC_SIZE_SMALL; else as.arcSize = D2D1_ARC_SIZE_LARGE; @@ -241,7 +242,6 @@ void uiDrawPathEnd(uiDrawPath *p) ID2D1PathGeometry *pathGeometry(uiDrawPath *p) { if (p->sink != NULL) - // TODO userbug() - complain("path not ended"); + uiprivUserBug("You cannot draw with a uiDrawPath that was not ended. (path: %p)", p); return p->path; } diff --git a/windows/drawtext.cpp b/windows/drawtext.cpp index b2bd81da..ec2ae152 100644 --- a/windows/drawtext.cpp +++ b/windows/drawtext.cpp @@ -1,391 +1,115 @@ -// 22 december 2015 +// 17 january 2017 #include "uipriv_windows.hpp" #include "draw.hpp" -// TODO really migrate +#include "attrstr.hpp" -// notes: -// only available in windows 8 and newer: -// - character spacing -// - kerning control -// - justficiation (how could I possibly be making this up?!) -// - vertical text (SERIOUSLY?! WHAT THE ACTUAL FUCK, MICROSOFT?!?!?!? DID YOU NOT THINK ABOUT THIS THE FIRST TIME, TRYING TO IMPROVE THE INTERNATIONALIZATION OF WINDOWS 7?!?!?! bonus: some parts of MSDN even say 8.1 and up only!) - -struct uiDrawFontFamilies { - fontCollection *fc; -}; - -uiDrawFontFamilies *uiDrawListFontFamilies(void) -{ - struct uiDrawFontFamilies *ff; - - ff = uiNew(struct uiDrawFontFamilies); - ff->fc = loadFontCollection(); - return ff; -} - -uintmax_t uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff) -{ - return ff->fc->fonts->GetFontFamilyCount(); -} - -char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, uintmax_t n) -{ - IDWriteFontFamily *family; - WCHAR *wname; - char *name; - HRESULT hr; - - hr = ff->fc->fonts->GetFontFamily(n, &family); - if (hr != S_OK) - logHRESULT(L"error getting font out of collection", hr); - wname = fontCollectionFamilyName(ff->fc, family); - name = toUTF8(wname); - uiFree(wname); - family->Release(); - return name; -} - -void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff) -{ - fontCollectionFree(ff->fc); - uiFree(ff); -} - -struct uiDrawTextFont { - IDWriteFont *f; - WCHAR *family; // save for convenience in uiDrawNewTextLayout() - double size; -}; - -uiDrawTextFont *mkTextFont(IDWriteFont *df, BOOL addRef, WCHAR *family, BOOL copyFamily, double size) -{ - uiDrawTextFont *font; - WCHAR *copy; - HRESULT hr; - - font = uiNew(uiDrawTextFont); - font->f = df; - if (addRef) - font->f->AddRef(); - if (copyFamily) { - copy = (WCHAR *) uiAlloc((wcslen(family) + 1) * sizeof (WCHAR), "WCHAR[]"); - wcscpy(copy, family); - font->family = copy; - } else - font->family = family; - font->size = size; - return font; -} - -// TODO consider moving these all to dwrite.cpp - -static const struct { - bool lastOne; - uiDrawTextWeight uival; - DWRITE_FONT_WEIGHT dwval; -} dwriteWeights[] = { - { false, uiDrawTextWeightThin, DWRITE_FONT_WEIGHT_THIN }, - { false, uiDrawTextWeightUltraLight, DWRITE_FONT_WEIGHT_ULTRA_LIGHT }, - { false, uiDrawTextWeightLight, DWRITE_FONT_WEIGHT_LIGHT }, - { false, uiDrawTextWeightBook, DWRITE_FONT_WEIGHT_SEMI_LIGHT }, - { false, uiDrawTextWeightNormal, DWRITE_FONT_WEIGHT_NORMAL }, - { false, uiDrawTextWeightMedium, DWRITE_FONT_WEIGHT_MEDIUM }, - { false, uiDrawTextWeightSemiBold, DWRITE_FONT_WEIGHT_SEMI_BOLD }, - { false, uiDrawTextWeightBold, DWRITE_FONT_WEIGHT_BOLD }, - { false, uiDrawTextWeightUtraBold, DWRITE_FONT_WEIGHT_ULTRA_BOLD }, - { false, uiDrawTextWeightHeavy, DWRITE_FONT_WEIGHT_HEAVY }, - { true, uiDrawTextWeightUltraHeavy, DWRITE_FONT_WEIGHT_ULTRA_BLACK, }, -}; - -static const struct { - bool lastOne; - uiDrawTextItalic uival; - DWRITE_FONT_STYLE dwval; -} dwriteItalics[] = { - { false, uiDrawTextItalicNormal, DWRITE_FONT_STYLE_NORMAL }, - { false, uiDrawTextItalicOblique, DWRITE_FONT_STYLE_OBLIQUE }, - { true, uiDrawTextItalicItalic, DWRITE_FONT_STYLE_ITALIC }, -}; - -static const struct { - bool lastOne; - uiDrawTextStretch uival; - DWRITE_FONT_STRETCH dwval; -} dwriteStretches[] = { - { false, uiDrawTextStretchUltraCondensed, DWRITE_FONT_STRETCH_ULTRA_CONDENSED }, - { false, uiDrawTextStretchExtraCondensed, DWRITE_FONT_STRETCH_EXTRA_CONDENSED }, - { false, uiDrawTextStretchCondensed, DWRITE_FONT_STRETCH_CONDENSED }, - { false, uiDrawTextStretchSemiCondensed, DWRITE_FONT_STRETCH_SEMI_CONDENSED }, - { false, uiDrawTextStretchNormal, DWRITE_FONT_STRETCH_NORMAL }, - { false, uiDrawTextStretchSemiExpanded, DWRITE_FONT_STRETCH_SEMI_EXPANDED }, - { false, uiDrawTextStretchExpanded, DWRITE_FONT_STRETCH_EXPANDED }, - { false, uiDrawTextStretchExtraExpanded, DWRITE_FONT_STRETCH_EXTRA_EXPANDED }, - { true, uiDrawTextStretchUltraExpanded, DWRITE_FONT_STRETCH_ULTRA_EXPANDED }, -}; - -void attrToDWriteAttr(struct dwriteAttr *attr) -{ - bool found; - int i; - - found = false; - for (i = 0; ; i++) { - if (dwriteWeights[i].uival == attr->weight) { - attr->dweight = dwriteWeights[i].dwval; - found = true; - break; - } - if (dwriteWeights[i].lastOne) - break; - } - if (!found) - complain("invalid weight %d passed to attrToDWriteAttr()", attr->weight); - - found = false; - for (i = 0; ; i++) { - if (dwriteItalics[i].uival == attr->italic) { - attr->ditalic = dwriteItalics[i].dwval; - found = true; - break; - } - if (dwriteItalics[i].lastOne) - break; - } - if (!found) - complain("invalid italic %d passed to attrToDWriteAttr()", attr->italic); - - found = false; - for (i = 0; ; i++) { - if (dwriteStretches[i].uival == attr->stretch) { - attr->dstretch = dwriteStretches[i].dwval; - found = true; - break; - } - if (dwriteStretches[i].lastOne) - break; - } - if (!found) - complain("invalid stretch %d passed to attrToDWriteAttr()", attr->stretch); -} - -void dwriteAttrToAttr(struct dwriteAttr *attr) -{ - int weight, against, n; - int curdiff, curindex; - bool found; - int i; - - // weight is scaled; we need to test to see what's nearest - weight = (int) (attr->dweight); - against = (int) (dwriteWeights[0].dwval); - curdiff = abs(against - weight); - curindex = 0; - for (i = 1; ; i++) { - against = (int) (dwriteWeights[i].dwval); - n = abs(against - weight); - if (n < curdiff) { - curdiff = n; - curindex = i; - } - if (dwriteWeights[i].lastOne) - break; - } - attr->weight = dwriteWeights[i].uival; - - // italic and stretch are simple values; we can just do a matching search - found = false; - for (i = 0; ; i++) { - if (dwriteItalics[i].dwval == attr->ditalic) { - attr->italic = dwriteItalics[i].uival; - found = true; - break; - } - if (dwriteItalics[i].lastOne) - break; - } - if (!found) - complain("invalid italic %d passed to dwriteAttrToAttr()", attr->ditalic); - - found = false; - for (i = 0; ; i++) { - if (dwriteStretches[i].dwval == attr->dstretch) { - attr->stretch = dwriteStretches[i].uival; - found = true; - break; - } - if (dwriteStretches[i].lastOne) - break; - } - if (!found) - complain("invalid stretch %d passed to dwriteAttrToAttr()", attr->dstretch); -} - -uiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc) -{ - uiDrawTextFont *font; - IDWriteFontCollection *collection; - UINT32 index; - BOOL exists; - struct dwriteAttr attr; - IDWriteFontFamily *family; - WCHAR *wfamily; - IDWriteFont *match; - HRESULT hr; - - // always get the latest available font information - hr = dwfactory->GetSystemFontCollection(&collection, TRUE); - if (hr != S_OK) - logHRESULT(L"error getting system font collection", hr); - - wfamily = toUTF16(desc->Family); - hr = collection->FindFamilyName(wfamily, &index, &exists); - if (hr != S_OK) - logHRESULT(L"error finding font family", hr); - if (!exists) - complain("TODO family not found in uiDrawLoadClosestFont()", hr); - hr = collection->GetFontFamily(index, &family); - if (hr != S_OK) - logHRESULT(L"error loading font family", hr); - - attr.weight = desc->Weight; - attr.italic = desc->Italic; - attr.stretch = desc->Stretch; - attrToDWriteAttr(&attr); - hr = family->GetFirstMatchingFont( - attr.dweight, - attr.dstretch, - attr.ditalic, - &match); - if (hr != S_OK) - logHRESULT(L"error loading font", hr); - - font = mkTextFont(match, - FALSE, // we own the initial reference; no need to add another one - wfamily, FALSE, // will be freed with font - desc->Size); - - family->Release(); - collection->Release(); - - return font; -} - -void uiDrawFreeTextFont(uiDrawTextFont *font) -{ - font->f->Release(); - uiFree(font->family); - uiFree(font); -} - -uintptr_t uiDrawTextFontHandle(uiDrawTextFont *font) -{ - return (uintptr_t) (font->f); -} - -void uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc) -{ - // TODO - - desc->Size = font->size; - - // TODO -} - -// text sizes are 1/72 of an inch -// points in Direct2D are 1/96 of an inch (https://msdn.microsoft.com/en-us/library/windows/desktop/ff684173%28v=vs.85%29.aspx, https://msdn.microsoft.com/en-us/library/windows/desktop/hh447022%28v=vs.85%29.aspx) -// As for the actual conversion from design units, see: -// - http://cboard.cprogramming.com/windows-programming/136733-directwrite-font-height-issues.html -// - https://sourceforge.net/p/vstgui/mailman/message/32483143/ -// - http://xboxforums.create.msdn.com/forums/t/109445.aspx -// - https://msdn.microsoft.com/en-us/library/dd183564%28v=vs.85%29.aspx -// - http://www.fontbureau.com/blog/the-em/ -// TODO make points here about how DIPs in DirectWrite == DIPs in Direct2D; if not, figure out what they really are? for the width and layout functions later -static double scaleUnits(double what, double designUnitsPerEm, double size) -{ - return (what / designUnitsPerEm) * (size * (96.0 / 72.0)); -} - -void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics) -{ - DWRITE_FONT_METRICS dm; - - font->f->GetMetrics(&dm); - metrics->Ascent = scaleUnits(dm.ascent, dm.designUnitsPerEm, font->size); - metrics->Descent = scaleUnits(dm.descent, dm.designUnitsPerEm, font->size); - // TODO what happens if dm.xxx is negative? - // TODO remember what this was for - metrics->Leading = scaleUnits(dm.lineGap, dm.designUnitsPerEm, font->size); - metrics->UnderlinePos = scaleUnits(dm.underlinePosition, dm.designUnitsPerEm, font->size); - metrics->UnderlineThickness = scaleUnits(dm.underlineThickness, dm.designUnitsPerEm, font->size); -} - -// some attributes, such as foreground color, can't be applied until after we establish a Direct2D context :/ so we have to prepare all attributes in advance -// also since there's no way to clear the attributes from a layout en masse (apart from overwriting them all), we'll play it safe by creating a new layout each time -enum layoutAttrType { - layoutAttrColor, -}; - -struct layoutAttr { - enum layoutAttrType type; - intmax_t start; - intmax_t end; - double components[4]; -}; +// TODO verify our renderer is correct, especially with regards to snapping struct uiDrawTextLayout { - WCHAR *text; - size_t textlen; - double width; IDWriteTextFormat *format; - std::vector *attrs; + IDWriteTextLayout *layout; + std::vector *backgroundParams; + // for converting DirectWrite indices from/to byte offsets + size_t *u8tou16; + size_t nUTF8; + size_t *u16tou8; + size_t nUTF16; }; -uiDrawTextLayout *uiDrawNewTextLayout(const char *text, uiDrawTextFont *defaultFont, double width) +// TODO copy notes about DirectWrite DIPs being equal to Direct2D DIPs here + +// typographic points are 1/72 inch; this parameter is 1/96 inch +// fortunately Microsoft does this too, in https://msdn.microsoft.com/en-us/library/windows/desktop/dd371554%28v=vs.85%29.aspx +#define pointSizeToDWriteSize(size) (size * (96.0 / 72.0)) + +// TODO move this and the layout creation stuff to attrstr.cpp like the other ports, or move the other ports into their drawtext.* files +// TODO should be const but then I can't operator[] on it; the real solution is to find a way to do designated array initializers in C++11 but I do not know enough C++ voodoo to make it work (it is possible but no one else has actually done it before) +static std::map dwriteAligns = { + { uiDrawTextAlignLeft, DWRITE_TEXT_ALIGNMENT_LEADING }, + { uiDrawTextAlignCenter, DWRITE_TEXT_ALIGNMENT_CENTER }, + { uiDrawTextAlignRight, DWRITE_TEXT_ALIGNMENT_TRAILING }, +}; + +uiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *p) { - uiDrawTextLayout *layout; + uiDrawTextLayout *tl; + WCHAR *wDefaultFamily; + DWRITE_WORD_WRAPPING wrap; + FLOAT maxWidth; HRESULT hr; - layout = uiNew(uiDrawTextLayout); + tl = uiprivNew(uiDrawTextLayout); - hr = dwfactory->CreateTextFormat(defaultFont->family, - NULL, - defaultFont->f->GetWeight(), - defaultFont->f->GetStyle(), - defaultFont->f->GetStretch(), - // typographic points are 1/72 inch; this parameter is 1/96 inch - // fortunately Microsoft does this too, in https://msdn.microsoft.com/en-us/library/windows/desktop/dd371554%28v=vs.85%29.aspx - defaultFont->size * (96.0 / 72.0), + wDefaultFamily = toUTF16(p->DefaultFont->Family); + hr = dwfactory->CreateTextFormat( + wDefaultFamily, NULL, + uiprivWeightToDWriteWeight(p->DefaultFont->Weight), + uiprivItalicToDWriteStyle(p->DefaultFont->Italic), + uiprivStretchToDWriteStretch(p->DefaultFont->Stretch), + pointSizeToDWriteSize(p->DefaultFont->Size), // see http://stackoverflow.com/questions/28397971/idwritefactorycreatetextformat-failing and https://msdn.microsoft.com/en-us/library/windows/desktop/dd368203.aspx - // TODO use the current locale again? + // TODO use the current locale? L"", - &(layout->format)); + &(tl->format)); + uiprivFree(wDefaultFamily); if (hr != S_OK) logHRESULT(L"error creating IDWriteTextFormat", hr); + hr = tl->format->SetTextAlignment(dwriteAligns[p->Align]); + if (hr != S_OK) + logHRESULT(L"error applying text layout alignment", hr); - layout->text = toUTF16(text); - layout->textlen = wcslen(layout->text); + hr = dwfactory->CreateTextLayout( + (const WCHAR *) uiprivAttributedStringUTF16String(p->String), uiprivAttributedStringUTF16Len(p->String), + tl->format, + // FLOAT is float, not double, so this should work... TODO + FLT_MAX, FLT_MAX, + &(tl->layout)); + if (hr != S_OK) + logHRESULT(L"error creating IDWriteTextLayout", hr); - uiDrawTextLayoutSetWidth(layout, width); + // and set the width + // this is the only wrapping mode (apart from "no wrap") available prior to Windows 8.1 (TODO verify this fact) (TODO this should be the default anyway) + wrap = DWRITE_WORD_WRAPPING_WRAP; + maxWidth = (FLOAT) (p->Width); + if (p->Width < 0) { + // TODO is this wrapping juggling even necessary? + wrap = DWRITE_WORD_WRAPPING_NO_WRAP; + // setting the max width in this case technically isn't needed since the wrap mode will simply ignore the max width, but let's do it just to be safe + maxWidth = FLT_MAX; // see TODO above + } + hr = tl->layout->SetWordWrapping(wrap); + if (hr != S_OK) + logHRESULT(L"error setting IDWriteTextLayout word wrapping mode", hr); + hr = tl->layout->SetMaxWidth(maxWidth); + if (hr != S_OK) + logHRESULT(L"error setting IDWriteTextLayout max layout width", hr); - layout->attrs = new std::vector; + uiprivAttributedStringApplyAttributesToDWriteTextLayout(p, tl->layout, &(tl->backgroundParams)); - return layout; + // and finally copy the UTF-8/UTF-16 index conversion tables + tl->u8tou16 = uiprivAttributedStringCopyUTF8ToUTF16Table(p->String, &(tl->nUTF8)); + tl->u16tou8 = uiprivAttributedStringCopyUTF16ToUTF8Table(p->String, &(tl->nUTF16)); + + return tl; } -void uiDrawFreeTextLayout(uiDrawTextLayout *layout) +void uiDrawFreeTextLayout(uiDrawTextLayout *tl) { - delete layout->attrs; - layout->format->Release(); - uiFree(layout->text); - uiFree(layout); + uiprivFree(tl->u16tou8); + uiprivFree(tl->u8tou16); + for (auto p : *(tl->backgroundParams)) + uiprivFree(p); + delete tl->backgroundParams; + tl->layout->Release(); + tl->format->Release(); + uiprivFree(tl); } -static ID2D1SolidColorBrush *mkSolidBrush(ID2D1RenderTarget *rt, double r, double g, double b, double a) +// TODO make this shared code somehow +static HRESULT mkSolidBrush(ID2D1RenderTarget *rt, double r, double g, double b, double a, ID2D1SolidColorBrush **brush) { D2D1_BRUSH_PROPERTIES props; D2D1_COLOR_F color; - ID2D1SolidColorBrush *brush; - HRESULT hr; ZeroMemory(&props, sizeof (D2D1_BRUSH_PROPERTIES)); props.opacity = 1.0; @@ -396,129 +120,417 @@ static ID2D1SolidColorBrush *mkSolidBrush(ID2D1RenderTarget *rt, double r, doubl color.g = g; color.b = b; color.a = a; - hr = rt->CreateSolidColorBrush( + return rt->CreateSolidColorBrush( &color, &props, - &brush); + brush); +} + +static ID2D1SolidColorBrush *mustMakeSolidBrush(ID2D1RenderTarget *rt, double r, double g, double b, double a) +{ + ID2D1SolidColorBrush *brush; + HRESULT hr; + + hr = mkSolidBrush(rt, r, g, b, a, &brush); if (hr != S_OK) logHRESULT(L"error creating solid brush", hr); return brush; } -IDWriteTextLayout *prepareLayout(uiDrawTextLayout *layout, ID2D1RenderTarget *rt) +// some of the stuff we want to do isn't possible with what DirectWrite provides itself; we need to do it ourselves + +drawingEffectsAttr::drawingEffectsAttr(void) { - IDWriteTextLayout *dl; - DWRITE_TEXT_RANGE range; - IUnknown *unkBrush; - DWRITE_WORD_WRAPPING wrap; - FLOAT maxWidth; - HRESULT hr; + this->refcount = 1; + this->hasColor = false; + this->hasUnderline = false; + this->hasUnderlineColor = false; +} - hr = dwfactory->CreateTextLayout(layout->text, layout->textlen, - layout->format, - // FLOAT is float, not double, so this should work... TODO - FLT_MAX, FLT_MAX, - &dl); - if (hr != S_OK) - logHRESULT(L"error creating IDWriteTextLayout", hr); +HRESULT STDMETHODCALLTYPE drawingEffectsAttr::QueryInterface(REFIID riid, void **ppvObject) +{ + if (ppvObject == NULL) + return E_POINTER; + if (riid == IID_IUnknown) { + this->AddRef(); + *ppvObject = this; + return S_OK; + } + *ppvObject = NULL; + return E_NOINTERFACE; +} - for (const struct layoutAttr &attr : *(layout->attrs)) { - range.startPosition = attr.start; - range.length = attr.end - attr.start; - switch (attr.type) { - case layoutAttrColor: - if (rt == NULL) // determining extents, not drawing - break; - unkBrush = mkSolidBrush(rt, - attr.components[0], - attr.components[1], - attr.components[2], - attr.components[3]); - hr = dl->SetDrawingEffect(unkBrush, range); - unkBrush->Release(); // associated with dl - break; - default: - hr = E_FAIL; - logHRESULT(L"invalid text attribute type", hr); +ULONG STDMETHODCALLTYPE drawingEffectsAttr::AddRef(void) +{ + this->refcount++; + return this->refcount; +} + +ULONG STDMETHODCALLTYPE drawingEffectsAttr::Release(void) +{ + this->refcount--; + if (this->refcount == 0) { + delete this; + return 0; + } + return this->refcount; +} + +void drawingEffectsAttr::setColor(double r, double g, double b, double a) +{ + this->hasColor = true; + this->r = r; + this->g = g; + this->b = b; + this->a = a; +} + +void drawingEffectsAttr::setUnderline(uiUnderline u) +{ + this->hasUnderline = true; + this->u = u; +} + +void drawingEffectsAttr::setUnderlineColor(double r, double g, double b, double a) +{ + this->hasUnderlineColor = true; + this->ur = r; + this->ug = g; + this->ub = b; + this->ua = a; +} + +HRESULT drawingEffectsAttr::mkColorBrush(ID2D1RenderTarget *rt, ID2D1SolidColorBrush **b) +{ + if (!this->hasColor) { + *b = NULL; + return S_OK; + } + return mkSolidBrush(rt, this->r, this->g, this->b, this->a, b); +} + +HRESULT drawingEffectsAttr::underline(uiUnderline *u) +{ + if (u == NULL) + return E_POINTER; + if (!this->hasUnderline) + return E_UNEXPECTED; + *u = this->u; + return S_OK; +} + +HRESULT drawingEffectsAttr::mkUnderlineBrush(ID2D1RenderTarget *rt, ID2D1SolidColorBrush **b) +{ + if (!this->hasUnderlineColor) { + *b = NULL; + return S_OK; + } + return mkSolidBrush(rt, this->ur, this->ug, this->ub, this->ua, b); +} + +// this is based on http://www.charlespetzold.com/blog/2014/01/Character-Formatting-Extensions-with-DirectWrite.html +class textRenderer : public IDWriteTextRenderer { + ULONG refcount; + ID2D1RenderTarget *rt; + BOOL snap; + ID2D1SolidColorBrush *black; +public: + textRenderer(ID2D1RenderTarget *rt, BOOL snap, ID2D1SolidColorBrush *black) + { + this->refcount = 1; + this->rt = rt; + this->snap = snap; + this->black = black; + } + + // IUnknown + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) + { + if (ppvObject == NULL) + return E_POINTER; + if (riid == IID_IUnknown || + riid == __uuidof (IDWritePixelSnapping) || + riid == __uuidof (IDWriteTextRenderer)) { + this->AddRef(); + *ppvObject = this; + return S_OK; } + *ppvObject = NULL; + return E_NOINTERFACE; + } + + virtual ULONG STDMETHODCALLTYPE AddRef(void) + { + this->refcount++; + return this->refcount; + } + + virtual ULONG STDMETHODCALLTYPE Release(void) + { + this->refcount--; + if (this->refcount == 0) { + delete this; + return 0; + } + return this->refcount; + } + + // IDWritePixelSnapping + virtual HRESULT STDMETHODCALLTYPE GetCurrentTransform(void *clientDrawingContext, DWRITE_MATRIX *transform) + { + D2D1_MATRIX_3X2_F d2dtf; + + if (transform == NULL) + return E_POINTER; + this->rt->GetTransform(&d2dtf); + transform->m11 = d2dtf._11; + transform->m12 = d2dtf._12; + transform->m21 = d2dtf._21; + transform->m22 = d2dtf._22; + transform->dx = d2dtf._31; + transform->dy = d2dtf._32; + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE GetPixelsPerDip(void *clientDrawingContext, FLOAT *pixelsPerDip) + { + FLOAT dpix, dpiy; + + if (pixelsPerDip == NULL) + return E_POINTER; + this->rt->GetDpi(&dpix, &dpiy); + *pixelsPerDip = dpix / 96; + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE IsPixelSnappingDisabled(void *clientDrawingContext, BOOL *isDisabled) + { + if (isDisabled == NULL) + return E_POINTER; + *isDisabled = !this->snap; + return S_OK; + } + + // IDWriteTextRenderer + virtual HRESULT STDMETHODCALLTYPE DrawGlyphRun(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, DWRITE_MEASURING_MODE measuringMode, const DWRITE_GLYPH_RUN *glyphRun, const DWRITE_GLYPH_RUN_DESCRIPTION *glyphRunDescription, IUnknown *clientDrawingEffect) + { + D2D1_POINT_2F baseline; + drawingEffectsAttr *dea = (drawingEffectsAttr *) clientDrawingEffect; + ID2D1SolidColorBrush *brush; + + baseline.x = baselineOriginX; + baseline.y = baselineOriginY; + brush = NULL; + if (dea != NULL) { + HRESULT hr; + + hr = dea->mkColorBrush(this->rt, &brush); + if (hr != S_OK) + return hr; + } + if (brush == NULL) { + brush = this->black; + brush->AddRef(); + } + this->rt->DrawGlyphRun( + baseline, + glyphRun, + brush, + measuringMode); + brush->Release(); + return S_OK; + } + + virtual HRESULT STDMETHODCALLTYPE DrawInlineObject(void *clientDrawingContext, FLOAT originX, FLOAT originY, IDWriteInlineObject *inlineObject, BOOL isSideways, BOOL isRightToLeft, IUnknown *clientDrawingEffect) + { + if (inlineObject == NULL) + return E_POINTER; + return inlineObject->Draw(clientDrawingContext, this, + originX, originY, + isSideways, isRightToLeft, + clientDrawingEffect); + } + + virtual HRESULT STDMETHODCALLTYPE DrawStrikethrough(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, const DWRITE_STRIKETHROUGH *strikethrough, IUnknown *clientDrawingEffect) + { + // we don't support strikethrough + return E_UNEXPECTED; + } + + // TODO clean this function up + virtual HRESULT STDMETHODCALLTYPE DrawUnderline(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, const DWRITE_UNDERLINE *underline, IUnknown *clientDrawingEffect) + { + drawingEffectsAttr *dea = (drawingEffectsAttr *) clientDrawingEffect; + uiUnderline utype; + ID2D1SolidColorBrush *brush; + D2D1_RECT_F rect; + D2D1::Matrix3x2F pixeltf; + FLOAT dpix, dpiy; + D2D1_POINT_2F pt; + HRESULT hr; + + if (underline == NULL) + return E_POINTER; + if (dea == NULL) // we can only get here through an underline + return E_UNEXPECTED; + hr = dea->underline(&utype); + if (hr != S_OK) // we *should* only get here through an underline that's actually set... + return hr; + hr = dea->mkUnderlineBrush(this->rt, &brush); if (hr != S_OK) - logHRESULT(L"error adding attribute to text layout", hr); + return hr; + if (brush == NULL) { + // TODO document this rule if not already done + hr = dea->mkColorBrush(this->rt, &brush); + if (hr != S_OK) + return hr; + } + if (brush == NULL) { + brush = this->black; + brush->AddRef(); + } + rect.left = baselineOriginX; + rect.top = baselineOriginY + underline->offset; + rect.right = rect.left + underline->width; + rect.bottom = rect.top + underline->thickness; + switch (utype) { + case uiUnderlineSingle: + this->rt->FillRectangle(&rect, brush); + break; + case uiUnderlineDouble: + // TODO do any of the matrix methods return errors? + // TODO standardize double-underline shape across platforms? wavy underline shape? + this->rt->GetTransform(&pixeltf); + this->rt->GetDpi(&dpix, &dpiy); + pixeltf = pixeltf * D2D1::Matrix3x2F::Scale(dpix / 96, dpiy / 96); + pt.x = 0; + pt.y = rect.top; + pt = pixeltf.TransformPoint(pt); + rect.top = (FLOAT) ((int) (pt.y + 0.5)); + pixeltf.Invert(); + pt = pixeltf.TransformPoint(pt); + rect.top = pt.y; + // first line + rect.top -= underline->thickness; + // and it seems we need to recompute this + rect.bottom = rect.top + underline->thickness; + this->rt->FillRectangle(&rect, brush); + // second line + rect.top += 2 * underline->thickness; + rect.bottom = rect.top + underline->thickness; + this->rt->FillRectangle(&rect, brush); + break; + case uiUnderlineSuggestion: + { // TODO get rid of the extra block + // TODO properly clean resources on failure + // TODO use fully qualified C overloads for all methods + // TODO ensure all methods properly have errors handled + ID2D1PathGeometry *path; + ID2D1GeometrySink *sink; + double amplitude, period, xOffset, yOffset; + double t; + bool first = true; + HRESULT hr; + + hr = d2dfactory->CreatePathGeometry(&path); + if (hr != S_OK) + return hr; + hr = path->Open(&sink); + if (hr != S_OK) + return hr; + amplitude = underline->thickness; + period = 5 * underline->thickness; + xOffset = baselineOriginX; + yOffset = baselineOriginY + underline->offset; + for (t = 0; t < underline->width; t++) { + double x, angle, y; + D2D1_POINT_2F pt; + + x = t + xOffset; + angle = 2 * uiPi * fmod(x, period) / period; + y = amplitude * sin(angle) + yOffset; + pt.x = x; + pt.y = y; + if (first) { + sink->BeginFigure(pt, D2D1_FIGURE_BEGIN_HOLLOW); + first = false; + } else + sink->AddLine(pt); + } + sink->EndFigure(D2D1_FIGURE_END_OPEN); + hr = sink->Close(); + if (hr != S_OK) + return hr; + sink->Release(); + this->rt->DrawGeometry(path, brush, underline->thickness); + path->Release(); + } + break; + } + brush->Release(); + return S_OK; } +}; - // and set the width - // this is the only wrapping mode (apart from "no wrap") available prior to Windows 8.1 - wrap = DWRITE_WORD_WRAPPING_WRAP; - maxWidth = layout->width; - if (layout->width < 0) { - wrap = DWRITE_WORD_WRAPPING_NO_WRAP; - // setting the max width in this case technically isn't needed since the wrap mode will simply ignore the max width, but let's do it just to be safe - maxWidth = FLT_MAX; // see TODO above - } - hr = dl->SetWordWrapping(wrap); - if (hr != S_OK) - logHRESULT(L"error setting word wrapping mode", hr); - hr = dl->SetMaxWidth(maxWidth); - if (hr != S_OK) - logHRESULT(L"error setting max layout width", hr); - - return dl; -} - - -void uiDrawTextLayoutSetWidth(uiDrawTextLayout *layout, double width) +// TODO this ignores clipping? +void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y) { - layout->width = width; -} - -// TODO for a single line the height includes the leading; it should not -void uiDrawTextLayoutExtents(uiDrawTextLayout *layout, double *width, double *height) -{ - IDWriteTextLayout *dl; - DWRITE_TEXT_METRICS metrics; - HRESULT hr; - - dl = prepareLayout(layout, NULL); - hr = dl->GetMetrics(&metrics); - if (hr != S_OK) - logHRESULT(L"error getting layout metrics", hr); - *width = metrics.width; - // TODO make sure the behavior of this on empty strings is the same on all platforms - *height = metrics.height; - dl->Release(); -} - -void uiDrawText(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout) -{ - IDWriteTextLayout *dl; D2D1_POINT_2F pt; - ID2D1Brush *black; + ID2D1SolidColorBrush *black; + textRenderer *renderer; HRESULT hr; + for (auto p : *(tl->backgroundParams)) { + // TODO + } + // TODO document that fully opaque black is the default text color; figure out whether this is upheld in various scenarios on other platforms - black = mkSolidBrush(c->rt, 0.0, 0.0, 0.0, 1.0); + // TODO figure out if this needs to be cleaned out + black = mustMakeSolidBrush(c->rt, 0.0, 0.0, 0.0, 1.0); - dl = prepareLayout(layout, c->rt); +#define renderD2D 0 +#define renderOur 1 +#if renderD2D pt.x = x; pt.y = y; // TODO D2D1_DRAW_TEXT_OPTIONS_NO_SNAP? // TODO D2D1_DRAW_TEXT_OPTIONS_CLIP? - // TODO when setting 8.1 as minimum, D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT? - c->rt->DrawTextLayout(pt, dl, black, D2D1_DRAW_TEXT_OPTIONS_NONE); - dl->Release(); + // TODO LONGTERM when setting 8.1 as minimum (TODO verify), D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT? + // TODO what is our pixel snapping setting related to the OPTIONS enum values? + c->rt->DrawTextLayout(pt, tl->layout, black, D2D1_DRAW_TEXT_OPTIONS_NONE); +#endif +#if renderD2D && renderOur + // draw ours semitransparent so we can check + // TODO get the actual color Charles Petzold uses and use that + black->Release(); + black = mustMakeSolidBrush(c->rt, 1.0, 0.0, 0.0, 0.75); +#endif +#if renderOur + renderer = new textRenderer(c->rt, + TRUE, // TODO FALSE for no-snap? + black); + hr = tl->layout->Draw(NULL, + renderer, + x, y); + if (hr != S_OK) + logHRESULT(L"error drawing IDWriteTextLayout", hr); + renderer->Release(); +#endif black->Release(); } -void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, intmax_t startChar, intmax_t endChar, double r, double g, double b, double a) +// TODO for a single line the height includes the leading; should it? TextEdit on OS X always includes the leading and/or paragraph spacing, otherwise Klee won't work... +// TODO width does not include trailing whitespace +void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height) { - struct layoutAttr attr; + DWRITE_TEXT_METRICS metrics; + HRESULT hr; - attr.type = layoutAttrColor; - attr.start = startChar; - attr.end = endChar; - attr.components[0] = r; - attr.components[1] = g; - attr.components[2] = b; - attr.components[3] = a; - layout->attrs->push_back(attr); + hr = tl->layout->GetMetrics(&metrics); + if (hr != S_OK) + logHRESULT(L"error getting IDWriteTextLayout layout metrics", hr); + *width = metrics.width; + // TODO make sure the behavior of this on empty strings is the same on all platforms (ideally should be 0-width, line height-height; TODO note this in the docs too) + *height = metrics.height; } diff --git a/windows/dwrite.cpp b/windows/dwrite.cpp index 9156f179..4d6b6741 100644 --- a/windows/dwrite.cpp +++ b/windows/dwrite.cpp @@ -1,10 +1,11 @@ // 14 april 2016 #include "uipriv_windows.hpp" -// TODO really migrate? +#include "attrstr.hpp" IDWriteFactory *dwfactory = NULL; -HRESULT initDrawText(void) +// TOOD rename to something else, maybe +HRESULT uiprivInitDrawText(void) { // TOOD use DWRITE_FACTORY_TYPE_ISOLATED instead? return DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, @@ -12,17 +13,17 @@ HRESULT initDrawText(void) (IUnknown **) (&dwfactory)); } -void uninitDrawText(void) +void uiprivUninitDrawText(void) { dwfactory->Release(); } -fontCollection *loadFontCollection(void) +fontCollection *uiprivLoadFontCollection(void) { fontCollection *fc; HRESULT hr; - fc = uiNew(fontCollection); + fc = uiprivNew(fontCollection); // always get the latest available font information hr = dwfactory->GetSystemFontCollection(&(fc->fonts), TRUE); if (hr != S_OK) @@ -31,7 +32,13 @@ fontCollection *loadFontCollection(void) return fc; } -WCHAR *fontCollectionFamilyName(fontCollection *fc, IDWriteFontFamily *family) +void uiprivFontCollectionFree(fontCollection *fc) +{ + fc->fonts->Release(); + uiprivFree(fc); +} + +WCHAR *uiprivFontCollectionFamilyName(fontCollection *fc, IDWriteFontFamily *family) { IDWriteLocalizedStrings *names; WCHAR *str; @@ -40,12 +47,12 @@ WCHAR *fontCollectionFamilyName(fontCollection *fc, IDWriteFontFamily *family) hr = family->GetFamilyNames(&names); if (hr != S_OK) logHRESULT(L"error getting names of font out", hr); - str = fontCollectionCorrectString(fc, names); + str = uiprivFontCollectionCorrectString(fc, names); names->Release(); return str; } -WCHAR *fontCollectionCorrectString(fontCollection *fc, IDWriteLocalizedStrings *names) +WCHAR *uiprivFontCollectionCorrectString(fontCollection *fc, IDWriteLocalizedStrings *names) { UINT32 index; BOOL exists; @@ -66,6 +73,7 @@ WCHAR *fontCollectionCorrectString(fontCollection *fc, IDWriteLocalizedStrings * hr = names->FindLocaleName(fc->userLocale, &index, &exists); if (hr != S_OK || (hr == S_OK && !exists)) hr = names->FindLocaleName(L"en-us", &index, &exists); + // TODO check hr again here? or did I decide that would be redundant because COM requires output arguments to be filled regardless of return value? if (!exists) index = 0; @@ -73,16 +81,10 @@ WCHAR *fontCollectionCorrectString(fontCollection *fc, IDWriteLocalizedStrings * if (hr != S_OK) logHRESULT(L"error getting length of font name", hr); // GetStringLength() does not include the null terminator, but GetString() does - wname = (WCHAR *) uiAlloc((length + 1) * sizeof (WCHAR), "WCHAR[]"); + wname = (WCHAR *) uiprivAlloc((length + 1) * sizeof (WCHAR), "WCHAR[]"); hr = names->GetString(index, wname, length + 1); if (hr != S_OK) logHRESULT(L"error getting font name", hr); return wname; } - -void fontCollectionFree(fontCollection *fc) -{ - fc->fonts->Release(); - uiFree(fc); -} diff --git a/windows/editablecombo.cpp b/windows/editablecombo.cpp new file mode 100644 index 00000000..f1831bb6 --- /dev/null +++ b/windows/editablecombo.cpp @@ -0,0 +1,117 @@ +// 20 may 2015 +#include "uipriv_windows.hpp" + +// TODO no scrollbars? also not sure if true for combobox as well + +// we as Common Controls 6 users don't need to worry about the height of comboboxes; see http://blogs.msdn.com/b/oldnewthing/archive/2006/03/10/548537.aspx + +struct uiEditableCombobox { + uiWindowsControl c; + HWND hwnd; + void (*onChanged)(uiEditableCombobox *, void *); + void *onChangedData; +}; + +static BOOL onWM_COMMAND(uiControl *cc, HWND hwnd, WORD code, LRESULT *lResult) +{ + uiEditableCombobox *c = uiEditableCombobox(cc); + + if (code == CBN_SELCHANGE) { + // like on OS X, this is sent before the edit has been updated :( + if (PostMessage(parentOf(hwnd), + WM_COMMAND, + MAKEWPARAM(GetWindowLongPtrW(hwnd, GWLP_ID), CBN_EDITCHANGE), + (LPARAM) hwnd) == 0) + logLastError(L"error posting CBN_EDITCHANGE after CBN_SELCHANGE"); + *lResult = 0; + return TRUE; + } + if (code != CBN_EDITCHANGE) + return FALSE; + (*(c->onChanged))(c, c->onChangedData); + *lResult = 0; + return TRUE; +} + +void uiEditableComboboxDestroy(uiControl *cc) +{ + uiEditableCombobox *c = uiEditableCombobox(cc); + + uiWindowsUnregisterWM_COMMANDHandler(c->hwnd); + uiWindowsEnsureDestroyWindow(c->hwnd); + uiFreeControl(uiControl(c)); +} + +uiWindowsControlAllDefaultsExceptDestroy(uiEditableCombobox) + +// from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing +#define comboboxWidth 107 /* this is actually the shorter progress bar width, but Microsoft only indicates as wide as necessary; LONGTERM */ +#define comboboxHeight 14 /* LONGTERM: is this too high? */ + +static void uiEditableComboboxMinimumSize(uiWindowsControl *cc, int *width, int *height) +{ + uiEditableCombobox *c = uiEditableCombobox(cc); + uiWindowsSizing sizing; + int x, y; + + x = comboboxWidth; + y = comboboxHeight; + uiWindowsGetSizing(c->hwnd, &sizing); + uiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y); + *width = x; + *height = y; +} + +static void defaultOnChanged(uiEditableCombobox *c, void *data) +{ + // do nothing +} + +void uiEditableComboboxAppend(uiEditableCombobox *c, const char *text) +{ + WCHAR *wtext; + LRESULT res; + + wtext = toUTF16(text); + res = SendMessageW(c->hwnd, CB_ADDSTRING, 0, (LPARAM) wtext); + if (res == (LRESULT) CB_ERR) + logLastError(L"error appending item to uiEditableCombobox"); + else if (res == (LRESULT) CB_ERRSPACE) + logLastError(L"memory exhausted appending item to uiEditableCombobox"); + uiprivFree(wtext); +} + +char *uiEditableComboboxText(uiEditableCombobox *c) +{ + return uiWindowsWindowText(c->hwnd); +} + +void uiEditableComboboxSetText(uiEditableCombobox *c, const char *text) +{ + // does not trigger any notifications + uiWindowsSetWindowText(c->hwnd, text); +} + +void uiEditableComboboxOnChanged(uiEditableCombobox *c, void (*f)(uiEditableCombobox *c, void *data), void *data) +{ + c->onChanged = f; + c->onChangedData = data; +} + +uiEditableCombobox *uiNewEditableCombobox(void) +{ + uiEditableCombobox *c; + + uiWindowsNewControl(uiEditableCombobox, c); + + c->hwnd = uiWindowsEnsureCreateControlHWND(WS_EX_CLIENTEDGE, + L"combobox", L"", + CBS_DROPDOWN | WS_TABSTOP, + hInstance, NULL, + TRUE); + + uiWindowsRegisterWM_COMMANDHandler(c->hwnd, onWM_COMMAND, uiControl(c)); + uiEditableComboboxOnChanged(c, defaultOnChanged, NULL); + + return c; +} diff --git a/windows/entry.cpp b/windows/entry.cpp index 878f359d..a7a077f2 100644 --- a/windows/entry.cpp +++ b/windows/entry.cpp @@ -37,7 +37,7 @@ uiWindowsControlAllDefaultsExceptDestroy(uiEntry) #define entryWidth 107 /* this is actually the shorter progress bar width, but Microsoft only indicates as wide as necessary */ #define entryHeight 14 -static void uiEntryMinimumSize(uiWindowsControl *c, intmax_t *width, intmax_t *height) +static void uiEntryMinimumSize(uiWindowsControl *c, int *width, int *height) { uiEntry *e = uiEntry(c); uiWindowsSizing sizing; @@ -92,7 +92,7 @@ void uiEntrySetReadOnly(uiEntry *e, int readonly) logLastError(L"error making uiEntry read-only"); } -uiEntry *uiNewEntry(void) +static uiEntry *finishNewEntry(DWORD style) { uiEntry *e; @@ -100,7 +100,7 @@ uiEntry *uiNewEntry(void) e->hwnd = uiWindowsEnsureCreateControlHWND(WS_EX_CLIENTEDGE, L"edit", L"", - ES_AUTOHSCROLL | ES_LEFT | ES_NOHIDESEL | WS_TABSTOP, + style | ES_AUTOHSCROLL | ES_LEFT | ES_NOHIDESEL | WS_TABSTOP, hInstance, NULL, TRUE); @@ -109,3 +109,26 @@ uiEntry *uiNewEntry(void) return e; } + +uiEntry *uiNewEntry(void) +{ + return finishNewEntry(0); +} + +uiEntry *uiNewPasswordEntry(void) +{ + return finishNewEntry(ES_PASSWORD); +} + +uiEntry *uiNewSearchEntry(void) +{ + uiEntry *e; + HRESULT hr; + + e = finishNewEntry(0); + // TODO this is from ThemeExplorer; is it documented anywhere? + // TODO SearchBoxEditComposited has no border + hr = SetWindowTheme(e->hwnd, L"SearchBoxEdit", NULL); + // TODO will hr be S_OK if themes are disabled? + return e; +} diff --git a/windows/events.cpp b/windows/events.cpp index 56ef78e7..c13d6d00 100644 --- a/windows/events.cpp +++ b/windows/events.cpp @@ -8,7 +8,8 @@ struct handler { uiControl *c; // just to ensure handlers[new HWND] initializes properly - struct handler() + // TODO gcc can't handle a struct keyword here? or is that a MSVC extension? + handler() { this->commandHandler = NULL; this->notifyHandler = NULL; @@ -22,7 +23,7 @@ static std::map handlers; void uiWindowsRegisterWM_COMMANDHandler(HWND hwnd, BOOL (*handler)(uiControl *, HWND, WORD, LRESULT *), uiControl *c) { if (handlers[hwnd].commandHandler != NULL) - implbug(L"already registered a WM_COMMAND handler to window handle %p", hwnd); + uiprivImplBug("already registered a WM_COMMAND handler to window handle %p", hwnd); handlers[hwnd].commandHandler = handler; handlers[hwnd].c = c; } @@ -30,7 +31,7 @@ void uiWindowsRegisterWM_COMMANDHandler(HWND hwnd, BOOL (*handler)(uiControl *, void uiWindowsRegisterWM_NOTIFYHandler(HWND hwnd, BOOL (*handler)(uiControl *, HWND, NMHDR *, LRESULT *), uiControl *c) { if (handlers[hwnd].notifyHandler != NULL) - implbug(L"already registered a WM_NOTIFY handler to window handle %p", hwnd); + uiprivImplBug("already registered a WM_NOTIFY handler to window handle %p", hwnd); handlers[hwnd].notifyHandler = handler; handlers[hwnd].c = c; } @@ -38,7 +39,7 @@ void uiWindowsRegisterWM_NOTIFYHandler(HWND hwnd, BOOL (*handler)(uiControl *, H void uiWindowsRegisterWM_HSCROLLHandler(HWND hwnd, BOOL (*handler)(uiControl *, HWND, WORD, LRESULT *), uiControl *c) { if (handlers[hwnd].hscrollHandler != NULL) - implbug(L"already registered a WM_HSCROLL handler to window handle %p", hwnd); + uiprivImplBug("already registered a WM_HSCROLL handler to window handle %p", hwnd); handlers[hwnd].hscrollHandler = handler; handlers[hwnd].c = c; } @@ -46,21 +47,21 @@ void uiWindowsRegisterWM_HSCROLLHandler(HWND hwnd, BOOL (*handler)(uiControl *, void uiWindowsUnregisterWM_COMMANDHandler(HWND hwnd) { if (handlers[hwnd].commandHandler == NULL) - implbug(L"window handle %p not registered to receive WM_COMMAND events", hwnd); + uiprivImplBug("window handle %p not registered to receive WM_COMMAND events", hwnd); handlers[hwnd].commandHandler = NULL; } void uiWindowsUnregisterWM_NOTIFYHandler(HWND hwnd) { if (handlers[hwnd].notifyHandler == NULL) - implbug(L"window handle %p not registered to receive WM_NOTIFY events", hwnd); + uiprivImplBug("window handle %p not registered to receive WM_NOTIFY events", hwnd); handlers[hwnd].notifyHandler = NULL; } void uiWindowsUnregisterWM_HSCROLLHandler(HWND hwnd) { if (handlers[hwnd].hscrollHandler == NULL) - implbug(L"window handle %p not registered to receive WM_HSCROLL events", hwnd); + uiprivImplBug("window handle %p not registered to receive WM_HSCROLL events", hwnd); handlers[hwnd].hscrollHandler = NULL; } @@ -130,14 +131,14 @@ static std::map wininichanges; void uiWindowsRegisterReceiveWM_WININICHANGE(HWND hwnd) { if (wininichanges[hwnd]) - implbug(L"window handle %p already subscribed to receive WM_WINICHANGEs", hwnd); + uiprivImplBug("window handle %p already subscribed to receive WM_WINICHANGEs", hwnd); wininichanges[hwnd] = true; } void uiWindowsUnregisterReceiveWM_WININICHANGE(HWND hwnd) { if (!wininichanges[hwnd]) - implbug(L"window handle %p not registered to receive WM_WININICHANGEs", hwnd); + uiprivImplBug("window handle %p not registered to receive WM_WININICHANGEs", hwnd); wininichanges[hwnd] = false; } diff --git a/windows/fontbutton.cpp b/windows/fontbutton.cpp index 323504a3..d6e5e0d8 100644 --- a/windows/fontbutton.cpp +++ b/windows/fontbutton.cpp @@ -1,5 +1,6 @@ // 14 april 2016 #include "uipriv_windows.hpp" +#include "attrstr.hpp" struct uiFontButton { uiWindowsControl c; @@ -15,7 +16,7 @@ static void uiFontButtonDestroy(uiControl *c) uiFontButton *b = uiFontButton(c); uiWindowsUnregisterWM_COMMANDHandler(b->hwnd); - destroyFontDialogParams(&(b->params)); + uiprivDestroyFontDialogParams(&(b->params)); uiWindowsEnsureDestroyWindow(b->hwnd); uiFreeControl(uiControl(b)); } @@ -24,10 +25,9 @@ static void updateFontButtonLabel(uiFontButton *b) { WCHAR *text; - text = fontDialogParamsToString(&(b->params)); - // TODO error check - SendMessageW(b->hwnd, WM_SETTEXT, 0, (LPARAM) text); - uiFree(text); + text = uiprivFontDialogParamsToString(&(b->params)); + setWindowText(b->hwnd, text); + uiprivFree(text); // changing the text might necessitate a change in the button's size uiWindowsControlMinimumSizeChanged(uiWindowsControl(b)); @@ -41,8 +41,8 @@ static BOOL onWM_COMMAND(uiControl *c, HWND hwnd, WORD code, LRESULT *lResult) if (code != BN_CLICKED) return FALSE; - parent = GetAncestor(b->hwnd, GA_ROOT); // TODO didn't we have a function for this - if (showFontDialog(parent, &(b->params))) { + parent = parentToplevel(b->hwnd); + if (uiprivShowFontDialog(parent, &(b->params))) { updateFontButtonLabel(b); (*(b->onChanged))(b, b->onChangedData); } @@ -56,7 +56,7 @@ uiWindowsControlAllDefaultsExceptDestroy(uiFontButton) // from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing #define buttonHeight 14 -static void uiFontButtonMinimumSize(uiWindowsControl *c, intmax_t *width, intmax_t *height) +static void uiFontButtonMinimumSize(uiWindowsControl *c, int *width, int *height) { uiFontButton *b = uiFontButton(c); SIZE size; @@ -87,15 +87,13 @@ static void defaultOnChanged(uiFontButton *b, void *data) // do nothing } -uiDrawTextFont *uiFontButtonFont(uiFontButton *b) +void uiFontButtonFont(uiFontButton *b, uiFontDescriptor *desc) { - // we don't own b->params.font; we have to add a reference - // we don't own b->params.familyName either; we have to copy it - return mkTextFont(b->params.font, TRUE, b->params.familyName, TRUE, b->params.size); + uiprivFontDescriptorFromIDWriteFont(b->params.font, desc); + desc->Family = toUTF8(b->params.familyName); + desc->Size = b->params.size; } -// TODO document that the Handle of a Font may not be unique - void uiFontButtonOnChanged(uiFontButton *b, void (*f)(uiFontButton *, void *), void *data) { b->onChanged = f; @@ -114,7 +112,7 @@ uiFontButton *uiNewFontButton(void) hInstance, NULL, TRUE); - loadInitialFontDialogParams(&(b->params)); + uiprivLoadInitialFontDialogParams(&(b->params)); uiWindowsRegisterWM_COMMANDHandler(b->hwnd, onWM_COMMAND, uiControl(b)); uiFontButtonOnChanged(b, defaultOnChanged, NULL); @@ -123,3 +121,8 @@ uiFontButton *uiNewFontButton(void) return b; } + +void uiFreeFontButtonFont(uiFontDescriptor *desc) +{ + uiprivFree((char *) (desc->Family)); +} diff --git a/windows/fontdialog.cpp b/windows/fontdialog.cpp index feb264f0..fa9c1d0d 100644 --- a/windows/fontdialog.cpp +++ b/windows/fontdialog.cpp @@ -1,11 +1,14 @@ // 14 april 2016 #include "uipriv_windows.hpp" +#include "attrstr.hpp" // TODOs // - quote the Choose Font sample here for reference // - the Choose Font sample defaults to Regular/Italic/Bold/Bold Italic in some case (no styles?); do we? find out what the case is // - do we set initial family and style topmost as well? // - this should probably just handle IDWriteFonts +// - localization? +// - the Sample window overlaps the groupbox in a weird way (compare to the real ChooseFont() dialog) struct fontDialog { HWND hwnd; @@ -15,7 +18,7 @@ struct fontDialog { struct fontDialogParams *params; - fontCollection *fc; + struct fontCollection *fc; RECT sampleRect; HWND sampleBox; @@ -118,7 +121,7 @@ static WCHAR *cbGetItemText(HWND cb, WPARAM item) len = SendMessageW(cb, CB_GETLBTEXTLEN, item, 0); if (len == (LRESULT) CB_ERR) logLastError(L"error getting item text length from combobox"); - text = (WCHAR *) uiAlloc((len + 1) * sizeof (WCHAR), "WCHAR[]"); + text = (WCHAR *) uiprivAlloc((len + 1) * sizeof (WCHAR), "WCHAR[]"); if (SendMessageW(cb, CB_GETLBTEXT, item, (LPARAM) text) != len) logLastError(L"error getting item text from combobox"); return text; @@ -135,7 +138,7 @@ static BOOL cbTypeToSelect(HWND cb, LRESULT *posOut, BOOL restoreAfter) text = windowText(cb); pos = SendMessageW(cb, CB_FINDSTRINGEXACT, (WPARAM) (-1), (LPARAM) text); if (pos == (LRESULT) CB_ERR) { - uiFree(text); + uiprivFree(text); return FALSE; } cbSetCurSel(cb, (WPARAM) pos); @@ -144,7 +147,7 @@ static BOOL cbTypeToSelect(HWND cb, LRESULT *posOut, BOOL restoreAfter) if (restoreAfter) if (SendMessageW(cb, WM_SETTEXT, 0, (LPARAM) text) != (LRESULT) TRUE) logLastError(L"error restoring old combobox text"); - uiFree(text); + uiprivFree(text); // and restore the selection like above // TODO isn't there a 32-bit version of this if (SendMessageW(cb, CB_SETEDITSEL, 0, MAKELPARAM(selStart, selEnd)) != (LRESULT) TRUE) @@ -166,7 +169,7 @@ static WCHAR *fontStyleName(struct fontCollection *fc, IDWriteFont *font) hr = font->GetFaceNames(&str); if (hr != S_OK) logHRESULT(L"error getting font style name for font dialog", hr); - wstr = fontCollectionCorrectString(fc, str); + wstr = uiprivFontCollectionCorrectString(fc, str); str->Release(); return wstr; } @@ -174,8 +177,7 @@ static WCHAR *fontStyleName(struct fontCollection *fc, IDWriteFont *font) static void queueRedrawSampleText(struct fontDialog *f) { // TODO TRUE? - if (InvalidateRect(f->sampleBox, NULL, TRUE) == 0) - logLastError(L"error queueing a redraw of the font dialog's sample text"); + invalidateRect(f->sampleBox, NULL, TRUE); } static void styleChanged(struct fontDialog *f) @@ -252,7 +254,7 @@ static void familyChanged(struct fontDialog *f) logHRESULT(L"error getting font for filling styles box", hr); label = fontStyleName(f->fc, font); pos = cbAddString(f->styleCombobox, label); - uiFree(label); + uiprivFree(label); cbSetItemData(f->styleCombobox, (WPARAM) pos, (LPARAM) font); if (font->GetWeight() == weight && font->GetStyle() == style && @@ -321,6 +323,7 @@ static void sizeEdited(struct fontDialog *f) wsize = windowText(f->sizeCombobox); // this is what the Choose Font dialog does; it swallows errors while the real ChooseFont() is not lenient (and only checks on OK) size = wcstod(wsize, NULL); + // TODO free wsize? I forget already if (size <= 0) // don't change on invalid size return; f->curSize = size; @@ -362,7 +365,7 @@ static void fontDialogDrawSampleText(struct fontDialog *f, ID2D1RenderTarget *rt if (hr != S_OK) exists = FALSE; if (exists) { - sample = fontCollectionCorrectString(f->fc, sampleStrings); + sample = uiprivFontCollectionCorrectString(f->fc, sampleStrings); sampleStrings->Release(); } else sample = L"The quick brown fox jumps over the lazy dog."; @@ -383,12 +386,12 @@ static void fontDialogDrawSampleText(struct fontDialog *f, ID2D1RenderTarget *rt &format); if (hr != S_OK) logHRESULT(L"error creating IDWriteTextFormat", hr); - uiFree(family); + uiprivFree(family); rect.left = 0; rect.top = 0; - rect.right = rt->GetSize().width; - rect.bottom = rt->GetSize().height; + rect.right = realGetSize(rt).width; + rect.bottom = realGetSize(rt).height; rt->DrawText(sample, wcslen(sample), format, &rect, @@ -399,7 +402,7 @@ static void fontDialogDrawSampleText(struct fontDialog *f, ID2D1RenderTarget *rt format->Release(); if (exists) - uiFree(sample); + uiprivFree(sample); black->Release(); } @@ -463,38 +466,30 @@ static struct fontDialog *beginFontDialog(HWND hwnd, LPARAM lParam) HWND samplePlacement; HRESULT hr; - f = uiNew(struct fontDialog); + f = uiprivNew(struct fontDialog); f->hwnd = hwnd; f->params = (struct fontDialogParams *) lParam; - f->familyCombobox = GetDlgItem(f->hwnd, rcFontFamilyCombobox); - if (f->familyCombobox == NULL) - logLastError(L"error getting font family combobox handle"); - f->styleCombobox = GetDlgItem(f->hwnd, rcFontStyleCombobox); - if (f->styleCombobox == NULL) - logLastError(L"error getting font style combobox handle"); - f->sizeCombobox = GetDlgItem(f->hwnd, rcFontSizeCombobox); - if (f->sizeCombobox == NULL) - logLastError(L"error getting font size combobox handle"); + f->familyCombobox = getDlgItem(f->hwnd, rcFontFamilyCombobox); + f->styleCombobox = getDlgItem(f->hwnd, rcFontStyleCombobox); + f->sizeCombobox = getDlgItem(f->hwnd, rcFontSizeCombobox); - f->fc = loadFontCollection(); + f->fc = uiprivLoadFontCollection(); nFamilies = f->fc->fonts->GetFontFamilyCount(); for (i = 0; i < nFamilies; i++) { hr = f->fc->fonts->GetFontFamily(i, &family); if (hr != S_OK) logHRESULT(L"error getting font family", hr); - wname = fontCollectionFamilyName(f->fc, family); + wname = uiprivFontCollectionFamilyName(f->fc, family); pos = cbAddString(f->familyCombobox, wname); - uiFree(wname); + uiprivFree(wname); cbSetItemData(f->familyCombobox, (WPARAM) pos, (LPARAM) family); } for (i = 0; defaultSizes[i].text != NULL; i++) cbInsertString(f->sizeCombobox, defaultSizes[i].text, (WPARAM) i); - samplePlacement = GetDlgItem(f->hwnd, rcFontSamplePlacement); - if (samplePlacement == NULL) - logLastError(L"error getting sample placement static control handle"); + samplePlacement = getDlgItem(f->hwnd, rcFontSamplePlacement); uiWindowsEnsureGetWindowRect(samplePlacement, &(f->sampleRect)); mapWindowRect(NULL, f->hwnd, &(f->sampleRect)); uiWindowsEnsureDestroyWindow(samplePlacement); @@ -508,10 +503,10 @@ static void endFontDialog(struct fontDialog *f, INT_PTR code) { wipeStylesBox(f); cbWipeAndReleaseData(f->familyCombobox); - fontCollectionFree(f->fc); + uiprivFontCollectionFree(f->fc); if (EndDialog(f->hwnd, code) == 0) logLastError(L"error ending font dialog"); - uiFree(f); + uiprivFree(f); } static INT_PTR tryFinishDialog(struct fontDialog *f, WPARAM wParam) @@ -525,13 +520,13 @@ static INT_PTR tryFinishDialog(struct fontDialog *f, WPARAM wParam) } // OK - destroyFontDialogParams(f->params); + uiprivDestroyFontDialogParams(f->params); f->params->font = (IDWriteFont *) cbGetItemData(f->styleCombobox, f->curStyle); // we need to save font from being destroyed with the combobox f->params->font->AddRef(); f->params->size = f->curSize; family = (IDWriteFontFamily *) cbGetItemData(f->familyCombobox, f->curFamily); - f->params->familyName = fontCollectionFamilyName(f->fc, family); + f->params->familyName = uiprivFontCollectionFamilyName(f->fc, family); f->params->styleName = fontStyleName(f->fc, f->params->font); endFontDialog(f, 2); return TRUE; @@ -596,9 +591,106 @@ static INT_PTR CALLBACK fontDialogDlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, L return FALSE; } -BOOL showFontDialog(HWND parent, struct fontDialogParams *params) +// because Windows doesn't really support resources in static libraries, we have to embed this directly; oh well +/* +// this is for our custom DirectWrite-based font dialog (see fontdialog.cpp) +// this is based on the "New Font Dialog with Syslink" in Microsoft's font.dlg +// LONGTERM look at localization +// LONGTERM make it look tighter and nicer like the real one, including the actual heights of the font family and style comboboxes +rcFontDialog DIALOGEX 13, 54, 243, 200 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_3DLOOK +CAPTION "Font" +FONT 9, "Segoe UI" +BEGIN + LTEXT "&Font:", -1, 7, 7, 98, 9 + COMBOBOX rcFontFamilyCombobox, 7, 16, 98, 76, + CBS_SIMPLE | CBS_AUTOHSCROLL | CBS_DISABLENOSCROLL | + CBS_SORT | WS_VSCROLL | WS_TABSTOP | CBS_HASSTRINGS + + LTEXT "Font st&yle:", -1, 114, 7, 74, 9 + COMBOBOX rcFontStyleCombobox, 114, 16, 74, 76, + CBS_SIMPLE | CBS_AUTOHSCROLL | CBS_DISABLENOSCROLL | + WS_VSCROLL | WS_TABSTOP | CBS_HASSTRINGS + + LTEXT "&Size:", -1, 198, 7, 36, 9 + COMBOBOX rcFontSizeCombobox, 198, 16, 36, 76, + CBS_SIMPLE | CBS_AUTOHSCROLL | CBS_DISABLENOSCROLL | + CBS_SORT | WS_VSCROLL | WS_TABSTOP | CBS_HASSTRINGS + + GROUPBOX "Sample", -1, 7, 97, 227, 70, WS_GROUP + CTEXT "AaBbYyZz", rcFontSamplePlacement, 9, 106, 224, 60, SS_NOPREFIX | NOT WS_VISIBLE + + DEFPUSHBUTTON "OK", IDOK, 141, 181, 45, 14, WS_GROUP + PUSHBUTTON "Cancel", IDCANCEL, 190, 181, 45, 14, WS_GROUP +END +*/ +static const uint8_t data_rcFontDialog[] = { + 0x01, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xC4, 0x00, 0xC8, 0x80, + 0x0A, 0x00, 0x0D, 0x00, 0x36, 0x00, 0xF3, 0x00, + 0xC8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, + 0x6F, 0x00, 0x6E, 0x00, 0x74, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x01, 0x53, 0x00, + 0x65, 0x00, 0x67, 0x00, 0x6F, 0x00, 0x65, 0x00, + 0x20, 0x00, 0x55, 0x00, 0x49, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x50, 0x07, 0x00, 0x07, 0x00, + 0x62, 0x00, 0x09, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x82, 0x00, 0x26, 0x00, 0x46, 0x00, + 0x6F, 0x00, 0x6E, 0x00, 0x74, 0x00, 0x3A, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x41, 0x0B, 0x21, 0x50, + 0x07, 0x00, 0x10, 0x00, 0x62, 0x00, 0x4C, 0x00, + 0xE8, 0x03, 0x00, 0x00, 0xFF, 0xFF, 0x85, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50, + 0x72, 0x00, 0x07, 0x00, 0x4A, 0x00, 0x09, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x82, 0x00, + 0x46, 0x00, 0x6F, 0x00, 0x6E, 0x00, 0x74, 0x00, + 0x20, 0x00, 0x73, 0x00, 0x74, 0x00, 0x26, 0x00, + 0x79, 0x00, 0x6C, 0x00, 0x65, 0x00, 0x3A, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x41, 0x0A, 0x21, 0x50, + 0x72, 0x00, 0x10, 0x00, 0x4A, 0x00, 0x4C, 0x00, + 0xE9, 0x03, 0x00, 0x00, 0xFF, 0xFF, 0x85, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50, + 0xC6, 0x00, 0x07, 0x00, 0x24, 0x00, 0x09, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x82, 0x00, + 0x26, 0x00, 0x53, 0x00, 0x69, 0x00, 0x7A, 0x00, + 0x65, 0x00, 0x3A, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x41, 0x0B, 0x21, 0x50, 0xC6, 0x00, 0x10, 0x00, + 0x24, 0x00, 0x4C, 0x00, 0xEA, 0x03, 0x00, 0x00, + 0xFF, 0xFF, 0x85, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x02, 0x50, 0x07, 0x00, 0x61, 0x00, + 0xE3, 0x00, 0x46, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x80, 0x00, 0x53, 0x00, 0x61, 0x00, + 0x6D, 0x00, 0x70, 0x00, 0x6C, 0x00, 0x65, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x02, 0x40, + 0x09, 0x00, 0x6A, 0x00, 0xE0, 0x00, 0x3C, 0x00, + 0xEB, 0x03, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x42, 0x00, 0x62, 0x00, + 0x59, 0x00, 0x79, 0x00, 0x5A, 0x00, 0x7A, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x50, + 0x8D, 0x00, 0xB5, 0x00, 0x2D, 0x00, 0x0E, 0x00, + 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, + 0x4F, 0x00, 0x4B, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x50, 0xBE, 0x00, 0xB5, 0x00, + 0x2D, 0x00, 0x0E, 0x00, 0x02, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x80, 0x00, 0x43, 0x00, 0x61, 0x00, + 0x6E, 0x00, 0x63, 0x00, 0x65, 0x00, 0x6C, 0x00, + 0x00, 0x00, 0x00, 0x00, +}; +static_assert(ARRAYSIZE(data_rcFontDialog) == 476, "wrong size for resource rcFontDialog"); + +BOOL uiprivShowFontDialog(HWND parent, struct fontDialogParams *params) { - switch (DialogBoxParamW(hInstance, MAKEINTRESOURCE(rcFontDialog), parent, fontDialogDlgProc, (LPARAM) params)) { + switch (DialogBoxIndirectParamW(hInstance, (const DLGTEMPLATE *) data_rcFontDialog, parent, fontDialogDlgProc, (LPARAM) params)) { case 1: // cancel return FALSE; case 2: // ok @@ -628,7 +720,7 @@ static IDWriteFontFamily *tryFindFamily(IDWriteFontCollection *fc, const WCHAR * return family; } -void loadInitialFontDialogParams(struct fontDialogParams *params) +void uiprivLoadInitialFontDialogParams(struct fontDialogParams *params) { struct fontCollection *fc; IDWriteFontFamily *family; @@ -641,7 +733,7 @@ void loadInitialFontDialogParams(struct fontDialogParams *params) // If Arial isn't found, we'll use Helvetica and then MS Sans Serif as fallbacks, and if not, we'll just grab the first font family in the collection. // We need the correct localized name for Regular (and possibly Arial too? let's say yes to be safe), so let's grab the strings from DirectWrite instead of hardcoding them. - fc = loadFontCollection(); + fc = uiprivLoadFontCollection(); family = tryFindFamily(fc->fonts, L"Arial"); if (family == NULL) { family = tryFindFamily(fc->fonts, L"Helvetica"); @@ -666,27 +758,27 @@ void loadInitialFontDialogParams(struct fontDialogParams *params) params->font = font; params->size = 10; - params->familyName = fontCollectionFamilyName(fc, family); + params->familyName = uiprivFontCollectionFamilyName(fc, family); params->styleName = fontStyleName(fc, font); // don't release font; we still need it family->Release(); - fontCollectionFree(fc); + uiprivFontCollectionFree(fc); } -void destroyFontDialogParams(struct fontDialogParams *params) +void uiprivDestroyFontDialogParams(struct fontDialogParams *params) { params->font->Release(); - uiFree(params->familyName); - uiFree(params->styleName); + uiprivFree(params->familyName); + uiprivFree(params->styleName); } -WCHAR *fontDialogParamsToString(struct fontDialogParams *params) +WCHAR *uiprivFontDialogParamsToString(struct fontDialogParams *params) { WCHAR *text; // TODO dynamically allocate - text = (WCHAR *) uiAlloc(512 * sizeof (WCHAR), "WCHAR[]"); + text = (WCHAR *) uiprivAlloc(512 * sizeof (WCHAR), "WCHAR[]"); _snwprintf(text, 512, L"%s %s %g", params->familyName, params->styleName, diff --git a/windows/fontmatch.cpp b/windows/fontmatch.cpp new file mode 100644 index 00000000..73f29543 --- /dev/null +++ b/windows/fontmatch.cpp @@ -0,0 +1,61 @@ +// 11 march 2018 +#include "uipriv_windows.hpp" +#include "attrstr.hpp" + +// TODO should be const but then I can't operator[] on it; the real solution is to find a way to do designated array initializers in C++11 but I do not know enough C++ voodoo to make it work (it is possible but no one else has actually done it before) +static std::map dwriteItalics = { + { uiTextItalicNormal, DWRITE_FONT_STYLE_NORMAL }, + { uiTextItalicOblique, DWRITE_FONT_STYLE_OBLIQUE }, + { uiTextItalicItalic, DWRITE_FONT_STYLE_ITALIC }, +}; + +// TODO should be const but then I can't operator[] on it; the real solution is to find a way to do designated array initializers in C++11 but I do not know enough C++ voodoo to make it work (it is possible but no one else has actually done it before) +static std::map dwriteStretches = { + { uiTextStretchUltraCondensed, DWRITE_FONT_STRETCH_ULTRA_CONDENSED }, + { uiTextStretchExtraCondensed, DWRITE_FONT_STRETCH_EXTRA_CONDENSED }, + { uiTextStretchCondensed, DWRITE_FONT_STRETCH_CONDENSED }, + { uiTextStretchSemiCondensed, DWRITE_FONT_STRETCH_SEMI_CONDENSED }, + { uiTextStretchNormal, DWRITE_FONT_STRETCH_NORMAL }, + { uiTextStretchSemiExpanded, DWRITE_FONT_STRETCH_SEMI_EXPANDED }, + { uiTextStretchExpanded, DWRITE_FONT_STRETCH_EXPANDED }, + { uiTextStretchExtraExpanded, DWRITE_FONT_STRETCH_EXTRA_EXPANDED }, + { uiTextStretchUltraExpanded, DWRITE_FONT_STRETCH_ULTRA_EXPANDED }, +}; + +// for the most part, DirectWrite weights correlate to ours +// the differences: +// - Minimum — libui: 0, DirectWrite: 1 +// - Maximum — libui: 1000, DirectWrite: 999 +// TODO figure out what to do about this shorter range (the actual major values are the same (but with different names), so it's just a range issue) +DWRITE_FONT_WEIGHT uiprivWeightToDWriteWeight(uiTextWeight w) +{ + return (DWRITE_FONT_WEIGHT) w; +} + +DWRITE_FONT_STYLE uiprivItalicToDWriteStyle(uiTextItalic i) +{ + return dwriteItalics[i]; +} + +DWRITE_FONT_STRETCH uiprivStretchToDWriteStretch(uiTextStretch s) +{ + return dwriteStretches[s]; +} + +void uiprivFontDescriptorFromIDWriteFont(IDWriteFont *font, uiFontDescriptor *uidesc) +{ + DWRITE_FONT_STYLE dwitalic; + DWRITE_FONT_STRETCH dwstretch; + + dwitalic = font->GetStyle(); + // TODO reverse the above misalignment if it is corrected + uidesc->Weight = (uiTextWeight) (font->GetWeight()); + dwstretch = font->GetStretch(); + + for (uidesc->Italic = uiTextItalicNormal; uidesc->Italic < uiTextItalicItalic; uidesc->Italic++) + if (dwriteItalics[uidesc->Italic] == dwitalic) + break; + for (uidesc->Stretch = uiTextStretchUltraCondensed; uidesc->Stretch < uiTextStretchUltraExpanded; uidesc->Stretch++) + if (dwriteStretches[uidesc->Stretch] == dwstretch) + break; +} diff --git a/windows/form.cpp b/windows/form.cpp new file mode 100644 index 00000000..ed194671 --- /dev/null +++ b/windows/form.cpp @@ -0,0 +1,319 @@ +// 8 june 2016 +#include "uipriv_windows.hpp" + +struct formChild { + uiControl *c; + HWND label; + int stretchy; + int height; +}; + +struct uiForm { + uiWindowsControl c; + HWND hwnd; + std::vector *controls; + int padded; +}; + +static void formPadding(uiForm *f, int *xpadding, int *ypadding) +{ + uiWindowsSizing sizing; + + *xpadding = 0; + *ypadding = 0; + if (f->padded) { + uiWindowsGetSizing(f->hwnd, &sizing); + uiWindowsSizingStandardPadding(&sizing, xpadding, ypadding); + } +} + +// via http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing +#define labelHeight 8 +#define labelYOffset 3 + +static void formRelayout(uiForm *f) +{ + RECT r; + int x, y, width, height; + int xpadding, ypadding; + int nStretchy; + int labelwid, stretchyht; + int thiswid; + int i; + int minimumWidth, minimumHeight; + uiWindowsSizing sizing; + int labelht, labelyoff; + int nVisible; + + if (f->controls->size() == 0) + return; + + uiWindowsEnsureGetClientRect(f->hwnd, &r); + x = r.left; + y = r.top; + width = r.right - r.left; + height = r.bottom - r.top; + + // 0) get this Form's padding + formPadding(f, &xpadding, &ypadding); + + // 1) get width of labels and height of non-stretchy controls + // this will tell us how much space will be left for controls + labelwid = 0; + stretchyht = height; + nStretchy = 0; + nVisible = 0; + for (struct formChild &fc : *(f->controls)) { + if (!uiControlVisible(fc.c)) { + ShowWindow(fc.label, SW_HIDE); + continue; + } + ShowWindow(fc.label, SW_SHOW); + nVisible++; + thiswid = uiWindowsWindowTextWidth(fc.label); + if (labelwid < thiswid) + labelwid = thiswid; + if (fc.stretchy) { + nStretchy++; + continue; + } + uiWindowsControlMinimumSize(uiWindowsControl(fc.c), &minimumWidth, &minimumHeight); + fc.height = minimumHeight; + stretchyht -= minimumHeight; + } + if (nVisible == 0) // nothing to do + return; + + // 2) inset the available rect by the needed padding + width -= xpadding; + height -= (nVisible - 1) * ypadding; + stretchyht -= (nVisible - 1) * ypadding; + + // 3) now get the width of controls and the height of stretchy controls + width -= labelwid; + if (nStretchy != 0) { + stretchyht /= nStretchy; + for (struct formChild &fc : *(f->controls)) { + if (!uiControlVisible(fc.c)) + continue; + if (fc.stretchy) + fc.height = stretchyht; + } + } + + // 4) get the y offset + labelyoff = labelYOffset; + uiWindowsGetSizing(f->hwnd, &sizing); + uiWindowsSizingDlgUnitsToPixels(&sizing, NULL, &labelyoff); + + // 5) now we can position controls + // first, make relative to the top-left corner of the container + // also prefer left alignment on Windows + x = labelwid + xpadding; + y = 0; + for (const struct formChild &fc : *(f->controls)) { + if (!uiControlVisible(fc.c)) + continue; + labelht = labelHeight; + uiWindowsGetSizing(f->hwnd, &sizing); + uiWindowsSizingDlgUnitsToPixels(&sizing, NULL, &labelht); + uiWindowsEnsureMoveWindowDuringResize(fc.label, 0, y + labelyoff - sizing.InternalLeading, labelwid, labelht); + uiWindowsEnsureMoveWindowDuringResize((HWND) uiControlHandle(fc.c), x, y, width, fc.height); + y += fc.height + ypadding; + } +} + +static void uiFormDestroy(uiControl *c) +{ + uiForm *f = uiForm(c); + + for (const struct formChild &fc : *(f->controls)) { + uiControlSetParent(fc.c, NULL); + uiControlDestroy(fc.c); + uiWindowsEnsureDestroyWindow(fc.label); + } + delete f->controls; + uiWindowsEnsureDestroyWindow(f->hwnd); + uiFreeControl(uiControl(f)); +} + +uiWindowsControlDefaultHandle(uiForm) +uiWindowsControlDefaultParent(uiForm) +uiWindowsControlDefaultSetParent(uiForm) +uiWindowsControlDefaultToplevel(uiForm) +uiWindowsControlDefaultVisible(uiForm) +uiWindowsControlDefaultShow(uiForm) +uiWindowsControlDefaultHide(uiForm) +uiWindowsControlDefaultEnabled(uiForm) +uiWindowsControlDefaultEnable(uiForm) +uiWindowsControlDefaultDisable(uiForm) + +static void uiFormSyncEnableState(uiWindowsControl *c, int enabled) +{ + uiForm *f = uiForm(c); + + if (uiWindowsShouldStopSyncEnableState(uiWindowsControl(f), enabled)) + return; + for (const struct formChild &fc : *(f->controls)) + uiWindowsControlSyncEnableState(uiWindowsControl(fc.c), enabled); +} + +uiWindowsControlDefaultSetParentHWND(uiForm) + +static void uiFormMinimumSize(uiWindowsControl *c, int *width, int *height) +{ + uiForm *f = uiForm(c); + int xpadding, ypadding; + int nStretchy; + // these two contain the largest minimum width and height of all stretchy controls in the form + // all stretchy controls will use this value to determine the final minimum size + int maxLabelWidth, maxControlWidth; + int maxStretchyHeight; + int labelwid; + int i; + int minimumWidth, minimumHeight; + int nVisible; + uiWindowsSizing sizing; + + *width = 0; + *height = 0; + if (f->controls->size() == 0) + return; + + // 0) get this Form's padding + formPadding(f, &xpadding, &ypadding); + + // 1) determine the longest width of all controls and labels; add in the height of non-stretchy controls and get (but not add in) the largest heights of stretchy controls + // we still add in like direction of stretchy controls + nStretchy = 0; + maxLabelWidth = 0; + maxControlWidth = 0; + maxStretchyHeight = 0; + nVisible = 0; + for (const struct formChild &fc : *(f->controls)) { + if (!uiControlVisible(fc.c)) + continue; + nVisible++; + labelwid = uiWindowsWindowTextWidth(fc.label); + if (maxLabelWidth < labelwid) + maxLabelWidth = labelwid; + uiWindowsControlMinimumSize(uiWindowsControl(fc.c), &minimumWidth, &minimumHeight); + if (fc.stretchy) { + nStretchy++; + if (maxStretchyHeight < minimumHeight) + maxStretchyHeight = minimumHeight; + } + if (maxControlWidth < minimumWidth) + maxControlWidth = minimumWidth; + if (!fc.stretchy) + *height += minimumHeight; + } + if (nVisible == 0) // nothing to show; return 0x0 + return; + *width += maxLabelWidth + maxControlWidth; + + // 2) outset the desired rect with the needed padding + *width += xpadding; + *height += (nVisible - 1) * ypadding; + + // 3) and now we can add in stretchy controls + *height += nStretchy * maxStretchyHeight; +} + +static void uiFormMinimumSizeChanged(uiWindowsControl *c) +{ + uiForm *f = uiForm(c); + + if (uiWindowsControlTooSmall(uiWindowsControl(f))) { + uiWindowsControlContinueMinimumSizeChanged(uiWindowsControl(f)); + return; + } + formRelayout(f); +} + +uiWindowsControlDefaultLayoutRect(uiForm) +uiWindowsControlDefaultAssignControlIDZOrder(uiForm) + +static void uiFormChildVisibilityChanged(uiWindowsControl *c) +{ + // TODO eliminate the redundancy + uiWindowsControlMinimumSizeChanged(c); +} + +static void formArrangeChildren(uiForm *f) +{ + LONG_PTR controlID; + HWND insertAfter; + int i; + + controlID = 100; + insertAfter = NULL; + for (const struct formChild &fc : *(f->controls)) { + // TODO assign label ID and z-order + uiWindowsControlAssignControlIDZOrder(uiWindowsControl(fc.c), &controlID, &insertAfter); + } +} + +void uiFormAppend(uiForm *f, const char *label, uiControl *c, int stretchy) +{ + struct formChild fc; + WCHAR *wlabel; + + fc.c = c; + wlabel = toUTF16(label); + fc.label = uiWindowsEnsureCreateControlHWND(0, + L"STATIC", wlabel, + SS_LEFT | SS_NOPREFIX, + hInstance, NULL, + TRUE); + uiprivFree(wlabel); + uiWindowsEnsureSetParentHWND(fc.label, f->hwnd); + fc.stretchy = stretchy; + uiControlSetParent(fc.c, uiControl(f)); + uiWindowsControlSetParentHWND(uiWindowsControl(fc.c), f->hwnd); + f->controls->push_back(fc); + formArrangeChildren(f); + uiWindowsControlMinimumSizeChanged(uiWindowsControl(f)); +} + +void uiFormDelete(uiForm *f, int index) +{ + struct formChild fc; + + fc = (*(f->controls))[index]; + uiControlSetParent(fc.c, NULL); + uiWindowsControlSetParentHWND(uiWindowsControl(fc.c), NULL); + uiWindowsEnsureDestroyWindow(fc.label); + f->controls->erase(f->controls->begin() + index); + formArrangeChildren(f); + uiWindowsControlMinimumSizeChanged(uiWindowsControl(f)); +} + +int uiFormPadded(uiForm *f) +{ + return f->padded; +} + +void uiFormSetPadded(uiForm *f, int padded) +{ + f->padded = padded; + uiWindowsControlMinimumSizeChanged(uiWindowsControl(f)); +} + +static void onResize(uiWindowsControl *c) +{ + formRelayout(uiForm(c)); +} + +uiForm *uiNewForm(void) +{ + uiForm *f; + + uiWindowsNewControl(uiForm, f); + + f->hwnd = uiWindowsMakeContainer(uiWindowsControl(f), onResize); + + f->controls = new std::vector; + + return f; +} diff --git a/windows/graphemes.cpp b/windows/graphemes.cpp new file mode 100644 index 00000000..c11dd203 --- /dev/null +++ b/windows/graphemes.cpp @@ -0,0 +1,60 @@ +// 25 may 2016 +#include "uipriv_windows.hpp" +#include "attrstr.hpp" + +// We could use CharNextW() to generate grapheme cluster boundaries, but it doesn't handle surrogate pairs properly (see http://archives.miloush.net/michkap/archive/2008/12/16/9223301.html). +// We could also use Uniscribe (see http://archives.miloush.net/michkap/archive/2005/01/14/352802.html, http://www.catch22.net/tuts/uniscribe-mysteries, http://www.catch22.net/tuts/keyboard-navigation, and https://maxradi.us/documents/uniscribe/), but its rules for buffer sizes is convoluted. +// Let's just deal with the CharNextW() bug. + +int uiprivGraphemesTakesUTF16(void) +{ + return 1; +} + +uiprivGraphemes *uiprivNewGraphemes(void *s, size_t len) +{ + uiprivGraphemes *g; + WCHAR *str; + size_t *pPTG, *pGTP; + + g = uiprivNew(uiprivGraphemes); + + g->len = 0; + str = (WCHAR *) s; + while (*str != L'\0') { + g->len++; + str = CharNextW(str); + // no need to worry about surrogates if we're just counting + } + + g->pointsToGraphemes = (size_t *) uiprivAlloc((len + 1) * sizeof (size_t), "size_t[] (graphemes)"); + g->graphemesToPoints = (size_t *) uiprivAlloc((g->len + 1) * sizeof (size_t), "size_t[] (graphemes)"); + + pPTG = g->pointsToGraphemes; + pGTP = g->graphemesToPoints; + str = (WCHAR *) s; + while (*str != L'\0') { + WCHAR *next, *p; + ptrdiff_t nextoff; + + // as part of the bug, we need to make sure we only call CharNextW() on low halves, otherwise it'll return the same low half forever + nextoff = 0; + if (IS_HIGH_SURROGATE(*str)) + nextoff = 1; + next = CharNextW(str + nextoff); + if (IS_LOW_SURROGATE(*next)) + next--; + + *pGTP = pPTG - g->pointsToGraphemes; + for (p = str; p < next; p++) + *pPTG++ = pGTP - g->graphemesToPoints; + pGTP++; + + str = next; + } + // and handle the last item for the end of the string + *pGTP = pPTG - g->pointsToGraphemes; + *pPTG = pGTP - g->graphemesToPoints; + + return g; +} diff --git a/windows/grid.cpp b/windows/grid.cpp new file mode 100644 index 00000000..cac87aff --- /dev/null +++ b/windows/grid.cpp @@ -0,0 +1,658 @@ +// 10 june 2016 +#include "uipriv_windows.hpp" + +// TODO compare with GTK+: +// - what happens if you call InsertAt() twice? +// - what happens if you call Append() twice? + +// TODOs +// - the Assorted page has clipping and repositioning issues + +struct gridChild { + uiControl *c; + int left; + int top; + int xspan; + int yspan; + int hexpand; + uiAlign halign; + int vexpand; + uiAlign valign; + + // have these here so they don't need to be reallocated each relayout + int finalx, finaly; + int finalwidth, finalheight; + int minwidth, minheight; +}; + +struct uiGrid { + uiWindowsControl c; + HWND hwnd; + std::vector *children; + std::map *indexof; + int padded; + + int xmin, ymin; + int xmax, ymax; +}; + +static bool gridRecomputeMinMax(uiGrid *g) +{ + bool first = true; + + for (struct gridChild *gc : *(g->children)) { + // this is important; we want g->xmin/g->ymin to satisfy gridLayoutData::visibleRow()/visibleColumn() + if (!uiControlVisible(gc->c)) + continue; + if (first) { + g->xmin = gc->left; + g->ymin = gc->top; + g->xmax = gc->left + gc->xspan; + g->ymax = gc->top + gc->yspan; + first = false; + continue; + } + if (g->xmin > gc->left) + g->xmin = gc->left; + if (g->ymin > gc->top) + g->ymin = gc->top; + if (g->xmax < (gc->left + gc->xspan)) + g->xmax = gc->left + gc->xspan; + if (g->ymax < (gc->top + gc->yspan)) + g->ymax = gc->top + gc->yspan; + } + return first != false; +} + +#define xcount(g) ((g)->xmax - (g)->xmin) +#define ycount(g) ((g)->ymax - (g)->ymin) +#define toxindex(g, x) ((x) - (g)->xmin) +#define toyindex(g, y) ((y) - (g)->ymin) + +class gridLayoutData { + int ycount; +public: + int **gg; // topological map gg[y][x] = control index + int *colwidths; + int *rowheights; + bool *hexpand; + bool *vexpand; + int nVisibleRows; + int nVisibleColumns; + + bool noVisible; + + gridLayoutData(uiGrid *g) + { + size_t i; + int x, y; + + this->noVisible = gridRecomputeMinMax(g); + + this->gg = new int *[ycount(g)]; + for (y = 0; y < ycount(g); y++) { + this->gg[y] = new int[xcount(g)]; + for (x = 0; x < xcount(g); x++) + this->gg[y][x] = -1; + } + + for (i = 0; i < g->children->size(); i++) { + struct gridChild *gc; + + gc = (*(g->children))[i]; + if (!uiControlVisible(gc->c)) + continue; + for (y = gc->top; y < gc->top + gc->yspan; y++) + for (x = gc->left; x < gc->left + gc->xspan; x++) + this->gg[toyindex(g, y)][toxindex(g, x)] = i; + } + + this->colwidths = new int[xcount(g)]; + ZeroMemory(this->colwidths, xcount(g) * sizeof (int)); + this->rowheights = new int[ycount(g)]; + ZeroMemory(this->rowheights, ycount(g) * sizeof (int)); + this->hexpand = new bool[xcount(g)]; + ZeroMemory(this->hexpand, xcount(g) * sizeof (bool)); + this->vexpand = new bool[ycount(g)]; + ZeroMemory(this->vexpand, ycount(g) * sizeof (bool)); + + this->ycount = ycount(g); + + // if a row or column only contains emptys and spanning cells of a opposite-direction spannings, it is invisible and should not be considered for padding amount calculations + // note that the first row and column will always be visible because gridRecomputeMinMax() computed a smallest fitting rectangle + if (this->noVisible) + return; + this->nVisibleRows = 0; + for (y = 0; y < this->ycount; y++) + if (this->visibleRow(g, y)) + this->nVisibleRows++; + this->nVisibleColumns = 0; + for (x = 0; x < xcount(g); x++) + if (this->visibleColumn(g, x)) + this->nVisibleColumns++; + } + + ~gridLayoutData() + { + size_t y; + + delete[] this->hexpand; + delete[] this->vexpand; + delete[] this->colwidths; + delete[] this->rowheights; + for (y = 0; y < this->ycount; y++) + delete[] this->gg[y]; + delete[] this->gg; + } + + bool visibleRow(uiGrid *g, int y) + { + int x; + struct gridChild *gc; + + for (x = 0; x < xcount(g); x++) + if (this->gg[y][x] != -1) { + gc = (*(g->children))[this->gg[y][x]]; + if (gc->yspan == 1 || gc->top - g->ymin == y) + return true; + } + return false; + } + + bool visibleColumn(uiGrid *g, int x) + { + int y; + struct gridChild *gc; + + for (y = 0; y < this->ycount; y++) + if (this->gg[y][x] != -1) { + gc = (*(g->children))[this->gg[y][x]]; + if (gc->xspan == 1 || gc->left - g->xmin == x) + return true; + } + return false; + } +}; + +static void gridPadding(uiGrid *g, int *xpadding, int *ypadding) +{ + uiWindowsSizing sizing; + + *xpadding = 0; + *ypadding = 0; + if (g->padded) { + uiWindowsGetSizing(g->hwnd, &sizing); + uiWindowsSizingStandardPadding(&sizing, xpadding, ypadding); + } +} + +static void gridRelayout(uiGrid *g) +{ + RECT r; + int x, y, width, height; + gridLayoutData *ld; + int xpadding, ypadding; + int ix, iy; + int iwidth, iheight; + int i; + struct gridChild *gc; + int nhexpand, nvexpand; + + if (g->children->size() == 0) + return; // nothing to do + + uiWindowsEnsureGetClientRect(g->hwnd, &r); + x = r.left; + y = r.top; + width = r.right - r.left; + height = r.bottom - r.top; + + gridPadding(g, &xpadding, &ypadding); + ld = new gridLayoutData(g); + if (ld->noVisible) { // nothing to do + delete ld; + return; + } + + // 0) discount padding from width/height + width -= (ld->nVisibleColumns - 1) * xpadding; + height -= (ld->nVisibleRows - 1) * ypadding; + + // 1) compute colwidths and rowheights before handling expansion + // we only count non-spanning controls to avoid weirdness + for (iy = 0; iy < ycount(g); iy++) + for (ix = 0; ix < xcount(g); ix++) { + i = ld->gg[iy][ix]; + if (i == -1) + continue; + gc = (*(g->children))[i]; + uiWindowsControlMinimumSize(uiWindowsControl(gc->c), &iwidth, &iheight); + if (gc->xspan == 1) + if (ld->colwidths[ix] < iwidth) + ld->colwidths[ix] = iwidth; + if (gc->yspan == 1) + if (ld->rowheights[iy] < iheight) + ld->rowheights[iy] = iheight; + // save these for step 6 + gc->minwidth = iwidth; + gc->minheight = iheight; + } + + // 2) figure out which rows/columns expand but not span + // we need to know which expanding rows/columns don't span before we can handle the ones that do + for (i = 0; i < g->children->size(); i++) { + gc = (*(g->children))[i]; + if (!uiControlVisible(gc->c)) + continue; + if (gc->hexpand && gc->xspan == 1) + ld->hexpand[toxindex(g, gc->left)] = true; + if (gc->vexpand && gc->yspan == 1) + ld->vexpand[toyindex(g, gc->top)] = true; + } + + // 3) figure out which rows/columns expand that do span + // the way we handle this is simple: if none of the spanned rows/columns expand, make all rows/columns expand + for (i = 0; i < g->children->size(); i++) { + gc = (*(g->children))[i]; + if (!uiControlVisible(gc->c)) + continue; + if (gc->hexpand && gc->xspan != 1) { + bool doit = true; + + for (ix = gc->left; ix < gc->left + gc->xspan; ix++) + if (ld->hexpand[toxindex(g, ix)]) { + doit = false; + break; + } + if (doit) + for (ix = gc->left; ix < gc->left + gc->xspan; ix++) + ld->hexpand[toxindex(g, ix)] = true; + } + if (gc->vexpand && gc->yspan != 1) { + bool doit = true; + + for (iy = gc->top; iy < gc->top + gc->yspan; iy++) + if (ld->vexpand[toyindex(g, iy)]) { + doit = false; + break; + } + if (doit) + for (iy = gc->top; iy < gc->top + gc->yspan; iy++) + ld->vexpand[toyindex(g, iy)] = true; + } + } + + // 4) compute and assign expanded widths/heights + nhexpand = 0; + nvexpand = 0; + for (i = 0; i < xcount(g); i++) + if (ld->hexpand[i]) + nhexpand++; + else + width -= ld->colwidths[i]; + for (i = 0; i < ycount(g); i++) + if (ld->vexpand[i]) + nvexpand++; + else + height -= ld->rowheights[i]; + for (i = 0; i < xcount(g); i++) + if (ld->hexpand[i]) + ld->colwidths[i] = width / nhexpand; + for (i = 0; i < ycount(g); i++) + if (ld->vexpand[i]) + ld->rowheights[i] = height / nvexpand; + + // 5) reset the final coordinates for the next step + for (i = 0; i < g->children->size(); i++) { + gc = (*(g->children))[i]; + if (!uiControlVisible(gc->c)) + continue; + gc->finalx = 0; + gc->finaly = 0; + gc->finalwidth = 0; + gc->finalheight = 0; + } + + // 6) compute cell positions and sizes + for (iy = 0; iy < ycount(g); iy++) { + int curx; + int prev; + + curx = 0; + prev = -1; + for (ix = 0; ix < xcount(g); ix++) { + if (!ld->visibleColumn(g, ix)) + continue; + i = ld->gg[iy][ix]; + if (i != -1) { + gc = (*(g->children))[i]; + if (iy == toyindex(g, gc->top)) { // don't repeat this step if the control spans vertically + if (i != prev) + gc->finalx = curx; + else + gc->finalwidth += xpadding; + gc->finalwidth += ld->colwidths[ix]; + } + } + curx += ld->colwidths[ix] + xpadding; + prev = i; + } + } + for (ix = 0; ix < xcount(g); ix++) { + int cury; + int prev; + + cury = 0; + prev = -1; + for (iy = 0; iy < ycount(g); iy++) { + if (!ld->visibleRow(g, iy)) + continue; + i = ld->gg[iy][ix]; + if (i != -1) { + gc = (*(g->children))[i]; + if (ix == toxindex(g, gc->left)) { // don't repeat this step if the control spans horizontally + if (i != prev) + gc->finaly = cury; + else + gc->finalheight += ypadding; + gc->finalheight += ld->rowheights[iy]; + } + } + cury += ld->rowheights[iy] + ypadding; + prev = i; + } + } + + // 7) everything as it stands now is set for xalign == Fill yalign == Fill; set the correct alignments + // this is why we saved minwidth/minheight above + for (i = 0; i < g->children->size(); i++) { + gc = (*(g->children))[i]; + if (!uiControlVisible(gc->c)) + continue; + if (gc->halign != uiAlignFill) { + switch (gc->halign) { + case uiAlignEnd: + gc->finalx += gc->finalwidth - gc->minwidth; + break; + case uiAlignCenter: + gc->finalx += (gc->finalwidth - gc->minwidth) / 2; + break; + } + gc->finalwidth = gc->minwidth; // for all three + } + if (gc->valign != uiAlignFill) { + switch (gc->valign) { + case uiAlignEnd: + gc->finaly += gc->finalheight - gc->minheight; + break; + case uiAlignCenter: + gc->finaly += (gc->finalheight - gc->minheight) / 2; + break; + } + gc->finalheight = gc->minheight; // for all three + } + } + + // 8) and FINALLY we resize + for (iy = 0; iy < ycount(g); iy++) + for (ix = 0; ix < xcount(g); ix++) { + i = ld->gg[iy][ix]; + if (i != -1) { // treat empty cells like spaces + gc = (*(g->children))[i]; + uiWindowsEnsureMoveWindowDuringResize( + (HWND) uiControlHandle(gc->c), + gc->finalx,//TODO + x, + gc->finaly,//TODO + y, + gc->finalwidth, + gc->finalheight); + } + } + + delete ld; +} + +static void uiGridDestroy(uiControl *c) +{ + uiGrid *g = uiGrid(c); + + for (struct gridChild *gc : *(g->children)) { + uiControlSetParent(gc->c, NULL); + uiControlDestroy(gc->c); + uiprivFree(gc); + } + delete g->indexof; + delete g->children; + uiWindowsEnsureDestroyWindow(g->hwnd); + uiFreeControl(uiControl(g)); +} + +uiWindowsControlDefaultHandle(uiGrid) +uiWindowsControlDefaultParent(uiGrid) +uiWindowsControlDefaultSetParent(uiGrid) +uiWindowsControlDefaultToplevel(uiGrid) +uiWindowsControlDefaultVisible(uiGrid) +uiWindowsControlDefaultShow(uiGrid) +uiWindowsControlDefaultHide(uiGrid) +uiWindowsControlDefaultEnabled(uiGrid) +uiWindowsControlDefaultEnable(uiGrid) +uiWindowsControlDefaultDisable(uiGrid) + +static void uiGridSyncEnableState(uiWindowsControl *c, int enabled) +{ + uiGrid *g = uiGrid(c); + + if (uiWindowsShouldStopSyncEnableState(uiWindowsControl(g), enabled)) + return; + for (const struct gridChild *gc : *(g->children)) + uiWindowsControlSyncEnableState(uiWindowsControl(gc->c), enabled); +} + +uiWindowsControlDefaultSetParentHWND(uiGrid) + +static void uiGridMinimumSize(uiWindowsControl *c, int *width, int *height) +{ + uiGrid *g = uiGrid(c); + int xpadding, ypadding; + gridLayoutData *ld; + int x, y; + int i; + struct gridChild *gc; + int minwid, minht; + int colwidth, rowheight; + + *width = 0; + *height = 0; + if (g->children->size() == 0) + return; // nothing to do + + gridPadding(g, &xpadding, &ypadding); + ld = new gridLayoutData(g); + if (ld->noVisible) { // nothing to do; return 0x0 + delete ld; + return; + } + + // 1) compute colwidths and rowheights before handling expansion + // TODO put this in its own function (but careful about the spanning calculation in gridRelayout()) + for (y = 0; y < ycount(g); y++) + for (x = 0; x < xcount(g); x++) { + i = ld->gg[y][x]; + if (i == -1) + continue; + gc = (*(g->children))[i]; + uiWindowsControlMinimumSize(uiWindowsControl(gc->c), &minwid, &minht); + // allot equal space in the presence of spanning to keep things sane + if (ld->colwidths[x] < minwid / gc->xspan) + ld->colwidths[x] = minwid / gc->xspan; + if (ld->rowheights[y] < minht / gc->yspan) + ld->rowheights[y] = minht / gc->yspan; + // save these for step 6 + gc->minwidth = minwid; + gc->minheight = minht; + } + + // 2) compute total column width/row height + colwidth = 0; + rowheight = 0; + for (x = 0; x < xcount(g); x++) + colwidth += ld->colwidths[x]; + for (y = 0; y < ycount(g); y++) + rowheight += ld->rowheights[y]; + + // and that's it; just account for padding + *width = colwidth + (ld->nVisibleColumns - 1) * xpadding; + *height = rowheight + (ld->nVisibleRows - 1) * ypadding; +} + +static void uiGridMinimumSizeChanged(uiWindowsControl *c) +{ + uiGrid *g = uiGrid(c); + + if (uiWindowsControlTooSmall(uiWindowsControl(g))) { + uiWindowsControlContinueMinimumSizeChanged(uiWindowsControl(g)); + return; + } + gridRelayout(g); +} + +uiWindowsControlDefaultLayoutRect(uiGrid) +uiWindowsControlDefaultAssignControlIDZOrder(uiGrid) + +static void uiGridChildVisibilityChanged(uiWindowsControl *c) +{ + // TODO eliminate the redundancy + uiWindowsControlMinimumSizeChanged(c); +} + +// must have called gridRecomputeMinMax() first +static void gridArrangeChildren(uiGrid *g) +{ + LONG_PTR controlID; + HWND insertAfter; + gridLayoutData *ld; + bool *visited; + int x, y; + int i; + struct gridChild *gc; + + if (g->children->size() == 0) + return; // nothing to do + ld = new gridLayoutData(g); + controlID = 100; + insertAfter = NULL; + visited = new bool[g->children->size()]; + ZeroMemory(visited, g->children->size() * sizeof (bool)); + for (y = 0; y < ycount(g); y++) + for (x = 0; x < xcount(g); x++) { + i = ld->gg[y][x]; + if (i == -1) + continue; + if (visited[i]) + continue; + visited[i] = true; + gc = (*(g->children))[i]; + uiWindowsControlAssignControlIDZOrder(uiWindowsControl(gc->c), &controlID, &insertAfter); + } + delete[] visited; + delete ld; +} + +static struct gridChild *toChild(uiControl *c, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign) +{ + struct gridChild *gc; + + if (xspan < 0) + uiprivUserBug("You cannot have a negative xspan in a uiGrid cell."); + if (yspan < 0) + uiprivUserBug("You cannot have a negative yspan in a uiGrid cell."); + gc = uiprivNew(struct gridChild); + gc->c = c; + gc->xspan = xspan; + gc->yspan = yspan; + gc->hexpand = hexpand; + gc->halign = halign; + gc->vexpand = vexpand; + gc->valign = valign; + return gc; +} + +static void add(uiGrid *g, struct gridChild *gc) +{ + uiControlSetParent(gc->c, uiControl(g)); + uiWindowsControlSetParentHWND(uiWindowsControl(gc->c), g->hwnd); + g->children->push_back(gc); + (*(g->indexof))[gc->c] = g->children->size() - 1; + gridRecomputeMinMax(g); + gridArrangeChildren(g); + uiWindowsControlMinimumSizeChanged(uiWindowsControl(g)); +} + +void uiGridAppend(uiGrid *g, uiControl *c, int left, int top, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign) +{ + struct gridChild *gc; + + gc = toChild(c, xspan, yspan, hexpand, halign, vexpand, valign); + gc->left = left; + gc->top = top; + add(g, gc); +} + +// TODO decide what happens if existing is NULL +void uiGridInsertAt(uiGrid *g, uiControl *c, uiControl *existing, uiAt at, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign) +{ + struct gridChild *gc; + struct gridChild *other; + + gc = toChild(c, xspan, yspan, hexpand, halign, vexpand, valign); + other = (*(g->children))[(*(g->indexof))[existing]]; + switch (at) { + case uiAtLeading: + gc->left = other->left - gc->xspan; + gc->top = other->top; + break; + case uiAtTop: + gc->left = other->left; + gc->top = other->top - gc->yspan; + break; + case uiAtTrailing: + gc->left = other->left + other->xspan; + gc->top = other->top; + break; + case uiAtBottom: + gc->left = other->left; + gc->top = other->top + other->yspan; + break; + // TODO add error checks to ALL enums + } + add(g, gc); +} + +int uiGridPadded(uiGrid *g) +{ + return g->padded; +} + +void uiGridSetPadded(uiGrid *g, int padded) +{ + g->padded = padded; + uiWindowsControlMinimumSizeChanged(uiWindowsControl(g)); +} + +static void onResize(uiWindowsControl *c) +{ + gridRelayout(uiGrid(c)); +} + +uiGrid *uiNewGrid(void) +{ + uiGrid *g; + + uiWindowsNewControl(uiGrid, g); + + g->hwnd = uiWindowsMakeContainer(uiWindowsControl(g), onResize); + + g->children = new std::vector; + g->indexof = new std::map; + + return g; +} diff --git a/windows/group.cpp b/windows/group.cpp index 3af67acd..1a2cc6ed 100644 --- a/windows/group.cpp +++ b/windows/group.cpp @@ -89,19 +89,22 @@ static void uiGroupSyncEnableState(uiWindowsControl *c, int enabled) uiWindowsControlDefaultSetParentHWND(uiGroup) -static void uiGroupMinimumSize(uiWindowsControl *c, intmax_t *width, intmax_t *height) +static void uiGroupMinimumSize(uiWindowsControl *c, int *width, int *height) { uiGroup *g = uiGroup(c); int mx, mtop, mbottom; + int labelWidth; *width = 0; *height = 0; if (g->child != NULL) uiWindowsControlMinimumSize(uiWindowsControl(g->child), width, height); + labelWidth = uiWindowsWindowTextWidth(g->hwnd); + if (*width < labelWidth) // don't clip the label; it doesn't ellipsize + *width = labelWidth; groupMargins(g, &mx, &mtop, &mbottom); *width += 2 * mx; *height += mtop + mbottom; - // TODO label width? and when? } static void uiGroupMinimumSizeChanged(uiWindowsControl *c) @@ -118,6 +121,12 @@ static void uiGroupMinimumSizeChanged(uiWindowsControl *c) uiWindowsControlDefaultLayoutRect(uiGroup) uiWindowsControlDefaultAssignControlIDZOrder(uiGroup) +static void uiGroupChildVisibilityChanged(uiWindowsControl *c) +{ + // TODO eliminate the redundancy + uiWindowsControlMinimumSizeChanged(c); +} + char *uiGroupTitle(uiGroup *g) { return uiWindowsWindowText(g->hwnd); @@ -159,18 +168,25 @@ void uiGroupSetMargined(uiGroup *g, int margined) static LRESULT CALLBACK groupSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) { uiGroup *g = uiGroup(dwRefData); + WINDOWPOS *wp = (WINDOWPOS *) lParam; + MINMAXINFO *mmi = (MINMAXINFO *) lParam; + int minwid, minht; LRESULT lResult; if (handleParentMessages(hwnd, uMsg, wParam, lParam, &lResult) != FALSE) return lResult; switch (uMsg) { case WM_WINDOWPOSCHANGED: - // TODO check - // TODO add check in container.c + if ((wp->flags & SWP_NOSIZE) != 0) + break; groupRelayout(g); - // TODO is this right? - break; - // TODO WM_GETMINMAXINFO + return 0; + case WM_GETMINMAXINFO: + lResult = DefWindowProcW(hwnd, uMsg, wParam, lParam); + uiWindowsControlMinimumSize(uiWindowsControl(g), &minwid, &minht); + mmi->ptMinTrackSize.x = minwid; + mmi->ptMinTrackSize.y = minht; + return lResult; case WM_NCDESTROY: if (RemoveWindowSubclass(hwnd, groupSubProc, uIdSubclass) == FALSE) logLastError(L"error removing groupbox subclass"); @@ -192,7 +208,7 @@ uiGroup *uiNewGroup(const char *text) BS_GROUPBOX, hInstance, NULL, TRUE); - uiFree(wtext); + uiprivFree(wtext); if (SetWindowSubclass(g->hwnd, groupSubProc, 0, (DWORD_PTR) g) == FALSE) logLastError(L"error subclassing groupbox to handle parent messages"); diff --git a/windows/image.cpp b/windows/image.cpp new file mode 100644 index 00000000..7d1a06ea --- /dev/null +++ b/windows/image.cpp @@ -0,0 +1,281 @@ +#include "uipriv_windows.hpp" + +// TODO: +// - is the alpha channel ignored when drawing images in tables? + +IWICImagingFactory *uiprivWICFactory = NULL; + +HRESULT uiprivInitImage(void) +{ + return CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, + IID_IWICImagingFactory, (void **) (&uiprivWICFactory)); +} + +void uiprivUninitImage(void) +{ + uiprivWICFactory->Release(); + uiprivWICFactory = NULL; +} + +struct uiImage { + double width; + double height; + std::vector *bitmaps; +}; + +uiImage *uiNewImage(double width, double height) +{ + uiImage *i; + + i = uiprivNew(uiImage); + i->width = width; + i->height = height; + i->bitmaps = new std::vector; + return i; +} + +void uiFreeImage(uiImage *i) +{ + for (IWICBitmap *b : *(i->bitmaps)) + b->Release(); + delete i->bitmaps; + uiprivFree(i); +} + +// to make things easier, we store images in WIC in the same way we store them in GDI (as system-endian ARGB) and premultiplied (as that's what AlphaBlend() expects (TODO confirm this)) +// but what WIC format is system-endian ARGB? for a little-endian system, that's BGRA +// it turns out that the Windows 8 BMP encoder uses BGRA if told to (https://docs.microsoft.com/en-us/windows/desktop/wic/bmp-format-overview) +// it also turns out Direct2D requires PBGRA for drawing (https://docs.microsoft.com/en-us/windows/desktop/wic/-wic-bitmapsources-howto-drawusingd2d) +// so I guess we can assume PBGRA is correct...? (TODO) +#define formatForGDI GUID_WICPixelFormat32bppPBGRA + +void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int byteStride) +{ + IWICBitmap *b; + WICRect r; + IWICBitmapLock *l; + uint8_t *pix, *data; + // TODO WICInProcPointer is not available in MinGW-w64 + BYTE *dipp; + UINT size; + UINT realStride; + int x, y; + HRESULT hr; + + hr = uiprivWICFactory->CreateBitmap(pixelWidth, pixelHeight, + formatForGDI, WICBitmapCacheOnDemand, + &b); + if (hr != S_OK) + logHRESULT(L"error calling CreateBitmap() in uiImageAppend()", hr); + r.X = 0; + r.Y = 0; + r.Width = pixelWidth; + r.Height = pixelHeight; + hr = b->Lock(&r, WICBitmapLockWrite, &l); + if (hr != S_OK) + logHRESULT(L"error calling Lock() in uiImageAppend()", hr); + + pix = (uint8_t *) pixels; + // TODO can size be NULL? + hr = l->GetDataPointer(&size, &dipp); + if (hr != S_OK) + logHRESULT(L"error calling GetDataPointer() in uiImageAppend()", hr); + data = (uint8_t *) dipp; + hr = l->GetStride(&realStride); + if (hr != S_OK) + logHRESULT(L"error calling GetStride() in uiImageAppend()", hr); + for (y = 0; y < pixelHeight; y++) { + for (x = 0; x < pixelWidth * 4; x += 4) { + union { + uint32_t v32; + uint8_t v8[4]; + } v; + + v.v32 = ((uint32_t) (pix[x + 3])) << 24; + v.v32 |= ((uint32_t) (pix[x])) << 16; + v.v32 |= ((uint32_t) (pix[x + 1])) << 8; + v.v32 |= ((uint32_t) (pix[x + 2])); + data[x] = v.v8[0]; + data[x + 1] = v.v8[1]; + data[x + 2] = v.v8[2]; + data[x + 3] = v.v8[3]; + } + pix += byteStride; + data += realStride; + } + + l->Release(); + i->bitmaps->push_back(b); +} + +struct matcher { + IWICBitmap *best; + int distX; + int distY; + int targetX; + int targetY; + bool foundLarger; +}; + +// TODO is this the right algorithm? +static void match(IWICBitmap *b, struct matcher *m) +{ + UINT ux, uy; + int x, y; + int x2, y2; + HRESULT hr; + + hr = b->GetSize(&ux, &uy); + if (hr != S_OK) + logHRESULT(L"error calling GetSize() in match()", hr); + x = ux; + y = uy; + if (m->best == NULL) + goto writeMatch; + + if (x < m->targetX && y < m->targetY) + if (m->foundLarger) + // always prefer larger ones + return; + if (x >= m->targetX && y >= m->targetY && !m->foundLarger) + // we set foundLarger below + goto writeMatch; + +// TODO +#define abs(x) ((x) < 0 ? -(x) : (x)) + x2 = abs(m->targetX - x); + y2 = abs(m->targetY - y); + if (x2 < m->distX && y2 < m->distY) + goto writeMatch; + + // TODO weight one dimension? threshhold? + return; + +writeMatch: + // must set this here too; otherwise the first image will never have ths set + if (x >= m->targetX && y >= m->targetY && !m->foundLarger) + m->foundLarger = true; + m->best = b; + m->distX = abs(m->targetX - x); + m->distY = abs(m->targetY - y); +} + +IWICBitmap *uiprivImageAppropriateForDC(uiImage *i, HDC dc) +{ + struct matcher m; + + m.best = NULL; + m.distX = INT_MAX; + m.distY = INT_MAX; + // TODO explain this + m.targetX = MulDiv(i->width, GetDeviceCaps(dc, LOGPIXELSX), 96); + m.targetY = MulDiv(i->height, GetDeviceCaps(dc, LOGPIXELSY), 96); + m.foundLarger = false; + for (IWICBitmap *b : *(i->bitmaps)) + match(b, &m); + return m.best; +} + +// TODO this needs to center images if the given size is not the same aspect ratio +HRESULT uiprivWICToGDI(IWICBitmap *b, HDC dc, int width, int height, HBITMAP *hb) +{ + UINT ux, uy; + int x, y; + IWICBitmapSource *src; + BITMAPINFO bmi; + VOID *bits; + BITMAP bmp; + HRESULT hr; + + hr = b->GetSize(&ux, &uy); + if (hr != S_OK) + return hr; + x = ux; + y = uy; + if (width == 0) + width = x; + if (height == 0) + height = y; + + // special case: don't invoke a scaler if the size is the same + if (width == x && height == y) { + b->AddRef(); // for the Release() later + src = b; + } else { + IWICBitmapScaler *scaler; + WICPixelFormatGUID guid; + IWICFormatConverter *conv; + + hr = uiprivWICFactory->CreateBitmapScaler(&scaler); + if (hr != S_OK) + return hr; + hr = scaler->Initialize(b, width, height, + // according to https://stackoverflow.com/questions/4250738/is-stretchblt-halftone-bilinear-for-all-scaling, this is what StretchBlt(COLORONCOLOR) does (with COLORONCOLOR being what's supported by AlphaBlend()) + WICBitmapInterpolationModeNearestNeighbor); + if (hr != S_OK) { + scaler->Release(); + return hr; + } + + // But we are not done yet! IWICBitmapScaler can use an + // entirely different pixel format than what we gave it, + // and by extension, what GDI wants. See also: + // - https://stackoverflow.com/questions/28323228/iwicbitmapscaler-doesnt-work-for-96bpprgbfloat-format + // - https://github.com/Microsoft/DirectXTex/blob/0d94e9469bc3e6080a71145f35efa559f8f2e522/DirectXTex/DirectXTexResize.cpp#L83 + hr = scaler->GetPixelFormat(&guid); + if (hr != S_OK) { + scaler->Release(); + return hr; + } + if (IsEqualGUID(guid, formatForGDI)) + src = scaler; + else { + hr = uiprivWICFactory->CreateFormatConverter(&conv); + if (hr != S_OK) { + scaler->Release(); + return hr; + } + hr = conv->Initialize(scaler, formatForGDI, + // TODO is the dither type correct in all cases? + WICBitmapDitherTypeNone, NULL, 0, WICBitmapPaletteTypeMedianCut); + scaler->Release(); + if (hr != S_OK) { + conv->Release(); + return hr; + } + src = conv; + } + } + + ZeroMemory(&bmi, sizeof (BITMAPINFO)); + bmi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = width; + bmi.bmiHeader.biHeight = -((int) height); + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + *hb = CreateDIBSection(dc, &bmi, DIB_RGB_COLORS, + &bits, NULL, 0); + if (*hb == NULL) { + logLastError(L"CreateDIBSection()"); + hr = E_FAIL; + goto fail; + } + + // now we need to figure out the stride of the image data GDI gave us + // TODO find out if CreateDIBSection() fills that in bmi for us + // TODO fill in the error returns here too + if (GetObject(*hb, sizeof (BITMAP), &bmp) == 0) + logLastError(L"error calling GetObject() in uiprivWICToGDI()"); + hr = src->CopyPixels(NULL, bmp.bmWidthBytes, + bmp.bmWidthBytes * bmp.bmHeight, (BYTE *) bits); + +fail: + if (*hb != NULL && hr != S_OK) { + // don't bother with the error returned here + DeleteObject(*hb); + *hb = NULL; + } + src->Release(); + return hr; +} diff --git a/windows/init.cpp b/windows/init.cpp index 15dd4edd..6b6752da 100644 --- a/windows/init.cpp +++ b/windows/init.cpp @@ -1,12 +1,13 @@ // 6 april 2015 #include "uipriv_windows.hpp" +#include "attrstr.hpp" HINSTANCE hInstance; int nCmdShow; HFONT hMessageFont; -// TODO needed? +// LONGTERM needed? HBRUSH hollowBrush; // the returned pointer is actually to the second character @@ -23,25 +24,23 @@ static const char *initerr(const char *message, const WCHAR *label, DWORD value) if (!hassysmsg) sysmsg = L""; wmessage = toUTF16(message + 1); - wout = debugstrf(L"-error initializing libui: %s; code %I32d (0x%08I32X) %s", + wout = strf(L"-error initializing libui: %s; code %I32d (0x%08I32X) %s", wmessage, value, value, sysmsg); - uiFree(wmessage); + uiprivFree(wmessage); if (hassysmsg) LocalFree(sysmsg); // ignore error - if (wout == NULL) // debugstrf() failed; return message raw - return message + 1; out = toUTF8(wout); - uiFree(wout); + uiprivFree(wout); return out + 1; } #define ieLastErr(msg) initerr("=" msg, L"GetLastError() ==", GetLastError()) #define ieHRESULT(msg, hr) initerr("=" msg, L"HRESULT", (DWORD) hr) -// TODO make common -uiInitOptions options; +// LONGTERM put this declaration in a common file +uiInitOptions uiprivOptions; #define wantedICCClasses ( \ ICC_STANDARD_CLASSES | /* user32.dll controls */ \ @@ -63,7 +62,7 @@ const char *uiInit(uiInitOptions *o) INITCOMMONCONTROLSEX icc; HRESULT hr; - options = *o; + uiprivOptions = *o; initAlloc(); @@ -72,6 +71,8 @@ const char *uiInit(uiInitOptions *o) if ((si.dwFlags & STARTF_USESHOWWINDOW) != 0) nCmdShow = si.wShowWindow; + // LONGTERM set DPI awareness + hDefaultIcon = LoadIconW(NULL, IDI_APPLICATION); if (hDefaultIcon == NULL) return ieLastErr("loading default icon for window classes"); @@ -81,7 +82,7 @@ const char *uiInit(uiInitOptions *o) ce = initUtilWindow(hDefaultIcon, hDefaultCursor); if (ce != NULL) - return ieLastErr("TODO use ce here"); + return initerr(ce, L"GetLastError() ==", GetLastError()); if (registerWindowClass(hDefaultIcon, hDefaultCursor) == 0) return ieLastErr("registering uiWindow window class"); @@ -94,9 +95,8 @@ const char *uiInit(uiInitOptions *o) if (hMessageFont == NULL) return ieLastErr("loading default messagebox font; this is the default UI font"); - // TODO rewrite this error message if (initContainer(hDefaultIcon, hDefaultCursor) == 0) - return ieLastErr("initializing uiMakeContainer() window class"); + return ieLastErr("initializing uiWindowsMakeContainer() window class"); hollowBrush = (HBRUSH) GetStockObject(HOLLOW_BRUSH); if (hollowBrush == NULL) @@ -111,14 +111,14 @@ const char *uiInit(uiInitOptions *o) hr = CoInitialize(NULL); if (hr != S_OK && hr != S_FALSE) return ieHRESULT("initializing COM", hr); - // TODO initialize COM security - // TODO (windows vista) turn off COM exception handling + // LONGTERM initialize COM security + // LONGTERM (windows vista) turn off COM exception handling hr = initDraw(); if (hr != S_OK) return ieHRESULT("initializing Direct2D", hr); - hr = initDrawText(); + hr = uiprivInitDrawText(); if (hr != S_OK) return ieHRESULT("initializing DirectWrite", hr); @@ -131,16 +131,22 @@ const char *uiInit(uiInitOptions *o) if (registerD2DScratchClass(hDefaultIcon, hDefaultCursor) == 0) return ieLastErr("initializing D2D scratch window class"); + hr = uiprivInitImage(); + if (hr != S_OK) + return ieHRESULT("initializing WIC", hr); + return NULL; } void uiUninit(void) { + uiprivUninitTimers(); + uiprivUninitImage(); uninitMenus(); unregisterD2DScratchClass(); unregisterMessageFilter(); unregisterArea(); - uninitDrawText(); + uiprivUninitDrawText(); uninitDraw(); CoUninitialize(); if (DeleteObject(hollowBrush) == 0) @@ -157,7 +163,7 @@ void uiUninit(void) void uiFreeInitError(const char *err) { if (*(err - 1) == '-') - uiFree((void *) err); + uiprivFree((void *) (err - 1)); } BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) diff --git a/windows/label.cpp b/windows/label.cpp index 6a401b0f..94a0d88e 100644 --- a/windows/label.cpp +++ b/windows/label.cpp @@ -11,7 +11,7 @@ uiWindowsControlAllDefaults(uiLabel) // via http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing #define labelHeight 8 -static void uiLabelMinimumSize(uiWindowsControl *c, intmax_t *width, intmax_t *height) +static void uiLabelMinimumSize(uiWindowsControl *c, int *width, int *height) { uiLabel *l = uiLabel(c); uiWindowsSizing sizing; @@ -51,7 +51,7 @@ uiLabel *uiNewLabel(const char *text) SS_LEFTNOWORDWRAP | SS_NOPREFIX, hInstance, NULL, TRUE); - uiFree(wtext); + uiprivFree(wtext); return l; } diff --git a/windows/main.cpp b/windows/main.cpp index 2480cd46..004e784c 100644 --- a/windows/main.cpp +++ b/windows/main.cpp @@ -41,31 +41,80 @@ void unregisterMessageFilter(void) logLastError(L"error unregistering libui message filter"); } -// TODO http://blogs.msdn.com/b/oldnewthing/archive/2005/04/08/406509.aspx when adding accelerators, TranslateAccelerators() before IsDialogMessage() +// LONGTERM http://blogs.msdn.com/b/oldnewthing/archive/2005/04/08/406509.aspx when adding accelerators, TranslateAccelerators() before IsDialogMessage() + +static void processMessage(MSG *msg) +{ + HWND correctParent; + + if (msg->hwnd != NULL) + correctParent = parentToplevel(msg->hwnd); + else // just to be safe + correctParent = GetActiveWindow(); + if (correctParent != NULL) + // this calls our mesage filter above for us + if (IsDialogMessage(correctParent, msg) != 0) + return; + TranslateMessage(msg); + DispatchMessageW(msg); +} + +static int waitMessage(MSG *msg) +{ + int res; + + res = GetMessageW(msg, NULL, 0, 0); + if (res < 0) { + logLastError(L"error calling GetMessage()"); + return 0; // bail out on error + } + return res != 0; // returns false on WM_QUIT +} void uiMain(void) { - MSG msg; - int res; - HWND active; + while (uiMainStep(1)) + ; +} - for (;;) { - res = GetMessageW(&msg, NULL, 0, 0); - if (res < 0) { - logLastError(L"error calling GetMessage()"); - break; // bail out on error - } - if (res == 0) // WM_QUIT - break; - // TODO really active? or parentToplevel(msg->hwnd)? - active = GetActiveWindow(); - if (active != NULL) - // TODO find documentation that says IsDialogMessage() calls CallMsgFilter() for us, because that's what's happening - if (IsDialogMessage(active, &msg) != 0) - continue; - TranslateMessage(&msg); - DispatchMessageW(&msg); +void uiMainSteps(void) +{ + // don't need to do anything here +} + +static int peekMessage(MSG *msg) +{ + BOOL res; + + res = PeekMessageW(msg, NULL, 0, 0, PM_REMOVE); + if (res == 0) + return 2; // no message available + if (msg->message != WM_QUIT) + return 1; // a message + return 0; // WM_QUIT +} + +int uiMainStep(int wait) +{ + MSG msg; + + if (wait) { + if (!waitMessage(&msg)) + return 0; + processMessage(&msg); + return 1; } + + // don't wait for a message + switch (peekMessage(&msg)) { + case 0: // quit + // TODO PostQuitMessage() again? + return 0; + case 1: // process a message + processMessage(&msg); + // fall out to the case for no message + } + return 1; // no message } void uiQuit(void) @@ -76,6 +125,36 @@ void uiQuit(void) void uiQueueMain(void (*f)(void *data), void *data) { if (PostMessageW(utilWindow, msgQueued, (WPARAM) f, (LPARAM) data) == 0) - // TODO this is likely not safe to call across threads (allocates memory) + // LONGTERM this is likely not safe to call across threads (allocates memory) logLastError(L"error queueing function to run on main thread"); } + +static std::map timers; + +void uiTimer(int milliseconds, int (*f)(void *data), void *data) +{ + uiprivTimer *timer; + + timer = uiprivNew(uiprivTimer); + timer->f = f; + timer->data = data; + // note that timer IDs are pointer sized precisely so we can use them as timer IDs; see https://blogs.msdn.microsoft.com/oldnewthing/20150924-00/?p=91521 + if (SetTimer(utilWindow, (UINT_PTR) timer, milliseconds, NULL) == 0) + logLastError(L"error calling SetTimer() in uiTimer()"); + timers[timer] = true; +} + +void uiprivFreeTimer(uiprivTimer *t) +{ + timers.erase(t); + uiprivFree(t); +} + +// since timers use uiprivAlloc(), we have to clean them up in uiUninit(), or else we'll get dangling allocation errors +void uiprivUninitTimers(void) +{ + // TODO why doesn't auto t : timers work? + for (auto t = timers.begin(); t != timers.end(); t++) + uiprivFree(t->first); + timers.clear(); +} diff --git a/windows/menu.cpp b/windows/menu.cpp index 89406d7f..65791bfb 100644 --- a/windows/menu.cpp +++ b/windows/menu.cpp @@ -1,11 +1,11 @@ // 24 april 2015 #include "uipriv_windows.hpp" -// TODO migrate to std::vector +// LONGTERM migrate to std::vector static uiMenu **menus = NULL; -static uintmax_t len = 0; -static uintmax_t cap = 0; +static size_t len = 0; +static size_t cap = 0; static BOOL menusFinalized = FALSE; static WORD curID = 100; // start somewhere safe static BOOL hasQuit = FALSE; @@ -15,8 +15,8 @@ static BOOL hasAbout = FALSE; struct uiMenu { WCHAR *name; uiMenuItem **items; - uintmax_t len; - uintmax_t cap; + size_t len; + size_t cap; }; struct uiMenuItem { @@ -28,8 +28,8 @@ struct uiMenuItem { BOOL disabled; // template for new instances; kept in sync with everything else BOOL checked; HMENU *hmenus; - uintmax_t len; - uintmax_t cap; + size_t len; + size_t cap; }; enum { @@ -45,7 +45,7 @@ enum { static void sync(uiMenuItem *item) { - uintmax_t i; + size_t i; MENUITEMINFOW mi; ZeroMemory(&mi, sizeof (MENUITEMINFOW)); @@ -68,7 +68,7 @@ static void defaultOnClicked(uiMenuItem *item, uiWindow *w, void *data) static void onQuitClicked(uiMenuItem *item, uiWindow *w, void *data) { - if (shouldQuit()) + if (uiprivShouldQuit()) uiQuit(); } @@ -87,7 +87,7 @@ void uiMenuItemDisable(uiMenuItem *i) void uiMenuItemOnClicked(uiMenuItem *i, void (*f)(uiMenuItem *, uiWindow *, void *), void *data) { if (i->type == typeQuit) - complain("attempt to call uiMenuItemOnClicked() on a Quit item; use uiOnShouldQuit() instead"); + uiprivUserBug("You can not call uiMenuItemOnClicked() on a Quit item; use uiOnShouldQuit() instead."); i->onClicked = f; i->onClickedData = data; } @@ -111,14 +111,14 @@ static uiMenuItem *newItem(uiMenu *m, int type, const char *name) uiMenuItem *item; if (menusFinalized) - complain("attempt to create a new menu item after menus have been finalized"); + uiprivUserBug("You can not create a new menu item after menus have been finalized."); if (m->len >= m->cap) { m->cap += grow; - m->items = (uiMenuItem **) uiRealloc(m->items, m->cap * sizeof (uiMenuItem *), "uiMenuitem *[]"); + m->items = (uiMenuItem **) uiprivRealloc(m->items, m->cap * sizeof (uiMenuItem *), "uiMenuitem *[]"); } - item = uiNew(uiMenuItem); + item = uiprivNew(uiMenuItem); m->items[m->len] = item; m->len++; @@ -146,10 +146,12 @@ static uiMenuItem *newItem(uiMenu *m, int type, const char *name) curID++; } - // TODO copy this from the unix one - item->onClicked = defaultOnClicked; - if (item->type == typeQuit) + if (item->type == typeQuit) { + // can't call uiMenuItemOnClicked() here item->onClicked = onQuitClicked; + item->onClickedData = NULL; + } else + uiMenuItemOnClicked(item, defaultOnClicked, NULL); return item; } @@ -167,7 +169,7 @@ uiMenuItem *uiMenuAppendCheckItem(uiMenu *m, const char *name) uiMenuItem *uiMenuAppendQuitItem(uiMenu *m) { if (hasQuit) - complain("attempt to add multiple Quit menu items"); + uiprivUserBug("You can not have multiple Quit menu items in a program."); hasQuit = TRUE; newItem(m, typeSeparator, NULL); return newItem(m, typeQuit, NULL); @@ -176,7 +178,7 @@ uiMenuItem *uiMenuAppendQuitItem(uiMenu *m) uiMenuItem *uiMenuAppendPreferencesItem(uiMenu *m) { if (hasPreferences) - complain("attempt to add multiple Preferences menu items"); + uiprivUserBug("You can not have multiple Preferences menu items in a program."); hasPreferences = TRUE; newItem(m, typeSeparator, NULL); return newItem(m, typePreferences, NULL); @@ -185,7 +187,8 @@ uiMenuItem *uiMenuAppendPreferencesItem(uiMenu *m) uiMenuItem *uiMenuAppendAboutItem(uiMenu *m) { if (hasAbout) - complain("attempt to add multiple About menu items"); + // TODO place these uiprivImplBug() and uiprivUserBug() strings in a header + uiprivUserBug("You can not have multiple About menu items in a program."); hasAbout = TRUE; newItem(m, typeSeparator, NULL); return newItem(m, typeAbout, NULL); @@ -201,13 +204,13 @@ uiMenu *uiNewMenu(const char *name) uiMenu *m; if (menusFinalized) - complain("attempt to create a new menu after menus have been finalized"); + uiprivUserBug("You can not create a new menu after menus have been finalized."); if (len >= cap) { cap += grow; - menus = (uiMenu **) uiRealloc(menus, cap * sizeof (uiMenu *), "uiMenu *[]"); + menus = (uiMenu **) uiprivRealloc(menus, cap * sizeof (uiMenu *), "uiMenu *[]"); } - m = uiNew(uiMenu); + m = uiprivNew(uiMenu); menus[len] = m; len++; @@ -234,7 +237,7 @@ static void appendMenuItem(HMENU menu, uiMenuItem *item) if (item->len >= item->cap) { item->cap += grow; - item->hmenus = (HMENU *) uiRealloc(item->hmenus, item->cap * sizeof (HMENU), "HMENU[]"); + item->hmenus = (HMENU *) uiprivRealloc(item->hmenus, item->cap * sizeof (HMENU), "HMENU[]"); } item->hmenus[item->len] = menu; item->len++; @@ -243,7 +246,7 @@ static void appendMenuItem(HMENU menu, uiMenuItem *item) static HMENU makeMenu(uiMenu *m) { HMENU menu; - uintmax_t i; + size_t i; menu = CreatePopupMenu(); if (menu == NULL) @@ -257,7 +260,7 @@ HMENU makeMenubar(void) { HMENU menubar; HMENU menu; - uintmax_t i; + size_t i; menusFinalized = TRUE; @@ -278,7 +281,7 @@ void runMenuEvent(WORD id, uiWindow *w) { uiMenu *m; uiMenuItem *item; - uintmax_t i, j; + size_t i, j; // this isn't optimal, but it works, and it should be just fine for most cases for (i = 0; i < len; i++) { @@ -290,7 +293,7 @@ void runMenuEvent(WORD id, uiWindow *w) } } // no match - complain("unknown menu ID %hu in runMenuEvent()", id); + uiprivImplBug("unknown menu ID %hu in runMenuEvent()", id); found: // first toggle checkboxes, if any @@ -303,9 +306,9 @@ found: static void freeMenu(uiMenu *m, HMENU submenu) { - uintmax_t i; + size_t i; uiMenuItem *item; - uintmax_t j; + size_t j; for (i = 0; i < m->len; i++) { item = m->items[i]; @@ -313,7 +316,7 @@ static void freeMenu(uiMenu *m, HMENU submenu) if (item->hmenus[j] == submenu) break; if (j >= item->len) - complain("submenu handle %p not found in freeMenu()", submenu); + uiprivImplBug("submenu handle %p not found in freeMenu()", submenu); for (; j < item->len - 1; j++) item->hmenus[j] = item->hmenus[j + 1]; item->hmenus[j] = NULL; @@ -323,7 +326,7 @@ static void freeMenu(uiMenu *m, HMENU submenu) void freeMenubar(HMENU menubar) { - uintmax_t i; + size_t i; MENUITEMINFOW mi; for (i = 0; i < len; i++) { @@ -341,25 +344,26 @@ void uninitMenus(void) { uiMenu *m; uiMenuItem *item; - uintmax_t i, j; + size_t i, j; for (i = 0; i < len; i++) { m = menus[i]; - uiFree(m->name); + uiprivFree(m->name); for (j = 0; j < m->len; j++) { item = m->items[j]; if (item->len != 0) - complain("menu item %p (%ws) still has uiWindows attached; did you forget to destroy some windows?", item, item->name); + // LONGTERM uiprivUserBug()? + uiprivImplBug("menu item %p (%ws) still has uiWindows attached; did you forget to destroy some windows?", item, item->name); if (item->name != NULL) - uiFree(item->name); + uiprivFree(item->name); if (item->hmenus != NULL) - uiFree(item->hmenus); - uiFree(item); + uiprivFree(item->hmenus); + uiprivFree(item); } if (m->items != NULL) - uiFree(m->items); - uiFree(m); + uiprivFree(m->items); + uiprivFree(m); } if (menus != NULL) - uiFree(menus); + uiprivFree(menus); } diff --git a/windows/multilineentry.cpp b/windows/multilineentry.cpp index 38908f38..1365e4e3 100644 --- a/windows/multilineentry.cpp +++ b/windows/multilineentry.cpp @@ -1,8 +1,7 @@ // 8 april 2015 #include "uipriv_windows.hpp" -// TODO there's alpha darkening of text going on; something is up in our parent logic -// TODO resizing collapses newlines +// TODO there's alpha darkening of text going on in read-only ones; something is up in our parent logic struct uiMultilineEntry { uiWindowsControl c; @@ -38,10 +37,10 @@ uiWindowsControlAllDefaultsExceptDestroy(uiMultilineEntry) // from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing #define entryWidth 107 /* this is actually the shorter progress bar width, but Microsoft only indicates as wide as necessary */ -// TODO change this for multiline text boxes +// LONGTERM change this for multiline text boxes (longterm because how?) #define entryHeight 14 -static void uiMultilineEntryMinimumSize(uiWindowsControl *c, intmax_t *width, intmax_t *height) +static void uiMultilineEntryMinimumSize(uiWindowsControl *c, int *width, int *height) { uiMultilineEntry *e = uiMultilineEntry(c); uiWindowsSizing sizing; @@ -60,36 +59,46 @@ static void defaultOnChanged(uiMultilineEntry *e, void *data) // do nothing } -// TODO apply crlf conversion char *uiMultilineEntryText(uiMultilineEntry *e) { - return uiWindowsWindowText(e->hwnd); + char *out; + + out = uiWindowsWindowText(e->hwnd); + CRLFtoLF(out); + return out; } -// TODO apply crlf conversion void uiMultilineEntrySetText(uiMultilineEntry *e, const char *text) { + char *crlf; + // doing this raises an EN_CHANGED e->inhibitChanged = TRUE; - uiWindowsSetWindowText(e->hwnd, text); + crlf = LFtoCRLF(text); + uiWindowsSetWindowText(e->hwnd, crlf); + uiprivFree(crlf); e->inhibitChanged = FALSE; // don't queue the control for resize; entry sizes are independent of their contents } -// TOOD crlf stuff void uiMultilineEntryAppend(uiMultilineEntry *e, const char *text) { LRESULT n; + char *crlf; WCHAR *wtext; - // TODO does doing this raise EN_CHANGED? + // doing this raises an EN_CHANGED + e->inhibitChanged = TRUE; // TODO preserve selection? caret? what if caret used to be at end? // TODO scroll to bottom? n = SendMessageW(e->hwnd, WM_GETTEXTLENGTH, 0, 0); SendMessageW(e->hwnd, EM_SETSEL, n, n); - wtext = toUTF16(text); + crlf = LFtoCRLF(text); + wtext = toUTF16(crlf); + uiprivFree(crlf); SendMessageW(e->hwnd, EM_REPLACESEL, FALSE, (LPARAM) wtext); - uiFree(wtext); + uiprivFree(wtext); + e->inhibitChanged = FALSE; } void uiMultilineEntryOnChanged(uiMultilineEntry *e, void (*f)(uiMultilineEntry *, void *), void *data) @@ -114,7 +123,7 @@ void uiMultilineEntrySetReadOnly(uiMultilineEntry *e, int readonly) logLastError(L"error making uiMultilineEntry read-only"); } -uiMultilineEntry *uiNewMultilineEntry(void) +static uiMultilineEntry *finishMultilineEntry(DWORD style) { uiMultilineEntry *e; @@ -122,7 +131,7 @@ uiMultilineEntry *uiNewMultilineEntry(void) e->hwnd = uiWindowsEnsureCreateControlHWND(WS_EX_CLIENTEDGE, L"edit", L"", - ES_AUTOVSCROLL | ES_LEFT | ES_MULTILINE | ES_NOHIDESEL | ES_WANTRETURN | WS_TABSTOP | WS_VSCROLL, + ES_AUTOVSCROLL | ES_LEFT | ES_MULTILINE | ES_NOHIDESEL | ES_WANTRETURN | WS_TABSTOP | WS_VSCROLL | style, hInstance, NULL, TRUE); @@ -131,3 +140,13 @@ uiMultilineEntry *uiNewMultilineEntry(void) return e; } + +uiMultilineEntry *uiNewMultilineEntry(void) +{ + return finishMultilineEntry(0); +} + +uiMultilineEntry *uiNewNonWrappingMultilineEntry(void) +{ + return finishMultilineEntry(WS_HSCROLL | ES_AUTOHSCROLL); +} diff --git a/windows/opentype.cpp b/windows/opentype.cpp new file mode 100644 index 00000000..777f30d6 --- /dev/null +++ b/windows/opentype.cpp @@ -0,0 +1,33 @@ +// 11 may 2017 +#include "uipriv_windows.hpp" +#include "attrstr.hpp" + +// TODO pull out my decision for empty uiOpenTypeFeatures, assuming that it isn't in another file or that I even made one + +static uiForEach addToTypography(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value, void *data) +{ + IDWriteTypography *dt = (IDWriteTypography *) data; + DWRITE_FONT_FEATURE dff; + HRESULT hr; + + ZeroMemory(&dff, sizeof (DWRITE_FONT_FEATURE)); + // yes, the cast here is necessary (the compiler will complain otherwise)... + dff.nameTag = (DWRITE_FONT_FEATURE_TAG) DWRITE_MAKE_OPENTYPE_TAG(a, b, c, d); + dff.parameter = (UINT32) value; + hr = dt->AddFontFeature(dff); + if (hr != S_OK) + logHRESULT(L"error adding OpenType feature to IDWriteTypography", hr); + return uiForEachContinue; +} + +IDWriteTypography *uiprivOpenTypeFeaturesToIDWriteTypography(const uiOpenTypeFeatures *otf) +{ + IDWriteTypography *dt; + HRESULT hr; + + hr = dwfactory->CreateTypography(&dt); + if (hr != S_OK) + logHRESULT(L"error creating IDWriteTypography", hr); + uiOpenTypeFeaturesForEach(otf, addToTypography, dt); + return dt; +} diff --git a/windows/progressbar.cpp b/windows/progressbar.cpp index 26153f78..c3a67dd3 100644 --- a/windows/progressbar.cpp +++ b/windows/progressbar.cpp @@ -12,7 +12,7 @@ uiWindowsControlAllDefaults(uiProgressBar) #define pbarWidth 237 #define pbarHeight 8 -static void uiProgressBarMinimumSize(uiWindowsControl *c, intmax_t *width, intmax_t *height) +static void uiProgressBarMinimumSize(uiWindowsControl *c, int *width, int *height) { uiProgressBar *p = uiProgressBar(c); uiWindowsSizing sizing; @@ -26,14 +26,36 @@ static void uiProgressBarMinimumSize(uiWindowsControl *c, intmax_t *width, intma *height = y; } +#define indeterminate(p) ((getStyle(p->hwnd) & PBS_MARQUEE) != 0) + +int uiProgressBarValue(uiProgressBar *p) +{ + if (indeterminate(p)) + return -1; + return SendMessage(p->hwnd, PBM_GETPOS, 0, 0); +} + // unfortunately, as of Vista progress bars have a forced animation on increase // we have to set the progress bar to value + 1 and decrease it back to value if we want an "instant" change // see http://stackoverflow.com/questions/2217688/windows-7-aero-theme-progress-bar-bug // it's not ideal/perfect, but it will have to do void uiProgressBarSetValue(uiProgressBar *p, int value) { + if (value == -1) { + if (!indeterminate(p)) { + setStyle(p->hwnd, getStyle(p->hwnd) | PBS_MARQUEE); + SendMessageW(p->hwnd, PBM_SETMARQUEE, (WPARAM) TRUE, 0); + } + return; + } + if (indeterminate(p)) { + SendMessageW(p->hwnd, PBM_SETMARQUEE, (WPARAM) FALSE, 0); + setStyle(p->hwnd, getStyle(p->hwnd) & ~PBS_MARQUEE); + } + if (value < 0 || value > 100) - complain("value %d out of range in uiProgressBarSetValue()", value); + uiprivUserBug("Value %d is out of range for uiProgressBars.", value); + if (value == 100) { // because we can't 101 SendMessageW(p->hwnd, PBM_SETRANGE32, 0, 101); SendMessageW(p->hwnd, PBM_SETPOS, 101, 0); diff --git a/windows/radiobuttons.cpp b/windows/radiobuttons.cpp index d68aabe7..0684a270 100644 --- a/windows/radiobuttons.cpp +++ b/windows/radiobuttons.cpp @@ -12,13 +12,14 @@ struct uiRadioButtons { uiWindowsControl c; HWND hwnd; // of the container std::vector *hwnds; // of the buttons + void (*onSelected)(uiRadioButtons *, void *); + void *onSelectedData; }; static BOOL onWM_COMMAND(uiControl *c, HWND clicked, WORD code, LRESULT *lResult) { uiRadioButtons *r = uiRadioButtons(c); WPARAM check; - uintmax_t i; if (code != BN_CLICKED) return FALSE; @@ -28,10 +29,16 @@ static BOOL onWM_COMMAND(uiControl *c, HWND clicked, WORD code, LRESULT *lResult check = BST_CHECKED; SendMessage(hwnd, BM_SETCHECK, check, 0); } + (*(r->onSelected))(r, r->onSelectedData); *lResult = 0; return TRUE; } +static void defaultOnSelected(uiRadioButtons *r, void *data) +{ + // do nothing +} + static void uiRadioButtonsDestroy(uiControl *c) { uiRadioButtons *r = uiRadioButtons(c); @@ -53,10 +60,10 @@ uiWindowsControlAllDefaultsExceptDestroy(uiRadioButtons) // from http://msdn.microsoft.com/en-us/library/windows/desktop/bb226818%28v=vs.85%29.aspx #define radiobuttonXFromLeftOfBoxToLeftOfLabel 12 -static void uiRadioButtonsMinimumSize(uiWindowsControl *c, intmax_t *width, intmax_t *height) +static void uiRadioButtonsMinimumSize(uiWindowsControl *c, int *width, int *height) { uiRadioButtons *r = uiRadioButtons(c); - intmax_t wid, maxwid; + int wid, maxwid; uiWindowsSizing sizing; int x, y; @@ -74,8 +81,6 @@ static void uiRadioButtonsMinimumSize(uiWindowsControl *c, intmax_t *width, intm x = radiobuttonXFromLeftOfBoxToLeftOfLabel; y = radiobuttonHeight; - // get it for the radio button itself since that's what counts - // TODO for all of them? uiWindowsGetSizing((*(r->hwnds))[0], &sizing); uiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y); @@ -86,28 +91,23 @@ static void uiRadioButtonsMinimumSize(uiWindowsControl *c, intmax_t *width, intm static void radiobuttonsRelayout(uiRadioButtons *r) { RECT client; - intmax_t height1; - intmax_t h; - intmax_t x, y, width, height; + int x, y, width, height; + int height1; + uiWindowsSizing sizing; + if (r->hwnds->size() == 0) + return; uiWindowsEnsureGetClientRect(r->hwnd, &client); x = client.left; y = client.top; width = client.right - client.left; - height = client.bottom - client.top; - // TODO compute the real height1 - height1 = 25; + height1 = radiobuttonHeight; + uiWindowsGetSizing((*(r->hwnds))[0], &sizing); + uiWindowsSizingDlgUnitsToPixels(&sizing, NULL, &height1); + height = height1; for (const HWND &hwnd : *(r->hwnds)) { - h = height1; - if (h > height) // clip to height - h = height; - uiWindowsEnsureMoveWindowDuringResize(hwnd, x, y, width, h); - y += height1; - height -= height1; - if (height <= 0) // clip to height - break; - // TODO don't do the above to avoid overlap - // TODO in fact, only do this on add/remove/change labels/etc. + uiWindowsEnsureMoveWindowDuringResize(hwnd, x, y, width, height); + y += height; } } @@ -140,7 +140,7 @@ void uiRadioButtonsAppend(uiRadioButtons *r, const char *text) BS_RADIOBUTTON | groupTabStop, hInstance, NULL, TRUE); - uiFree(wtext); + uiprivFree(wtext); uiWindowsEnsureSetParentHWND(hwnd, r->hwnd); uiWindowsRegisterWM_COMMANDHandler(hwnd, onWM_COMMAND, uiControl(r)); r->hwnds->push_back(hwnd); @@ -148,6 +148,33 @@ void uiRadioButtonsAppend(uiRadioButtons *r, const char *text) uiWindowsControlMinimumSizeChanged(uiWindowsControl(r)); } +int uiRadioButtonsSelected(uiRadioButtons *r) +{ + size_t i; + + for (i = 0; i < r->hwnds->size(); i++) + if (SendMessage((*(r->hwnds))[i], BM_GETCHECK, 0, 0) == BST_CHECKED) + return i; + return -1; +} + +void uiRadioButtonsSetSelected(uiRadioButtons *r, int n) +{ + int m; + + m = uiRadioButtonsSelected(r); + if (m != -1) + SendMessage((*(r->hwnds))[m], BM_SETCHECK, BST_UNCHECKED, 0); + if (n != -1) + SendMessage((*(r->hwnds))[n], BM_SETCHECK, BST_CHECKED, 0); +} + +void uiRadioButtonsOnSelected(uiRadioButtons *r, void (*f)(uiRadioButtons *, void *), void *data) +{ + r->onSelected = f; + r->onSelectedData = data; +} + static void onResize(uiWindowsControl *c) { radiobuttonsRelayout(uiRadioButtons(c)); @@ -163,5 +190,7 @@ uiRadioButtons *uiNewRadioButtons(void) r->hwnds = new std::vector; + uiRadioButtonsOnSelected(r, defaultOnSelected, NULL); + return r; } diff --git a/windows/resources.hpp b/windows/resources.hpp index 5bcbb3d9..4ae54725 100644 --- a/windows/resources.hpp +++ b/windows/resources.hpp @@ -1,9 +1,37 @@ // 30 may 2015 -#define rcTabPageDialog 100 -#define rcFontDialog 101 +#define rcTabPageDialog 29000 +#define rcFontDialog 29001 +#define rcColorDialog 29002 + +// TODO normalize these #define rcFontFamilyCombobox 1000 #define rcFontStyleCombobox 1001 #define rcFontSizeCombobox 1002 #define rcFontSamplePlacement 1003 + +#define rcColorSVChooser 1100 +#define rcColorHSlider 1101 +#define rcPreview 1102 +#define rcOpacitySlider 1103 +#define rcH 1104 +#define rcS 1105 +#define rcV 1106 +#define rcRDouble 1107 +#define rcRInt 1108 +#define rcGDouble 1109 +#define rcGInt 1110 +#define rcBDouble 1111 +#define rcBInt 1112 +#define rcADouble 1113 +#define rcAInt 1114 +#define rcHex 1115 +#define rcHLabel 1116 +#define rcSLabel 1117 +#define rcVLabel 1118 +#define rcRLabel 1119 +#define rcGLabel 1120 +#define rcBLabel 1121 +#define rcALabel 1122 +#define rcHexLabel 1123 diff --git a/windows/resources.rc b/windows/resources.rc index e5958dab..2ce7ee53 100644 --- a/windows/resources.rc +++ b/windows/resources.rc @@ -6,44 +6,8 @@ #pragma code_page(65001) // this is the Common Controls 6 manifest -// TODO set up the string values here +// we only define it in a shared build; static builds have to include the appropriate parts of the manifest in the output executable +// LONGTERM set up the string values here +#ifndef _UI_STATIC ISOLATIONAWARE_MANIFEST_RESOURCE_ID RT_MANIFEST "libui.manifest" - -// this is the dialog template used by tab pages; see windows/tabpage.c for details -rcTabPageDialog DIALOGEX 0, 0, 100, 100 -STYLE DS_CONTROL | WS_CHILD | WS_VISIBLE -EXSTYLE WS_EX_CONTROLPARENT -BEGIN - // nothing -END - -// this is for our custom DirectWrite-based font dialog (see fontdialog.cpp) -// this is based on the "New Font Dialog with Syslink" in Microsoft's font.dlg -// TODO look at localization -// TODO make it look tighter and nicer like the real one, including the actual heights of the font family and style comboboxes -rcFontDialog DIALOGEX 13, 54, 243, 200 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_3DLOOK -CAPTION "Font" -FONT 9, "Segoe UI" -BEGIN - LTEXT "&Font:", -1, 7, 7, 98, 9 - COMBOBOX rcFontFamilyCombobox, 7, 16, 98, 76, - CBS_SIMPLE | CBS_AUTOHSCROLL | CBS_DISABLENOSCROLL | - CBS_SORT | WS_VSCROLL | WS_TABSTOP | CBS_HASSTRINGS - - LTEXT "Font st&yle:", -1, 114, 7, 74, 9 - COMBOBOX rcFontStyleCombobox, 114, 16, 74, 76, - CBS_SIMPLE | CBS_AUTOHSCROLL | CBS_DISABLENOSCROLL | - WS_VSCROLL | WS_TABSTOP | CBS_HASSTRINGS - - LTEXT "&Size:", -1, 198, 7, 36, 9 - COMBOBOX rcFontSizeCombobox, 198, 16, 36, 76, - CBS_SIMPLE | CBS_AUTOHSCROLL | CBS_DISABLENOSCROLL | - CBS_SORT | WS_VSCROLL | WS_TABSTOP | CBS_HASSTRINGS - - GROUPBOX "Sample", -1, 7, 97, 227, 70, WS_GROUP - CTEXT "AaBbYyZz", rcFontSamplePlacement, 9, 106, 224, 60, SS_NOPREFIX | NOT WS_VISIBLE - - DEFPUSHBUTTON "OK", IDOK, 141, 181, 45, 14, WS_GROUP - PUSHBUTTON "Cancel", IDCANCEL, 190, 181, 45, 14, WS_GROUP -END +#endif diff --git a/windows/separator.cpp b/windows/separator.cpp index 9c4be072..28e77e83 100644 --- a/windows/separator.cpp +++ b/windows/separator.cpp @@ -1,6 +1,10 @@ // 20 may 2015 #include "uipriv_windows.hpp" +// TODO +// - font scaling issues? https://www.viksoe.dk/code/bevelline.htm +// - isn't something in vista app guidelines suggesting this too? or some other microsoft doc? and what about VS itself? + // references: // - http://stackoverflow.com/questions/2892703/how-do-i-draw-separators // - https://msdn.microsoft.com/en-us/library/windows/desktop/dn742405%28v=vs.85%29.aspx @@ -8,6 +12,7 @@ struct uiSeparator { uiWindowsControl c; HWND hwnd; + BOOL vertical; }; uiWindowsControlAllDefaults(uiSeparator) @@ -15,17 +20,25 @@ uiWindowsControlAllDefaults(uiSeparator) // via https://msdn.microsoft.com/en-us/library/windows/desktop/bb226818%28v=vs.85%29.aspx #define separatorHeight 1 -static void uiSeparatorMinimumSize(uiWindowsControl *c, intmax_t *width, intmax_t *height) +// TODO +#define separatorWidth 1 + +static void uiSeparatorMinimumSize(uiWindowsControl *c, int *width, int *height) { uiSeparator *s = uiSeparator(c); uiWindowsSizing sizing; - int y; + int x, y; *width = 1; // TODO + *height = 1; + x = separatorWidth; y = separatorHeight; uiWindowsGetSizing(s->hwnd, &sizing); - uiWindowsSizingDlgUnitsToPixels(&sizing, NULL, &y); - *height = y; + uiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y); + if (s->vertical) + *width = x; + else + *height = y; } uiSeparator *uiNewHorizontalSeparator(void) @@ -42,3 +55,19 @@ uiSeparator *uiNewHorizontalSeparator(void) return s; } + +uiSeparator *uiNewVerticalSeparator(void) +{ + uiSeparator *s; + + uiWindowsNewControl(uiSeparator, s); + + s->hwnd = uiWindowsEnsureCreateControlHWND(0, + L"static", L"", + SS_ETCHEDHORZ, + hInstance, NULL, + TRUE); + s->vertical = TRUE; + + return s; +} diff --git a/windows/sizing.cpp b/windows/sizing.cpp index a0abdb46..33cc00b9 100644 --- a/windows/sizing.cpp +++ b/windows/sizing.cpp @@ -2,7 +2,7 @@ #include "uipriv_windows.hpp" // TODO rework the error handling -void uiWindowsGetSizing(HWND hwnd, uiWindowsSizing *sizing) +void getSizing(HWND hwnd, uiWindowsSizing *sizing, HFONT font) { HDC dc; HFONT prevfont; @@ -12,7 +12,7 @@ void uiWindowsGetSizing(HWND hwnd, uiWindowsSizing *sizing) dc = GetDC(hwnd); if (dc == NULL) logLastError(L"error getting DC"); - prevfont = (HFONT) SelectObject(dc, hMessageFont); + prevfont = (HFONT) SelectObject(dc, font); if (prevfont == NULL) logLastError(L"error loading control font into device context"); @@ -26,12 +26,17 @@ void uiWindowsGetSizing(HWND hwnd, uiWindowsSizing *sizing) sizing->BaseY = (int) tm.tmHeight; sizing->InternalLeading = tm.tmInternalLeading; - if (SelectObject(dc, prevfont) != hMessageFont) + if (SelectObject(dc, prevfont) != font) logLastError(L"error restoring previous font into device context"); if (ReleaseDC(hwnd, dc) == 0) logLastError(L"error releasing DC"); } +void uiWindowsGetSizing(HWND hwnd, uiWindowsSizing *sizing) +{ + return getSizing(hwnd, sizing, hMessageFont); +} + #define dlgUnitsToX(dlg, baseX) MulDiv((dlg), (baseX), 4) #define dlgUnitsToY(dlg, baseY) MulDiv((dlg), (baseY), 8) @@ -46,6 +51,7 @@ void uiWindowsSizingDlgUnitsToPixels(uiWindowsSizing *sizing, int *x, int *y) // from https://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing and https://msdn.microsoft.com/en-us/library/windows/desktop/bb226818%28v=vs.85%29.aspx // this X value is really only for buttons but I don't see a better one :/ #define winXPadding 4 +// TODO is this too much? #define winYPadding 4 void uiWindowsSizingStandardPadding(uiWindowsSizing *sizing, int *x, int *y) diff --git a/windows/slider.cpp b/windows/slider.cpp index 83017e3b..5c671dda 100644 --- a/windows/slider.cpp +++ b/windows/slider.cpp @@ -32,7 +32,7 @@ uiWindowsControlAllDefaultsExceptDestroy(uiSlider); #define sliderWidth 107 /* this is actually the shorter progress bar width, but Microsoft doesn't indicate a width */ #define sliderHeight 15 -static void uiSliderMinimumSize(uiWindowsControl *c, intmax_t *width, intmax_t *height) +static void uiSliderMinimumSize(uiWindowsControl *c, int *width, int *height) { uiSlider *s = uiSlider(c); uiWindowsSizing sizing; @@ -51,12 +51,12 @@ static void defaultOnChanged(uiSlider *s, void *data) // do nothing } -intmax_t uiSliderValue(uiSlider *s) +int uiSliderValue(uiSlider *s) { - return (intmax_t) SendMessageW(s->hwnd, TBM_GETPOS, 0, 0); + return SendMessageW(s->hwnd, TBM_GETPOS, 0, 0); } -void uiSliderSetValue(uiSlider *s, intmax_t value) +void uiSliderSetValue(uiSlider *s, int value) { // don't use TBM_SETPOSNOTIFY; that triggers an event SendMessageW(s->hwnd, TBM_SETPOS, (WPARAM) TRUE, (LPARAM) value); @@ -68,9 +68,16 @@ void uiSliderOnChanged(uiSlider *s, void (*f)(uiSlider *, void *), void *data) s->onChangedData = data; } -uiSlider *uiNewSlider(intmax_t min, intmax_t max) +uiSlider *uiNewSlider(int min, int max) { uiSlider *s; + int temp; + + if (min >= max) { + temp = min; + min = max; + max = temp; + } uiWindowsNewControl(uiSlider, s); diff --git a/windows/spinbox.cpp b/windows/spinbox.cpp index 9118d1d7..57fb8d64 100644 --- a/windows/spinbox.cpp +++ b/windows/spinbox.cpp @@ -13,7 +13,7 @@ struct uiSpinbox { // utility functions -static intmax_t value(uiSpinbox *s) +static int value(uiSpinbox *s) { BOOL neededCap = FALSE; LRESULT val; @@ -27,11 +27,12 @@ static intmax_t value(uiSpinbox *s) SendMessageW(s->updown, UDM_SETPOS32, 0, (LPARAM) val); s->inhibitChanged = FALSE; } - return (intmax_t) val; + return val; } // control implementation +// TODO assign lResult static BOOL onWM_COMMAND(uiControl *c, HWND hwnd, WORD code, LRESULT *lResult) { uiSpinbox *s = (uiSpinbox *) c; @@ -47,10 +48,10 @@ static BOOL onWM_COMMAND(uiControl *c, HWND hwnd, WORD code, LRESULT *lResult) // This won't handle leading spaces, but spaces aren't allowed *anyway*. wtext = windowText(s->edit); if (wcscmp(wtext, L"-") == 0) { - uiFree(wtext); + uiprivFree(wtext); return TRUE; } - uiFree(wtext); + uiprivFree(wtext); // value() does the work for us value(s); (*(s->onChanged))(s, s->onChangedData); @@ -61,7 +62,7 @@ static void uiSpinboxDestroy(uiControl *c) { uiSpinbox *s = uiSpinbox(c); - uiWindowsUnregisterWM_COMMANDHandler(s->hwnd); + uiWindowsUnregisterWM_COMMANDHandler(s->edit); uiWindowsEnsureDestroyWindow(s->updown); uiWindowsEnsureDestroyWindow(s->edit); uiWindowsEnsureDestroyWindow(s->hwnd); @@ -76,7 +77,7 @@ uiWindowsControlAllDefaultsExceptDestroy(uiSpinbox) #define entryWidth 107 /* this is actually the shorter progress bar width, but Microsoft only indicates as wide as necessary */ #define entryHeight 14 -static void uiSpinboxMinimumSize(uiWindowsControl *c, intmax_t *width, intmax_t *height) +static void uiSpinboxMinimumSize(uiWindowsControl *c, int *width, int *height) { uiSpinbox *s = uiSpinbox(c); uiWindowsSizing sizing; @@ -108,7 +109,7 @@ static void spinboxArrangeChildren(uiSpinbox *s) static void recreateUpDown(uiSpinbox *s) { BOOL preserve = FALSE; - intmax_t current; + int current; // Microsoft's commctrl.h says to use this type INT min, max; @@ -156,12 +157,12 @@ static void defaultOnChanged(uiSpinbox *s, void *data) // do nothing } -intmax_t uiSpinboxValue(uiSpinbox *s) +int uiSpinboxValue(uiSpinbox *s) { return value(s); } -void uiSpinboxSetValue(uiSpinbox *s, intmax_t value) +void uiSpinboxSetValue(uiSpinbox *s, int value) { s->inhibitChanged = TRUE; SendMessageW(s->updown, UDM_SETPOS32, 0, (LPARAM) value); @@ -179,12 +180,16 @@ static void onResize(uiWindowsControl *c) spinboxRelayout(uiSpinbox(c)); } -uiSpinbox *uiNewSpinbox(intmax_t min, intmax_t max) +uiSpinbox *uiNewSpinbox(int min, int max) { uiSpinbox *s; + int temp; - if (min >= max) - complain("error: min >= max in uiNewSpinbox()"); + if (min >= max) { + temp = min; + min = max; + max = temp; + } uiWindowsNewControl(uiSpinbox, s); @@ -198,7 +203,7 @@ uiSpinbox *uiNewSpinbox(intmax_t min, intmax_t max) TRUE); uiWindowsEnsureSetParentHWND(s->edit, s->hwnd); - uiWindowsRegisterWM_COMMANDHandler(s->hwnd, onWM_COMMAND, uiControl(s)); + uiWindowsRegisterWM_COMMANDHandler(s->edit, onWM_COMMAND, uiControl(s)); uiSpinboxOnChanged(s, defaultOnChanged, NULL); recreateUpDown(s); diff --git a/windows/stddialogs.cpp b/windows/stddialogs.cpp index dfc372c7..c6ac4557 100644 --- a/windows/stddialogs.cpp +++ b/windows/stddialogs.cpp @@ -1,6 +1,9 @@ // 22 may 2015 #include "uipriv_windows.hpp" +// TODO document all this is what we want +// TODO do the same for font and color buttons + // notes: // - FOS_SUPPORTSTREAMABLEITEMS doesn't seem to be supported on windows vista, or at least not with the flags we use // - even with FOS_NOVALIDATE the dialogs will reject invalid filenames (at least on Vista, anyway) @@ -75,16 +78,26 @@ out: char *uiOpenFile(uiWindow *parent) { - return commonItemDialog(windowHWND(parent), + char *res; + + disableAllWindowsExcept(parent); + res = commonItemDialog(windowHWND(parent), CLSID_FileOpenDialog, IID_IFileOpenDialog, FOS_NOCHANGEDIR | FOS_ALLNONSTORAGEITEMS | FOS_NOVALIDATE | FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_SHAREAWARE | FOS_NOTESTFILECREATE | FOS_NODEREFERENCELINKS | FOS_FORCESHOWHIDDEN | FOS_DEFAULTNOMINIMODE); + enableAllWindowsExcept(parent); + return res; } char *uiSaveFile(uiWindow *parent) { - return commonItemDialog(windowHWND(parent), + char *res; + + disableAllWindowsExcept(parent); + res = commonItemDialog(windowHWND(parent), CLSID_FileSaveDialog, IID_IFileSaveDialog, FOS_OVERWRITEPROMPT | FOS_NOCHANGEDIR | FOS_ALLNONSTORAGEITEMS | FOS_NOVALIDATE | FOS_SHAREAWARE | FOS_NOTESTFILECREATE | FOS_NODEREFERENCELINKS | FOS_FORCESHOWHIDDEN | FOS_DEFAULTNOMINIMODE); + enableAllWindowsExcept(parent); + return res; } // TODO switch to TaskDialogIndirect()? @@ -101,16 +114,20 @@ static void msgbox(HWND parent, const char *title, const char *description, TASK if (hr != S_OK) logHRESULT(L"error showing task dialog", hr); - uiFree(wdescription); - uiFree(wtitle); + uiprivFree(wdescription); + uiprivFree(wtitle); } void uiMsgBox(uiWindow *parent, const char *title, const char *description) { + disableAllWindowsExcept(parent); msgbox(windowHWND(parent), title, description, TDCBF_OK_BUTTON, NULL); + enableAllWindowsExcept(parent); } void uiMsgBoxError(uiWindow *parent, const char *title, const char *description) { + disableAllWindowsExcept(parent); msgbox(windowHWND(parent), title, description, TDCBF_OK_BUTTON, TD_ERROR_ICON); + enableAllWindowsExcept(parent); } diff --git a/windows/tab.cpp b/windows/tab.cpp index 2dade325..e7239585 100644 --- a/windows/tab.cpp +++ b/windows/tab.cpp @@ -19,7 +19,7 @@ static LRESULT curpage(uiTab *t) return SendMessageW(t->tabHWND, TCM_GETCURSEL, 0, 0); } -static struct tabPage *tabPage(uiTab *t, intmax_t i) +static struct tabPage *tabPage(uiTab *t, int i) { return (*(t->pages))[i]; } @@ -127,10 +127,10 @@ static void uiTabSyncEnableState(uiWindowsControl *c, int enabled) uiWindowsControlDefaultSetParentHWND(uiTab) -static void uiTabMinimumSize(uiWindowsControl *c, intmax_t *width, intmax_t *height) +static void uiTabMinimumSize(uiWindowsControl *c, int *width, int *height) { uiTab *t = uiTab(c); - intmax_t pagewid, pageht; + int pagewid, pageht; struct tabPage *page; RECT r; @@ -166,6 +166,12 @@ static void uiTabMinimumSizeChanged(uiWindowsControl *c) uiWindowsControlDefaultLayoutRect(uiTab) uiWindowsControlDefaultAssignControlIDZOrder(uiTab) +static void uiTabChildVisibilityChanged(uiWindowsControl *c) +{ + // TODO eliminate the redundancy + uiWindowsControlMinimumSizeChanged(c); +} + static void tabArrangePages(uiTab *t) { LONG_PTR controlID = 100; @@ -182,7 +188,7 @@ void uiTabAppend(uiTab *t, const char *name, uiControl *child) uiTabInsertAt(t, name, t->pages->size(), child); } -void uiTabInsertAt(uiTab *t, const char *name, uintmax_t n, uiControl *child) +void uiTabInsertAt(uiTab *t, const char *name, int n, uiControl *child) { struct tabPage *page; LRESULT hide, show; @@ -206,7 +212,7 @@ void uiTabInsertAt(uiTab *t, const char *name, uintmax_t n, uiControl *child) item.pszText = wname; if (SendMessageW(t->tabHWND, TCM_INSERTITEM, (WPARAM) n, (LPARAM) (&item)) == (LRESULT) -1) logLastError(L"error adding tab to uiTab"); - uiFree(wname); + uiprivFree(wname); // we need to do this because adding the first tab doesn't send a TCN_SELCHANGE; it just shows the page show = curpage(t); @@ -216,7 +222,7 @@ void uiTabInsertAt(uiTab *t, const char *name, uintmax_t n, uiControl *child) } } -void uiTabDelete(uiTab *t, uintmax_t n) +void uiTabDelete(uiTab *t, int n) { struct tabPage *page; @@ -233,17 +239,17 @@ void uiTabDelete(uiTab *t, uintmax_t n) t->pages->erase(t->pages->begin() + n); } -uintmax_t uiTabNumPages(uiTab *t) +int uiTabNumPages(uiTab *t) { return t->pages->size(); } -int uiTabMargined(uiTab *t, uintmax_t n) +int uiTabMargined(uiTab *t, int n) { return tabPage(t, n)->margined; } -void uiTabSetMargined(uiTab *t, uintmax_t n, int margined) +void uiTabSetMargined(uiTab *t, int n, int margined) { struct tabPage *page; diff --git a/windows/table.cpp b/windows/table.cpp new file mode 100644 index 00000000..85a51c1a --- /dev/null +++ b/windows/table.cpp @@ -0,0 +1,522 @@ +#include "uipriv_windows.hpp" +#include "table.hpp" + +// general TODOs: +// - tooltips don't work properly on columns with icons (the listview always thinks there's enough room for a short label because it's not taking the icon into account); is this a bug in our LVN_GETDISPINFO handler or something else? +// - should clicking on some other column of the same row, even one that doesn't edit, cancel editing? +// - implement keyboard accessibility +// - implement accessibility in general (Dynamic Annotations maybe?) +// - if I didn't handle these already: "drawing focus rects here, subitem navigation and activation with the keyboard" + +uiTableModel *uiNewTableModel(uiTableModelHandler *mh) +{ + uiTableModel *m; + + m = uiprivNew(uiTableModel); + m->mh = mh; + m->tables = new std::vector; + return m; +} + +void uiFreeTableModel(uiTableModel *m) +{ + delete m->tables; + uiprivFree(m); +} + +// TODO document that when this is called, the model must return the new row count when asked +void uiTableModelRowInserted(uiTableModel *m, int newIndex) +{ + LVITEMW item; + int newCount; + + newCount = uiprivTableModelNumRows(m); + ZeroMemory(&item, sizeof (LVITEMW)); + item.mask = 0; + item.iItem = newIndex; + item.iSubItem = 0; + for (auto t : *(m->tables)) { + // actually insert the rows + if (SendMessageW(t->hwnd, LVM_SETITEMCOUNT, (WPARAM) newCount, LVSICF_NOINVALIDATEALL) == 0) + logLastError(L"error calling LVM_SETITEMCOUNT in uiTableModelRowInserted()"); + // and redraw every row from the new row down to simulate adding it + if (SendMessageW(t->hwnd, LVM_REDRAWITEMS, (WPARAM) newIndex, (LPARAM) (newCount - 1)) == FALSE) + logLastError(L"error calling LVM_REDRAWITEMS in uiTableModelRowInserted()"); + + // update selection state + if (SendMessageW(t->hwnd, LVM_INSERTITEM, 0, (LPARAM) (&item)) == (LRESULT) (-1)) + logLastError(L"error calling LVM_INSERTITEM in uiTableModelRowInserted() to update selection state"); + } +} + +// TODO compare LVM_UPDATE and LVM_REDRAWITEMS +void uiTableModelRowChanged(uiTableModel *m, int index) +{ + for (auto t : *(m->tables)) + if (SendMessageW(t->hwnd, LVM_UPDATE, (WPARAM) index, 0) == (LRESULT) (-1)) + logLastError(L"error calling LVM_UPDATE in uiTableModelRowChanged()"); +} + +// TODO document that when this is called, the model must return the OLD row count when asked +// TODO for this and the above, see what GTK+ requires and adjust accordingly +void uiTableModelRowDeleted(uiTableModel *m, int oldIndex) +{ + int newCount; + + newCount = uiprivTableModelNumRows(m); + newCount--; + for (auto t : *(m->tables)) { + // update selection state + if (SendMessageW(t->hwnd, LVM_DELETEITEM, (WPARAM) oldIndex, 0) == (LRESULT) (-1)) + logLastError(L"error calling LVM_DELETEITEM in uiTableModelRowDeleted() to update selection state"); + + // actually delete the rows + if (SendMessageW(t->hwnd, LVM_SETITEMCOUNT, (WPARAM) newCount, LVSICF_NOINVALIDATEALL) == 0) + logLastError(L"error calling LVM_SETITEMCOUNT in uiTableModelRowDeleted()"); + // and redraw every row from the new nth row down to simulate removing the old nth row + if (SendMessageW(t->hwnd, LVM_REDRAWITEMS, (WPARAM) oldIndex, (LPARAM) (newCount - 1)) == FALSE) + logLastError(L"error calling LVM_REDRAWITEMS in uiTableModelRowDeleted()"); + } +} + +uiTableModelHandler *uiprivTableModelHandler(uiTableModel *m) +{ + return m->mh; +} + +// TODO explain all this +static LRESULT CALLBACK tableSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIDSubclass, DWORD_PTR dwRefData) +{ + uiTable *t = (uiTable *) dwRefData; + NMHDR *nmhdr = (NMHDR *) lParam; + bool finishEdit, abortEdit; + HWND header; + LRESULT lResult; + HRESULT hr; + + finishEdit = false; + abortEdit = false; + switch (uMsg) { + case WM_TIMER: + if (wParam == (WPARAM) (&(t->inDoubleClickTimer))) { + t->inDoubleClickTimer = FALSE; + // TODO check errors + KillTimer(hwnd, wParam); + return 0; + } + if (wParam != (WPARAM) t) + break; + // TODO only increment and update if visible? + for (auto &i : *(t->indeterminatePositions)) { + i.second++; + // TODO check errors + SendMessageW(hwnd, LVM_UPDATE, (WPARAM) (i.first.first), 0); + } + return 0; + case WM_LBUTTONDOWN: + t->inLButtonDown = TRUE; + lResult = DefSubclassProc(hwnd, uMsg, wParam, lParam); + t->inLButtonDown = FALSE; + return lResult; + case WM_COMMAND: + if (HIWORD(wParam) == EN_UPDATE) { + // the real list view resizes the edit control on this notification specifically + hr = uiprivTableResizeWhileEditing(t); + if (hr != S_OK) { + // TODO + } + break; + } + // the real list view accepts changes in this case + if (HIWORD(wParam) == EN_KILLFOCUS) + finishEdit = true; + break; // don't override default handling + case WM_NOTIFY: + // list view accepts changes on column resize, but does not provide such notifications :/ + header = (HWND) SendMessageW(t->hwnd, LVM_GETHEADER, 0, 0); + if (nmhdr->hwndFrom == header) { + NMHEADERW *nm = (NMHEADERW *) nmhdr; + + switch (nmhdr->code) { + case HDN_ITEMCHANGED: + if ((nm->pitem->mask & HDI_WIDTH) == 0) + break; + // fall through + case HDN_DIVIDERDBLCLICK: + case HDN_TRACK: + case HDN_ENDTRACK: + finishEdit = true; + } + } + // I think this mirrors the WM_COMMAND one above... TODO + if (nmhdr->code == NM_KILLFOCUS) + finishEdit = true; + break; // don't override default handling + case LVM_CANCELEDITLABEL: + finishEdit = true; + // TODO properly imitate notifiactions + break; // don't override default handling + // TODO finish edit on WM_WINDOWPOSCHANGING and WM_SIZE? + // for the next three: this item is about to go away; don't bother keeping changes + case LVM_SETITEMCOUNT: + if (wParam <= t->editedItem) + abortEdit = true; + break; // don't override default handling + case LVM_DELETEITEM: + if (wParam == t->editedItem) + abortEdit = true; + break; // don't override default handling + case LVM_DELETEALLITEMS: + abortEdit = true; + break; // don't override default handling + case WM_NCDESTROY: + if (RemoveWindowSubclass(hwnd, tableSubProc, uIDSubclass) == FALSE) + logLastError(L"RemoveWindowSubclass()"); + // fall through + } + if (finishEdit) { + hr = uiprivTableFinishEditingText(t); + if (hr != S_OK) { + // TODO + } + } else if (abortEdit) { + hr = uiprivTableAbortEditingText(t); + if (hr != S_OK) { + // TODO + } + } + return DefSubclassProc(hwnd, uMsg, wParam, lParam); +} + +int uiprivTableProgress(uiTable *t, int item, int subitem, int modelColumn, LONG *pos) +{ + uiTableValue *value; + int progress; + std::pair p; + std::map, LONG>::iterator iter; + bool startTimer = false; + bool stopTimer = false; + + value = uiprivTableModelCellValue(t->model, item, modelColumn); + progress = uiTableValueInt(value); + uiFreeTableValue(value); + + p.first = item; + p.second = subitem; + iter = t->indeterminatePositions->find(p); + if (iter == t->indeterminatePositions->end()) { + if (progress == -1) { + startTimer = t->indeterminatePositions->size() == 0; + (*(t->indeterminatePositions))[p] = 0; + if (pos != NULL) + *pos = 0; + } + } else + if (progress != -1) { + t->indeterminatePositions->erase(p); + stopTimer = t->indeterminatePositions->size() == 0; + } else if (pos != NULL) + *pos = iter->second; + + if (startTimer) + // the interval shown here is PBM_SETMARQUEE's default + // TODO should we pass a function here instead? it seems to be called by DispatchMessage(), not DefWindowProc(), but I'm still unsure + if (SetTimer(t->hwnd, (UINT_PTR) t, 30, NULL) == 0) + logLastError(L"SetTimer()"); + if (stopTimer) + if (KillTimer(t->hwnd, (UINT_PTR) (&t)) == 0) + logLastError(L"KillTimer()"); + + return progress; +} + +// TODO properly integrate compound statements +static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) +{ + uiTable *t = uiTable(c); + HRESULT hr; + + switch (nmhdr->code) { + case LVN_GETDISPINFO: + hr = uiprivTableHandleLVN_GETDISPINFO(t, (NMLVDISPINFOW *) nmhdr, lResult); + if (hr != S_OK) { + // TODO + return FALSE; + } + return TRUE; + case NM_CUSTOMDRAW: + hr = uiprivTableHandleNM_CUSTOMDRAW(t, (NMLVCUSTOMDRAW *) nmhdr, lResult); + if (hr != S_OK) { + // TODO + return FALSE; + } + return TRUE; + case NM_CLICK: +#if 0 + { + NMITEMACTIVATE *nm = (NMITEMACTIVATE *) nmhdr; + LVHITTESTINFO ht; + WCHAR buf[256]; + + ZeroMemory(&ht, sizeof (LVHITTESTINFO)); + ht.pt = nm->ptAction; + if (SendMessageW(t->hwnd, LVM_SUBITEMHITTEST, 0, (LPARAM) (&ht)) == (LRESULT) (-1)) + MessageBoxW(GetAncestor(t->hwnd, GA_ROOT), L"No hit", L"No hit", MB_OK); + else { + wsprintf(buf, L"item %d subitem %d htflags 0x%I32X", + ht.iItem, ht.iSubItem, ht.flags); + MessageBoxW(GetAncestor(t->hwnd, GA_ROOT), buf, buf, MB_OK); + } + } + *lResult = 0; + return TRUE; +#else + hr = uiprivTableHandleNM_CLICK(t, (NMITEMACTIVATE *) nmhdr, lResult); + if (hr != S_OK) { + // TODO + return FALSE; + } + return TRUE; +#endif + case LVN_ITEMCHANGED: + { + NMLISTVIEW *nm = (NMLISTVIEW *) nmhdr; + UINT oldSelected, newSelected; + HRESULT hr; + + // TODO clean up these if cases + if (!t->inLButtonDown && t->edit == NULL) + return FALSE; + oldSelected = nm->uOldState & LVIS_SELECTED; + newSelected = nm->uNewState & LVIS_SELECTED; + if (t->inLButtonDown && oldSelected == 0 && newSelected != 0) { + t->inDoubleClickTimer = TRUE; + // TODO check error + SetTimer(t->hwnd, (UINT_PTR) (&(t->inDoubleClickTimer)), + GetDoubleClickTime(), NULL); + *lResult = 0; + return TRUE; + } + // the nm->iItem == -1 case is because that is used if "the change has been applied to all items in the list view" + if (t->edit != NULL && oldSelected != 0 && newSelected == 0 && (t->editedItem == nm->iItem || nm->iItem == -1)) { + // TODO see if the real list view accepts or rejects changes here; Windows Explorer accepts + hr = uiprivTableFinishEditingText(t); + if (hr != S_OK) { + // TODO + return FALSE; + } + *lResult = 0; + return TRUE; + } + return FALSE; + } + // the real list view accepts changes when scrolling or clicking column headers + case LVN_BEGINSCROLL: + case LVN_COLUMNCLICK: + hr = uiprivTableFinishEditingText(t); + if (hr != S_OK) { + // TODO + return FALSE; + } + *lResult = 0; + return TRUE; + } + return FALSE; +} + +static void uiTableDestroy(uiControl *c) +{ + uiTable *t = uiTable(c); + uiTableModel *model = t->model; + std::vector::iterator it; + HRESULT hr; + + hr = uiprivTableAbortEditingText(t); + if (hr != S_OK) { + // TODO + } + uiWindowsUnregisterWM_NOTIFYHandler(t->hwnd); + uiWindowsEnsureDestroyWindow(t->hwnd); + // detach table from model + for (it = model->tables->begin(); it != model->tables->end(); it++) { + if (*it == t) { + model->tables->erase(it); + break; + } + } + // free the columns + for (auto col : *(t->columns)) + uiprivFree(col); + delete t->columns; + // t->imagelist will be automatically destroyed + delete t->indeterminatePositions; + uiFreeControl(uiControl(t)); +} + +uiWindowsControlAllDefaultsExceptDestroy(uiTable) + +// suggested listview sizing from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing: +// "columns widths that avoid truncated data x an integral number of items" +// Don't think that'll cut it when some cells have overlong data (eg +// stupidly long URLs). So for now, just hardcode a minimum. +// TODO Investigate using LVM_GETHEADER/HDM_LAYOUT here +// TODO investigate using LVM_APPROXIMATEVIEWRECT here +#define tableMinWidth 107 /* in line with other controls */ +#define tableMinHeight (14 * 3) /* header + 2 lines (roughly) */ + +static void uiTableMinimumSize(uiWindowsControl *c, int *width, int *height) +{ + uiTable *t = uiTable(c); + uiWindowsSizing sizing; + int x, y; + + x = tableMinWidth; + y = tableMinHeight; + uiWindowsGetSizing(t->hwnd, &sizing); + uiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y); + *width = x; + *height = y; +} + +static uiprivTableColumnParams *appendColumn(uiTable *t, const char *name, int colfmt) +{ + WCHAR *wstr; + LVCOLUMNW lvc; + uiprivTableColumnParams *p; + + ZeroMemory(&lvc, sizeof (LVCOLUMNW)); + lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT; + lvc.fmt = colfmt; + lvc.cx = 120; // TODO + wstr = toUTF16(name); + lvc.pszText = wstr; + if (SendMessageW(t->hwnd, LVM_INSERTCOLUMNW, t->nColumns, (LPARAM) (&lvc)) == (LRESULT) (-1)) + logLastError(L"error calling LVM_INSERTCOLUMNW in appendColumn()"); + uiprivFree(wstr); + t->nColumns++; + + p = uiprivNew(uiprivTableColumnParams); + p->textModelColumn = -1; + p->textEditableModelColumn = -1; + p->textParams = uiprivDefaultTextColumnOptionalParams; + p->imageModelColumn = -1; + p->checkboxModelColumn = -1; + p->checkboxEditableModelColumn = -1; + p->progressBarModelColumn = -1; + p->buttonModelColumn = -1; + t->columns->push_back(p); + return p; +} + +void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) +{ + uiprivTableColumnParams *p; + + p = appendColumn(t, name, LVCFMT_LEFT); + p->textModelColumn = textModelColumn; + p->textEditableModelColumn = textEditableModelColumn; + if (textParams != NULL) + p->textParams = *textParams; +} + +void uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn) +{ + uiprivTableColumnParams *p; + + p = appendColumn(t, name, LVCFMT_LEFT); + p->imageModelColumn = imageModelColumn; +} + +void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) +{ + uiprivTableColumnParams *p; + + p = appendColumn(t, name, LVCFMT_LEFT); + p->textModelColumn = textModelColumn; + p->textEditableModelColumn = textEditableModelColumn; + if (textParams != NULL) + p->textParams = *textParams; + p->imageModelColumn = imageModelColumn; +} + +void uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn) +{ + uiprivTableColumnParams *p; + + p = appendColumn(t, name, LVCFMT_LEFT); + p->checkboxModelColumn = checkboxModelColumn; + p->checkboxEditableModelColumn = checkboxEditableModelColumn; +} + +void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams) +{ + uiprivTableColumnParams *p; + + p = appendColumn(t, name, LVCFMT_LEFT); + p->textModelColumn = textModelColumn; + p->textEditableModelColumn = textEditableModelColumn; + if (textParams != NULL) + p->textParams = *textParams; + p->checkboxModelColumn = checkboxModelColumn; + p->checkboxEditableModelColumn = checkboxEditableModelColumn; +} + +void uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressModelColumn) +{ + uiprivTableColumnParams *p; + + p = appendColumn(t, name, LVCFMT_LEFT); + p->progressBarModelColumn = progressModelColumn; +} + +void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonModelColumn, int buttonClickableModelColumn) +{ + uiprivTableColumnParams *p; + + // TODO see if we can get rid of this parameter + p = appendColumn(t, name, LVCFMT_LEFT); + p->buttonModelColumn = buttonModelColumn; + p->buttonClickableModelColumn = buttonClickableModelColumn; +} + +uiTable *uiNewTable(uiTableParams *p) +{ + uiTable *t; + int n; + HRESULT hr; + + uiWindowsNewControl(uiTable, t); + + t->columns = new std::vector; + t->model = p->Model; + t->backgroundColumn = p->RowBackgroundColorModelColumn; + + // WS_CLIPCHILDREN is here to prevent drawing over the edit box used for editing text + t->hwnd = uiWindowsEnsureCreateControlHWND(WS_EX_CLIENTEDGE, + WC_LISTVIEW, L"", + LVS_REPORT | LVS_OWNERDATA | LVS_SINGLESEL | WS_CLIPCHILDREN | WS_TABSTOP | WS_HSCROLL | WS_VSCROLL, + hInstance, NULL, + TRUE); + t->model->tables->push_back(t); + uiWindowsRegisterWM_NOTIFYHandler(t->hwnd, onWM_NOTIFY, uiControl(t)); + + // TODO: try LVS_EX_AUTOSIZECOLUMNS + // TODO check error + SendMessageW(t->hwnd, LVM_SETEXTENDEDLISTVIEWSTYLE, + (WPARAM) (LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP | LVS_EX_SUBITEMIMAGES), + (LPARAM) (LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP | LVS_EX_SUBITEMIMAGES)); + n = uiprivTableModelNumRows(t->model); + if (SendMessageW(t->hwnd, LVM_SETITEMCOUNT, (WPARAM) n, 0) == 0) + logLastError(L"error calling LVM_SETITEMCOUNT in uiNewTable()"); + + hr = uiprivUpdateImageListSize(t); + if (hr != S_OK) { + // TODO + } + + t->indeterminatePositions = new std::map, LONG>; + if (SetWindowSubclass(t->hwnd, tableSubProc, 0, (DWORD_PTR) t) == FALSE) + logLastError(L"SetWindowSubclass()"); + + return t; +} diff --git a/windows/table.hpp b/windows/table.hpp new file mode 100644 index 00000000..71946d62 --- /dev/null +++ b/windows/table.hpp @@ -0,0 +1,81 @@ +// 10 june 2018 +#include "../common/table.h" + +// table.cpp +#define uiprivNumLVN_GETDISPINFOSkip 3 +struct uiTableModel { + uiTableModelHandler *mh; + std::vector *tables; +}; +typedef struct uiprivTableColumnParams uiprivTableColumnParams; +struct uiprivTableColumnParams { + int textModelColumn; + int textEditableModelColumn; + uiTableTextColumnOptionalParams textParams; + + int imageModelColumn; + + int checkboxModelColumn; + int checkboxEditableModelColumn; + + int progressBarModelColumn; + + int buttonModelColumn; + int buttonClickableModelColumn; +}; +struct uiTable { + uiWindowsControl c; + uiTableModel *model; + HWND hwnd; + std::vector *columns; + WPARAM nColumns; + int backgroundColumn; + // TODO make sure replacing images while selected in the listview is even allowed + HIMAGELIST imagelist; + // TODO document all this + std::map, LONG> *indeterminatePositions; + BOOL inLButtonDown; + // TODO is this even necessary? it seems NM_CLICK is not sent if NM_DBLCLICK or LVN_ITEMACTIVATE (one of the two) happens... + BOOL inDoubleClickTimer; + HWND edit; + int editedItem; + int editedSubitem; +}; +extern int uiprivTableProgress(uiTable *t, int item, int subitem, int modelColumn, LONG *pos); + +// tabledispinfo.cpp +extern HRESULT uiprivTableHandleLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm, LRESULT *lResult); + +// tabledraw.cpp +extern HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *lResult); +extern HRESULT uiprivUpdateImageListSize(uiTable *t); + +// tableediting.cpp +extern HRESULT uiprivTableResizeWhileEditing(uiTable *t); +extern HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResult); +extern HRESULT uiprivTableFinishEditingText(uiTable *t); +extern HRESULT uiprivTableAbortEditingText(uiTable *t); + +// tablemetrics.cpp +typedef struct uiprivTableMetrics uiprivTableMetrics; +struct uiprivTableMetrics { + BOOL hasText; + BOOL hasImage; + BOOL focused; + BOOL selected; + + RECT itemBounds; + RECT itemIcon; + RECT itemLabel; + RECT subitemBounds; + RECT subitemIcon; + RECT subitemLabel; + + LRESULT bitmapMargin; + int cxIcon; + int cyIcon; + + RECT realTextBackground; + RECT realTextRect; +}; +extern HRESULT uiprivTableGetMetrics(uiTable *t, int iItem, int iSubItem, uiprivTableMetrics **mout); diff --git a/windows/tabledispinfo.cpp b/windows/tabledispinfo.cpp new file mode 100644 index 00000000..4198a1a2 --- /dev/null +++ b/windows/tabledispinfo.cpp @@ -0,0 +1,99 @@ +// 13 june 2018 +#include "uipriv_windows.hpp" +#include "table.hpp" + +// further reading: +// - https://msdn.microsoft.com/en-us/library/ye4z8x58.aspx + +static HRESULT handleLVIF_TEXT(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p) +{ + int strcol; + uiTableValue *value; + WCHAR *wstr; + int progress; + HRESULT hr; + + if ((nm->item.mask & LVIF_TEXT) == 0) + return S_OK; + + strcol = -1; + if (p->textModelColumn != -1) + strcol = p->textModelColumn; + else if (p->buttonModelColumn != -1) + strcol = p->buttonModelColumn; + if (strcol != -1) { + value = uiprivTableModelCellValue(t->model, nm->item.iItem, strcol); + wstr = toUTF16(uiTableValueString(value)); + uiFreeTableValue(value); + // We *could* just make pszText into a freshly allocated + // conversion and avoid the limitation of cchTextMax. + // But then, we would have to keep things around for some + // amount of time (some pages on MSDN say 2 additional + // LVN_GETDISPINFO messages). And in practice, anything + // that results in extra LVN_GETDISPINFO messages (such + // as LVN_GETITEMRECT with LVIR_LABEL) will break this + // counting. + // TODO make it so we don't have to make a copy; instead we can convert directly into pszText (this will also avoid the risk of having a dangling surrogate pair at the end) + wcsncpy(nm->item.pszText, wstr, nm->item.cchTextMax); + nm->item.pszText[nm->item.cchTextMax - 1] = L'\0'; + uiprivFree(wstr); + return S_OK; + } + + if (p->progressBarModelColumn != -1) { + progress = uiprivTableProgress(t, nm->item.iItem, nm->item.iSubItem, p->progressBarModelColumn, NULL); + + if (progress == -1) { + // TODO either localize this or replace it with something that's language-neutral + // TODO ensure null terminator + wcsncpy(nm->item.pszText, L"Indeterminate", nm->item.cchTextMax); + return S_OK; + } + // TODO ensure null terminator + _snwprintf(nm->item.pszText, nm->item.cchTextMax, L"%d%%", progress); + return S_OK; + } + + return S_OK; +} + +static HRESULT handleLVIF_IMAGE(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p) +{ + uiTableValue *value; + HRESULT hr; + + if (nm->item.iSubItem == 0 && p->imageModelColumn == -1 && p->checkboxModelColumn == -1) { + // Having an image list always leaves space for an image + // on the main item :| + // Other places on the internet imply that you should be + // able to do this but that it shouldn't work, but it works + // perfectly (and pixel-perfectly too) for me, so... + nm->item.mask |= LVIF_INDENT; + nm->item.iIndent = -1; + } + if ((nm->item.mask & LVIF_IMAGE) == 0) + return S_OK; // nothing to do here + + // TODO see if the -1 part is correct + // TODO see if we should use state instead of images for checkbox value + nm->item.iImage = -1; + if (p->imageModelColumn != -1 || p->checkboxModelColumn != -1) + nm->item.iImage = 0; + return S_OK; +} + +HRESULT uiprivTableHandleLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm, LRESULT *lResult) +{ + uiprivTableColumnParams *p; + HRESULT hr; + + p = (*(t->columns))[nm->item.iSubItem]; + hr = handleLVIF_TEXT(t, nm, p); + if (hr != S_OK) + return hr; + hr = handleLVIF_IMAGE(t, nm, p); + if (hr != S_OK) + return hr; + *lResult = 0; + return S_OK; +} diff --git a/windows/tabledraw.cpp b/windows/tabledraw.cpp new file mode 100644 index 00000000..eb50f28a --- /dev/null +++ b/windows/tabledraw.cpp @@ -0,0 +1,739 @@ +// 14 june 2018 +#include "uipriv_windows.hpp" +#include "table.hpp" + +// TODOs: +// - properly hide selection when not focused (or switch on LVS_SHOWSELALWAYS and draw that state) + +// TODO maybe split this into item and subitem structs? +struct drawState { + uiTable *t; + uiTableModel *model; + uiprivTableColumnParams *p; + + HDC dc; + int iItem; + int iSubItem; + + uiprivTableMetrics *m; + + COLORREF bgColor; + HBRUSH bgBrush; + BOOL freeBgBrush; + COLORREF textColor; + HBRUSH textBrush; + BOOL freeTextBrush; +}; + +static HRESULT drawBackgrounds(HRESULT hr, struct drawState *s) +{ + if (hr != S_OK) + return hr; + if (s->m->hasImage) + if (FillRect(s->dc, &(s->m->subitemIcon), GetSysColorBrush(COLOR_WINDOW)) == 0) { + logLastError(L"FillRect() icon"); + return E_FAIL; + } + if (FillRect(s->dc, &(s->m->realTextBackground), s->bgBrush) == 0) { + logLastError(L"FillRect()"); + return E_FAIL; + } + return S_OK; +} + +static void centerImageRect(RECT *image, RECT *space) +{ + LONG xoff, yoff; + + // first make sure both have the same upper-left + xoff = image->left - space->left; + yoff = image->top - space->top; + image->left -= xoff; + image->top -= yoff; + image->right -= xoff; + image->bottom -= yoff; + + // now center + xoff = ((space->right - space->left) - (image->right - image->left)) / 2; + yoff = ((space->bottom - space->top) - (image->bottom - image->top)) / 2; + image->left += xoff; + image->top += yoff; + image->right += xoff; + image->bottom += yoff; +} + +static HRESULT drawImagePart(HRESULT hr, struct drawState *s) +{ + uiTableValue *value; + IWICBitmap *wb; + HBITMAP b; + RECT r; + UINT fStyle; + + if (hr != S_OK) + return hr; + if (s->p->imageModelColumn == -1) + return S_OK; + + value = uiprivTableModelCellValue(s->model, s->iItem, s->p->imageModelColumn); + wb = uiprivImageAppropriateForDC(uiTableValueImage(value), s->dc); + uiFreeTableValue(value); + + hr = uiprivWICToGDI(wb, s->dc, s->m->cxIcon, s->m->cyIcon, &b); + if (hr != S_OK) + return hr; + // TODO rewrite this condition to make more sense; possibly swap the if and else blocks too + // TODO proper cleanup + if (ImageList_GetImageCount(s->t->imagelist) > 1) { + if (ImageList_Replace(s->t->imagelist, 0, b, NULL) == 0) { + logLastError(L"ImageList_Replace()"); + return E_FAIL; + } + } else + if (ImageList_Add(s->t->imagelist, b, NULL) == -1) { + logLastError(L"ImageList_Add()"); + return E_FAIL; + } + // TODO error check + DeleteObject(b); + + r = s->m->subitemIcon; + r.right = r.left + s->m->cxIcon; + r.bottom = r.top + s->m->cyIcon; + centerImageRect(&r, &(s->m->subitemIcon)); + fStyle = ILD_NORMAL; + if (s->m->selected) + fStyle = ILD_SELECTED; + if (ImageList_Draw(s->t->imagelist, 0, + s->dc, r.left, r.top, fStyle) == 0) { + logLastError(L"ImageList_Draw()"); + return E_FAIL; + } + return S_OK; +} + +// references for checkbox drawing: +// - https://blogs.msdn.microsoft.com/oldnewthing/20171129-00/?p=97485 +// - https://blogs.msdn.microsoft.com/oldnewthing/20171201-00/?p=97505 + +static HRESULT drawUnthemedCheckbox(struct drawState *s, int checked, int enabled) +{ + RECT r; + UINT state; + + r = s->m->subitemIcon; + // this is what the actual list view LVS_EX_CHECKBOXES code does to size the checkboxes + // TODO reverify the initial size + r.right = r.left + GetSystemMetrics(SM_CXSMICON); + r.bottom = r.top + GetSystemMetrics(SM_CYSMICON); + if (InflateRect(&r, -GetSystemMetrics(SM_CXEDGE), -GetSystemMetrics(SM_CYEDGE)) == 0) { + logLastError(L"InflateRect()"); + return E_FAIL; + } + r.right++; + r.bottom++; + + centerImageRect(&r, &(s->m->subitemIcon)); + state = DFCS_BUTTONCHECK | DFCS_FLAT; + if (checked) + state |= DFCS_CHECKED; + if (!enabled) + state |= DFCS_INACTIVE; + if (DrawFrameControl(s->dc, &r, DFC_BUTTON, state) == 0) { + logLastError(L"DrawFrameControl()"); + return E_FAIL; + } + return S_OK; +} + +static HRESULT drawThemedCheckbox(struct drawState *s, HTHEME theme, int checked, int enabled) +{ + RECT r; + SIZE size; + int state; + HRESULT hr; + + hr = GetThemePartSize(theme, s->dc, + BP_CHECKBOX, CBS_UNCHECKEDNORMAL, + NULL, TS_DRAW, &size); + if (hr != S_OK) { + logHRESULT(L"GetThemePartSize()", hr); + return hr; // TODO fall back? + } + r = s->m->subitemIcon; + r.right = r.left + size.cx; + r.bottom = r.top + size.cy; + + centerImageRect(&r, &(s->m->subitemIcon)); + if (!checked && enabled) + state = CBS_UNCHECKEDNORMAL; + else if (checked && enabled) + state = CBS_CHECKEDNORMAL; + else if (!checked && !enabled) + state = CBS_UNCHECKEDDISABLED; + else + state = CBS_CHECKEDDISABLED; + hr = DrawThemeBackground(theme, s->dc, + BP_CHECKBOX, state, + &r, NULL); + if (hr != S_OK) { + logHRESULT(L"DrawThemeBackground()", hr); + return hr; + } + return S_OK; +} + +static HRESULT drawCheckboxPart(HRESULT hr, struct drawState *s) +{ + uiTableValue *value; + int checked, enabled; + HTHEME theme; + + if (hr != S_OK) + return hr; + if (s->p->checkboxModelColumn == -1) + return S_OK; + + value = uiprivTableModelCellValue(s->model, s->iItem, s->p->checkboxModelColumn); + checked = uiTableValueInt(value); + uiFreeTableValue(value); + enabled = uiprivTableModelCellEditable(s->model, s->iItem, s->p->checkboxEditableModelColumn); + + theme = OpenThemeData(s->t->hwnd, L"button"); + if (theme != NULL) { + hr = drawThemedCheckbox(s, theme, checked, enabled); + if (hr != S_OK) + return hr; + hr = CloseThemeData(theme); + if (hr != S_OK) { + logHRESULT(L"CloseThemeData()", hr); + return hr; + } + } else { + hr = drawUnthemedCheckbox(s, checked, enabled); + if (hr != S_OK) + return hr; + } + return S_OK; +} + +static HRESULT drawTextPart(HRESULT hr, struct drawState *s) +{ + COLORREF prevText; + int prevMode; + RECT r; + uiTableValue *value; + WCHAR *wstr; + + if (hr != S_OK) + return hr; + if (!s->m->hasText) + return S_OK; + // don't draw the text underneath an edit control + if (s->t->edit != NULL && s->t->editedItem == s->iItem && s->t->editedSubitem == s->iSubItem) + return S_OK; + + prevText = SetTextColor(s->dc, s->textColor); + if (prevText == CLR_INVALID) { + logLastError(L"SetTextColor()"); + return E_FAIL; + } + prevMode = SetBkMode(s->dc, TRANSPARENT); + if (prevMode == 0) { + logLastError(L"SetBkMode()"); + return E_FAIL; + } + + value = uiprivTableModelCellValue(s->model, s->iItem, s->p->textModelColumn); + wstr = toUTF16(uiTableValueString(value)); + uiFreeTableValue(value); + // These flags are a menagerie of flags from various sources: + // guessing, the Windows 2000 source leak, various custom + // draw examples on the web, etc. + // TODO find the real correct flags + if (DrawTextW(s->dc, wstr, -1, &(s->m->realTextRect), DT_LEFT | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX | DT_EDITCONTROL) == 0) { + uiprivFree(wstr); + logLastError(L"DrawTextW()"); + return E_FAIL; + } + uiprivFree(wstr); + + // TODO decide once and for all what to compare to here and with SelectObject() + if (SetBkMode(s->dc, prevMode) != TRANSPARENT) { + logLastError(L"SetBkMode() prev"); + return E_FAIL; + } + if (SetTextColor(s->dc, prevText) != s->textColor) { + logLastError(L"SetTextColor() prev"); + return E_FAIL; + } + return S_OK; +} + +// much of this is to imitate what shell32.dll's CDrawProgressBar does +#define indeterminateSegments 8 + +static HRESULT drawProgressBarPart(HRESULT hr, struct drawState *s) +{ + int progress; + LONG indeterminatePos; + HTHEME theme; + RECT r; + RECT rBorder, rFill[2]; + int i, nFill; + TEXTMETRICW tm; + int sysColor; + + if (hr != S_OK) + return hr; + if (s->p->progressBarModelColumn == -1) + return S_OK; + + progress = uiprivTableProgress(s->t, s->iItem, s->iSubItem, s->p->progressBarModelColumn, &indeterminatePos); + + theme = OpenThemeData(s->t->hwnd, L"PROGRESS"); + + if (GetTextMetricsW(s->dc, &tm) == 0) { + logLastError(L"GetTextMetricsW()"); + hr = E_FAIL; + goto fail; + } + r = s->m->subitemBounds; + // this sets the height of the progressbar and vertically centers it in one fell swoop + r.top += (r.bottom - tm.tmHeight - r.top) / 2; + r.bottom = r.top + tm.tmHeight; + + // TODO check errors + rBorder = r; + InflateRect(&rBorder, -1, -1); + if (theme != NULL) { + RECT crect; + + hr = GetThemeBackgroundContentRect(theme, s->dc, + PP_TRANSPARENTBAR, PBBS_NORMAL, + &rBorder, &crect); + if (hr != S_OK) { + logHRESULT(L"GetThemeBackgroundContentRect()", hr); + goto fail; + } + hr = DrawThemeBackground(theme, s->dc, + PP_TRANSPARENTBAR, PBBS_NORMAL, + &crect, NULL); + if (hr != S_OK) { + logHRESULT(L"DrawThemeBackground() border", hr); + goto fail; + } + } else { + HPEN pen, prevPen; + HBRUSH brush, prevBrush; + + sysColor = COLOR_HIGHLIGHT; + if (s->m->selected) + sysColor = COLOR_HIGHLIGHTTEXT; + + // TODO check errors everywhere + pen = CreatePen(PS_SOLID, 1, GetSysColor(sysColor)); + prevPen = (HPEN) SelectObject(s->dc, pen); + brush = (HBRUSH) GetStockObject(NULL_BRUSH); + prevBrush = (HBRUSH) SelectObject(s->dc, brush); + Rectangle(s->dc, rBorder.left, rBorder.top, rBorder.right, rBorder.bottom); + SelectObject(s->dc, prevBrush); + SelectObject(s->dc, prevPen); + DeleteObject(pen); + } + + nFill = 1; + rFill[0] = r; + // TODO check error + InflateRect(&rFill[0], -1, -1); + if (progress != -1) + rFill[0].right -= (rFill[0].right - rFill[0].left) * (100 - progress) / 100; + else { + LONG barWidth; + LONG pieceWidth; + + // TODO explain all this + // TODO this should really start the progressbar scrolling into view instead of already on screen when first set + rFill[1] = rFill[0]; // save in case we need it + barWidth = rFill[0].right - rFill[0].left; + pieceWidth = barWidth / indeterminateSegments; + rFill[0].left += indeterminatePos % barWidth; + if ((rFill[0].left + pieceWidth) >= rFill[0].right) { + // make this piece wrap back around + nFill++; + rFill[1].right = rFill[1].left + (pieceWidth - (rFill[0].right - rFill[0].left)); + } else + rFill[0].right = rFill[0].left + pieceWidth; + } + for (i = 0; i < nFill; i++) + if (theme != NULL) { + hr = DrawThemeBackground(theme, s->dc, + PP_FILL, PBFS_NORMAL, + &rFill[i], NULL); + if (hr != S_OK) { + logHRESULT(L"DrawThemeBackground() fill", hr); + goto fail; + } + } else + // TODO check errors + FillRect(s->dc, &rFill[i], GetSysColorBrush(sysColor)); + + hr = S_OK; +fail: + // TODO check errors + if (theme != NULL) + CloseThemeData(theme); + return hr; +} + +static HRESULT drawButtonPart(HRESULT hr, struct drawState *s) +{ + uiTableValue *value; + WCHAR *wstr; + bool enabled; + HTHEME theme; + RECT r; + TEXTMETRICW tm; + + if (hr != S_OK) + return hr; + if (s->p->buttonModelColumn == -1) + return S_OK; + + value = uiprivTableModelCellValue(s->model, s->iItem, s->p->buttonModelColumn); + wstr = toUTF16(uiTableValueString(value)); + uiFreeTableValue(value); + enabled = uiprivTableModelCellEditable(s->model, s->iItem, s->p->buttonClickableModelColumn); + + theme = OpenThemeData(s->t->hwnd, L"button"); + + if (GetTextMetricsW(s->dc, &tm) == 0) { + logLastError(L"GetTextMetricsW()"); + hr = E_FAIL; + goto fail; + } + r = s->m->subitemBounds; + + if (theme != NULL) { + int state; + + state = PBS_NORMAL; + if (!enabled) + state = PBS_DISABLED; + hr = DrawThemeBackground(theme, s->dc, + BP_PUSHBUTTON, state, + &r, NULL); + if (hr != S_OK) { + logHRESULT(L"DrawThemeBackground()", hr); + goto fail; + } + // TODO DT_EDITCONTROL? + // TODO DT_PATH_ELLIPSIS DT_WORD_ELLIPSIS instead of DT_END_ELLIPSIS? a middle-ellipsis option would be ideal here + // TODO is there a theme property we can get instead of hardcoding these flags? if not, make these flags a macro + hr = DrawThemeText(theme, s->dc, + BP_PUSHBUTTON, state, + wstr, -1, + DT_CENTER | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX, 0, + &r); + if (hr != S_OK) { + logHRESULT(L"DrawThemeText()", hr); + goto fail; + } + } else { + UINT state; + HBRUSH color, prevColor; + int prevBkMode; + + // TODO check errors + // TODO explain why we're not doing this in the themed case (it has to do with extra transparent pixels) + InflateRect(&r, -1, -1); + state = DFCS_BUTTONPUSH; + if (!enabled) + state |= DFCS_INACTIVE; + if (DrawFrameControl(s->dc, &r, DFC_BUTTON, state) == 0) { + logLastError(L"DrawFrameControl()"); + hr = E_FAIL; + goto fail; + } + color = GetSysColorBrush(COLOR_BTNTEXT); + // TODO check errors for these two + prevColor = (HBRUSH) SelectObject(s->dc, color); + prevBkMode = SetBkMode(s->dc, TRANSPARENT); + // TODO DT_EDITCONTROL? + // TODO DT_PATH_ELLIPSIS DT_WORD_ELLIPSIS instead of DT_END_ELLIPSIS? a middle-ellipsis option would be ideal here + if (DrawTextW(s->dc, wstr, -1, &r, DT_CENTER | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX) == 0) { + logLastError(L"DrawTextW()"); + hr = E_FAIL; + goto fail; + } + // TODO check errors for these two + SetBkMode(s->dc, prevBkMode); + SelectObject(s->dc, prevColor); + } + + hr = S_OK; +fail: + // TODO check errors + if (theme != NULL) + CloseThemeData(theme); + uiprivFree(wstr); + return hr; +} + +static HRESULT freeDrawState(struct drawState *s) +{ + HRESULT hr, hrret; + + hrret = S_OK; + if (s->m != NULL) { + uiprivFree(s->m); + s->m = NULL; + } + if (s->freeTextBrush) { + if (DeleteObject(s->textBrush) == 0) { + logLastError(L"DeleteObject()"); + hrret = E_FAIL; + // continue cleaning up anyway + } + s->freeTextBrush = FALSE; + } + if (s->freeBgBrush) { + if (DeleteObject(s->bgBrush) == 0) { + logLastError(L"DeleteObject()"); + hrret = E_FAIL; + // continue cleaning up anyway + } + s->freeBgBrush = FALSE; + } + return hrret; +} + +static COLORREF blend(COLORREF base, double r, double g, double b, double a) +{ + double br, bg, bb; + + br = ((double) GetRValue(base)) / 255.0; + bg = ((double) GetGValue(base)) / 255.0; + bb = ((double) GetBValue(base)) / 255.0; + + br = (r * a) + (br * (1.0 - a)); + bg = (g * a) + (bg * (1.0 - a)); + bb = (b * a) + (bb * (1.0 - a)); + return RGB((BYTE) (br * 255), + (BYTE) (bg * 255), + (BYTE) (bb * 255)); +} + +static HRESULT fillDrawState(struct drawState *s, uiTable *t, NMLVCUSTOMDRAW *nm, uiprivTableColumnParams *p) +{ + LRESULT state; + HWND header; + HRESULT hr; + + ZeroMemory(s, sizeof (struct drawState)); + s->t = t; + s->model = t->model; + s->p = p; + + s->dc = nm->nmcd.hdc; + s->iItem = nm->nmcd.dwItemSpec; + s->iSubItem = nm->iSubItem; + + hr = uiprivTableGetMetrics(t, s->iItem, s->iSubItem, &(s->m)); + if (hr != S_OK) + goto fail; + + if (s->m->selected) { + s->bgColor = GetSysColor(COLOR_HIGHLIGHT); + s->bgBrush = GetSysColorBrush(COLOR_HIGHLIGHT); + s->textColor = GetSysColor(COLOR_HIGHLIGHTTEXT); + s->textBrush = GetSysColorBrush(COLOR_HIGHLIGHTTEXT); + } else { + double r, g, b, a; + + s->bgColor = GetSysColor(COLOR_WINDOW); + s->bgBrush = GetSysColorBrush(COLOR_WINDOW); + if (uiprivTableModelColorIfProvided(s->model, s->iItem, t->backgroundColumn, &r, &g, &b, &a)) { + s->bgColor = blend(s->bgColor, r, g, b, a); + s->bgBrush = CreateSolidBrush(s->bgColor); + if (s->bgBrush == NULL) { + logLastError(L"CreateSolidBrush()"); + hr = E_FAIL; + goto fail; + } + s->freeBgBrush = TRUE; + } + s->textColor = GetSysColor(COLOR_WINDOWTEXT); + s->textBrush = GetSysColorBrush(COLOR_WINDOWTEXT); + if (uiprivTableModelColorIfProvided(s->model, s->iItem, p->textParams.ColorModelColumn, &r, &g, &b, &a)) { + s->textColor = blend(s->bgColor, r, g, b, a); + s->textBrush = CreateSolidBrush(s->textColor); + if (s->textBrush == NULL) { + logLastError(L"CreateSolidBrush()"); + hr = E_FAIL; + goto fail; + } + s->freeTextBrush = TRUE; + } + } + + return S_OK; +fail: + // ignore the error; we need to return the one we got above + freeDrawState(s); + return hr; +} + +static HRESULT updateAndDrawFocusRects(HRESULT hr, uiTable *t, HDC dc, int iItem, RECT *realTextBackground, RECT *focus, bool *first) +{ + LRESULT state; + + if (hr != S_OK) + return hr; + if (GetFocus() != t->hwnd) + return S_OK; + // uItemState CDIS_FOCUS doesn't quite work right because of bugs in the Windows list view that causes spurious redraws without the flag while we hover over the focused item + // TODO only call this once + state = SendMessageW(t->hwnd, LVM_GETITEMSTATE, (WPARAM) iItem, (LRESULT) (LVIS_FOCUSED)); + if ((state & LVIS_FOCUSED) == 0) + return S_OK; + + if (realTextBackground != NULL) + if (*first) { + *focus = *realTextBackground; + *first = false; + return S_OK; + } else if (focus->right == realTextBackground->left) { + focus->right = realTextBackground->right; + return S_OK; + } + if (DrawFocusRect(dc, focus) == 0) { + logLastError(L"DrawFocusRect()"); + return E_FAIL; + } + if (realTextBackground != NULL) + *focus = *realTextBackground; + return S_OK; +} + +// normally we would only draw stuff in subitem stages +// this broke when we tried drawing focus rects in postpaint; the drawing either kept getting wiped or overdrawn, mouse hovering had something to do with it, and none of the "solutions" to this or similar problems on the internet worked +// so now we do everything in the item prepaint stage +// TODO consider switching to full-on owner draw +// TODO only compute the background brushes once? +HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *lResult) +{ + struct drawState s; + uiprivTableColumnParams *p; + NMLVCUSTOMDRAW b; + size_t i, n; + RECT focus; + bool focusFirst; + HRESULT hr; + + switch (nm->nmcd.dwDrawStage) { + case CDDS_PREPAINT: + *lResult = CDRF_NOTIFYITEMDRAW; + return S_OK; + case CDDS_ITEMPREPAINT: + break; + default: + *lResult = CDRF_DODEFAULT; + return S_OK; + } + + n = t->columns->size(); + b = *nm; + focusFirst = true; + for (i = 0; i < n; i++) { + b.iSubItem = i; + p = (*(t->columns))[i]; + hr = fillDrawState(&s, t, &b, p); + if (hr != S_OK) + return hr; + hr = drawBackgrounds(S_OK, &s); + hr = drawImagePart(hr, &s); + hr = drawCheckboxPart(hr, &s); + hr = drawTextPart(hr, &s); + hr = drawProgressBarPart(hr, &s); + hr = drawButtonPart(hr, &s); + hr = updateAndDrawFocusRects(hr, s.t, s.dc, nm->nmcd.dwItemSpec, &(s.m->realTextBackground), &focus, &focusFirst); + if (hr != S_OK) + goto fail; + hr = freeDrawState(&s); + if (hr != S_OK) // TODO really error out here? + return hr; + } + // and draw the last focus rect + hr = updateAndDrawFocusRects(hr, t, nm->nmcd.hdc, nm->nmcd.dwItemSpec, NULL, &focus, &focusFirst); + if (hr != S_OK) + return hr; + *lResult = CDRF_SKIPDEFAULT; + return S_OK; +fail: + // ignore error here + // TODO this is awkward cleanup placement for something that only really exists in a for loop + freeDrawState(&s); + return hr; +} + +// TODO run again when the DPI or the theme changes +// TODO properly clean things up here +// TODO properly destroy the old lists here too +HRESULT uiprivUpdateImageListSize(uiTable *t) +{ + HDC dc; + int cxList, cyList; + HTHEME theme; + SIZE sizeCheck; + HRESULT hr; + + dc = GetDC(t->hwnd); + if (dc == NULL) { + logLastError(L"GetDC()"); + return E_FAIL; + } + + cxList = GetSystemMetrics(SM_CXSMICON); + cyList = GetSystemMetrics(SM_CYSMICON); + sizeCheck.cx = cxList; + sizeCheck.cy = cyList; + theme = OpenThemeData(t->hwnd, L"button"); + if (theme != NULL) { + hr = GetThemePartSize(theme, dc, + BP_CHECKBOX, CBS_UNCHECKEDNORMAL, + NULL, TS_DRAW, &sizeCheck); + if (hr != S_OK) { + logHRESULT(L"GetThemePartSize()", hr); + return hr; // TODO fall back? + } + // make sure these checkmarks fit + // unthemed checkmarks will by the code above be smaller than cxList/cyList here + if (cxList < sizeCheck.cx) + cxList = sizeCheck.cx; + if (cyList < sizeCheck.cy) + cyList = sizeCheck.cy; + hr = CloseThemeData(theme); + if (hr != S_OK) { + logHRESULT(L"CloseThemeData()", hr); + return hr; + } + } + + // TODO handle errors + t->imagelist = ImageList_Create(cxList, cyList, + ILC_COLOR32, + 1, 1); + if (t->imagelist == NULL) { + logLastError(L"ImageList_Create()"); + return E_FAIL; + } + // TODO will this return NULL here because it's an initial state? + SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM) (t->imagelist)); + + if (ReleaseDC(t->hwnd, dc) == 0) { + logLastError(L"ReleaseDC()"); + return E_FAIL; + } + return S_OK; +} diff --git a/windows/tableediting.cpp b/windows/tableediting.cpp new file mode 100644 index 00000000..7bbb450c --- /dev/null +++ b/windows/tableediting.cpp @@ -0,0 +1,262 @@ +// 17 june 2018 +#include "uipriv_windows.hpp" +#include "table.hpp" + +// TODOs +// - clicking on the same item restarts editing instead of cancels it + +// this is not how the real list view positions and sizes the edit control, but this is a) close enough b) a lot easier to follow c) something I can actually get working d) something I'm slightly more comfortable including in libui +static HRESULT resizeEdit(uiTable *t, WCHAR *wstr, int iItem, int iSubItem) +{ + uiprivTableMetrics *m; + RECT r; + HDC dc; + HFONT prevFont; + TEXTMETRICW tm; + SIZE textSize; + RECT editRect, clientRect; + HRESULT hr; + + hr = uiprivTableGetMetrics(t, iItem, iSubItem, &m); + if (hr != S_OK) + return hr; + r = m->realTextRect; + uiprivFree(m); + + // TODO check errors for all these + dc = GetDC(t->hwnd); // use the list view DC since we're using its coordinate space + prevFont = (HFONT) SelectObject(dc, hMessageFont); + GetTextMetricsW(dc, &tm); + GetTextExtentPoint32W(dc, wstr, wcslen(wstr), &textSize); + SelectObject(dc, prevFont); + ReleaseDC(t->hwnd, dc); + + SendMessageW(t->edit, EM_GETRECT, 0, (LPARAM) (&editRect)); + r.left -= editRect.left; + // find the top of the text + r.top += ((r.bottom - r.top) - tm.tmHeight) / 2; + // and move THAT by the right offset + r.top -= editRect.top; + r.right = r.left + textSize.cx; + // the real listview does this to add some extra space at the end + // TODO this still isn't enough space + r.right += 4 * GetSystemMetrics(SM_CXEDGE) + GetSystemMetrics(SM_CYEDGE); + // and make the bottom equally positioned to the top + r.bottom = r.top + editRect.top + tm.tmHeight + editRect.top; + + // make sure the edit box doesn't stretch outside the listview + // the list view just does this, which is dumb for when the list view wouldn't be visible at all, but given that it doesn't scroll the edit into view either... + // TODO check errors + GetClientRect(t->hwnd, &clientRect); + IntersectRect(&r, &r, &clientRect); + + // TODO check error or use the right function + SetWindowPos(t->edit, NULL, + r.left, r.top, + r.right - r.left, r.bottom - r.top, + SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); + return S_OK; +} + +// the real list view intercepts these keys to control editing +static LRESULT CALLBACK editSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIDSubclass, DWORD_PTR dwRefData) +{ + uiTable *t = (uiTable *) dwRefData; + HRESULT hr; + + switch (uMsg) { + case WM_KEYDOWN: + switch (wParam) { + // TODO handle VK_TAB and VK_SHIFT+VK_TAB + case VK_RETURN: + hr = uiprivTableFinishEditingText(t); + if (hr != S_OK) { + // TODO + } + return 0; // yes, the real list view just returns here + case VK_ESCAPE: + hr = uiprivTableAbortEditingText(t); + if (hr != S_OK) { + // TODO + } + return 0; + } + break; + // the real list view also forces these flags + case WM_GETDLGCODE: + return DLGC_HASSETSEL | DLGC_WANTALLKEYS; + case WM_NCDESTROY: + if (RemoveWindowSubclass(hwnd, editSubProc, uIDSubclass) == FALSE) + logLastError(L"RemoveWindowSubclass()"); + // fall through + } + return DefSubclassProc(hwnd, uMsg, wParam, lParam); +} + +static HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableColumnParams *p) +{ + uiTableValue *value; + WCHAR *wstr; + HRESULT hr; + + // the real list view accepts changes to the existing item when editing a new item + hr = uiprivTableFinishEditingText(t); + if (hr != S_OK) + return hr; + + // the real list view creates the edit control with the string + value = uiprivTableModelCellValue(t->model, iItem, p->textModelColumn); + wstr = toUTF16(uiTableValueString(value)); + uiFreeTableValue(value); + // TODO copy WS_EX_RTLREADING + t->edit = CreateWindowExW(0, + L"EDIT", wstr, + // these styles are what the normal listview edit uses + WS_CHILD | WS_CLIPSIBLINGS | WS_BORDER | ES_AUTOHSCROLL, + // as is this size + 0, 0, 16384, 16384, + // and this control ID + t->hwnd, (HMENU) 1, hInstance, NULL); + if (t->edit == NULL) { + logLastError(L"CreateWindowExW()"); + uiprivFree(wstr); + return E_FAIL; + } + SendMessageW(t->edit, WM_SETFONT, (WPARAM) hMessageFont, (LPARAM) TRUE); + // TODO check errors + SetWindowSubclass(t->edit, editSubProc, 0, (DWORD_PTR) t); + + hr = resizeEdit(t, wstr, iItem, iSubItem); + if (hr != S_OK) + // TODO proper cleanup + return hr; + // TODO check errors on these two, if any + SetFocus(t->edit); + ShowWindow(t->edit, SW_SHOW); + SendMessageW(t->edit, EM_SETSEL, 0, (LPARAM) (-1)); + + uiprivFree(wstr); + t->editedItem = iItem; + t->editedSubitem = iSubItem; + return S_OK; +} + +HRESULT uiprivTableResizeWhileEditing(uiTable *t) +{ + WCHAR *text; + HRESULT hr; + + if (t->edit == NULL) + return S_OK; + text = windowText(t->edit); + hr = resizeEdit(t, text, t->editedItem, t->editedSubitem); + uiprivFree(text); + return hr; +} + +HRESULT uiprivTableFinishEditingText(uiTable *t) +{ + uiprivTableColumnParams *p; + uiTableValue *value; + char *text; + + if (t->edit == NULL) + return S_OK; + text = uiWindowsWindowText(t->edit); + value = uiNewTableValueString(text); + uiFreeText(text); + p = (*(t->columns))[t->editedSubitem]; + uiprivTableModelSetCellValue(t->model, t->editedItem, p->textModelColumn, value); + uiFreeTableValue(value); + // always refresh the value in case the model rejected it + if (SendMessageW(t->hwnd, LVM_UPDATE, (WPARAM) (t->editedItem), 0) == (LRESULT) (-1)) { + logLastError(L"LVM_UPDATE"); + return E_FAIL; + } + return uiprivTableAbortEditingText(t); +} + +HRESULT uiprivTableAbortEditingText(uiTable *t) +{ + HWND edit; + + if (t->edit == NULL) + return S_OK; + // set t->edit to NULL now so we don't trigger commits on focus killed + edit = t->edit; + t->edit = NULL; + + if (DestroyWindow(edit) == 0) { + logLastError(L"DestroyWindow()"); + return E_FAIL; + } + return S_OK; +} + +HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResult) +{ + LVHITTESTINFO ht; + uiprivTableColumnParams *p; + int modelColumn, editableColumn; + bool text, checkbox; + uiTableValue *value; + int checked, editable; + HRESULT hr; + + ZeroMemory(&ht, sizeof (LVHITTESTINFO)); + ht.pt = nm->ptAction; + if (SendMessageW(t->hwnd, LVM_SUBITEMHITTEST, 0, (LPARAM) (&ht)) == (LRESULT) (-1)) + goto done; + + modelColumn = -1; + editableColumn = -1; + text = false; + checkbox = false; + p = (*(t->columns))[ht.iSubItem]; + if (p->textModelColumn != -1) { + modelColumn = p->textModelColumn; + editableColumn = p->textEditableModelColumn; + text = true; + } else if (p->checkboxModelColumn != -1) { + modelColumn = p->checkboxModelColumn; + editableColumn = p->checkboxEditableModelColumn; + checkbox = true; + } else if (p->buttonModelColumn != -1) { + modelColumn = p->buttonModelColumn; + editableColumn = p->buttonClickableModelColumn; + } + if (modelColumn == -1) + goto done; + + if (text && t->inDoubleClickTimer) + // don't even ask for info if it's too soon to edit text + goto done; + + if (!uiprivTableModelCellEditable(t->model, ht.iItem, editableColumn)) + goto done; + + if (text) { + hr = openEditControl(t, ht.iItem, ht.iSubItem, p); + if (hr != S_OK) + return hr; + } else if (checkbox) { + if ((ht.flags & LVHT_ONITEMICON) == 0) + goto done; + value = uiprivTableModelCellValue(t->model, ht.iItem, modelColumn); + checked = uiTableValueInt(value); + uiFreeTableValue(value); + value = uiNewTableValueInt(!checked); + uiprivTableModelSetCellValue(t->model, ht.iItem, modelColumn, value); + uiFreeTableValue(value); + } else + uiprivTableModelSetCellValue(t->model, ht.iItem, modelColumn, NULL); + // always refresh the value in case the model rejected it + if (SendMessageW(t->hwnd, LVM_UPDATE, (WPARAM) (ht.iItem), 0) == (LRESULT) (-1)) { + logLastError(L"LVM_UPDATE"); + return E_FAIL; + } + +done: + *lResult = 0; + return S_OK; +} diff --git a/windows/tablemetrics.cpp b/windows/tablemetrics.cpp new file mode 100644 index 00000000..64236b9f --- /dev/null +++ b/windows/tablemetrics.cpp @@ -0,0 +1,105 @@ +// 14 june 2018 +#include "uipriv_windows.hpp" +#include "table.hpp" + +static HRESULT itemRect(HRESULT hr, uiTable *t, UINT uMsg, WPARAM wParam, LONG left, LONG top, LRESULT bad, RECT *r) +{ + if (hr != S_OK) + return hr; + ZeroMemory(r, sizeof (RECT)); + r->left = left; + r->top = top; + if (SendMessageW(t->hwnd, uMsg, wParam, (LPARAM) r) == bad) { + logLastError(L"itemRect() message"); + return E_FAIL; + } + return S_OK; +} + +HRESULT uiprivTableGetMetrics(uiTable *t, int iItem, int iSubItem, uiprivTableMetrics **mout) +{ + uiprivTableMetrics *m; + uiprivTableColumnParams *p; + LRESULT state; + HWND header; + RECT r; + HRESULT hr; + + if (mout == NULL) + return E_POINTER; + + m = uiprivNew(uiprivTableMetrics); + + p = (*(t->columns))[iSubItem]; + m->hasText = p->textModelColumn != -1; + m->hasImage = (p->imageModelColumn != -1) || (p->checkboxModelColumn != -1); + state = SendMessageW(t->hwnd, LVM_GETITEMSTATE, iItem, LVIS_FOCUSED | LVIS_SELECTED); + m->focused = (state & LVIS_FOCUSED) != 0; + m->selected = (state & LVIS_SELECTED) != 0; + + // TODO check LRESULT bad parameters here + hr = itemRect(S_OK, t, LVM_GETITEMRECT, iItem, + LVIR_BOUNDS, 0, FALSE, &(m->itemBounds)); + hr = itemRect(hr, t, LVM_GETITEMRECT, iItem, + LVIR_ICON, 0, FALSE, &(m->itemIcon)); + hr = itemRect(hr, t, LVM_GETITEMRECT, iItem, + LVIR_LABEL, 0, FALSE, &(m->itemLabel)); + hr = itemRect(hr, t, LVM_GETSUBITEMRECT, iItem, + LVIR_BOUNDS, iSubItem, 0, &(m->subitemBounds)); + hr = itemRect(hr, t, LVM_GETSUBITEMRECT, iItem, + LVIR_ICON, iSubItem, 0, &(m->subitemIcon)); + if (hr != S_OK) + goto fail; + // LVM_GETSUBITEMRECT treats LVIR_LABEL as the same as + // LVIR_BOUNDS, so we can't use that directly. Instead, let's + // assume the text is immediately after the icon. The correct + // rect will be determined by + // computeOtherRectsAndDrawBackgrounds() above. + m->subitemLabel = m->subitemBounds; + m->subitemLabel.left = m->subitemIcon.right; + // And on iSubItem == 0, LVIF_GETSUBITEMRECT still includes + // all the subitems, which we don't want. + if (iSubItem == 0) { + m->subitemBounds.right = m->itemLabel.right; + m->subitemLabel.right = m->itemLabel.right; + } + + header = (HWND) SendMessageW(t->hwnd, LVM_GETHEADER, 0, 0); + m->bitmapMargin = SendMessageW(header, HDM_GETBITMAPMARGIN, 0, 0); + if (ImageList_GetIconSize(t->imagelist, &(m->cxIcon), &(m->cyIcon)) == 0) { + logLastError(L"ImageList_GetIconSize()"); + hr = E_FAIL; + goto fail; + } + + r = m->subitemLabel; + if (!m->hasText && !m->hasImage) + r = m->subitemBounds; + else if (!m->hasImage && iSubItem != 0) + // By default, this will include images; we're not drawing + // images, so we will manually draw over the image area. + // There's a second part to this; see below. + r.left = m->subitemBounds.left; + m->realTextBackground = r; + + m->realTextRect = r; + // TODO confirm whether this really happens on column 0 as well + if (m->hasImage && iSubItem != 0) + // Normally there's this many hard-coded logical units + // of blank space, followed by the background, followed + // by a bitmap margin's worth of space. This looks bad, + // so we overrule that to start the background immediately + // and the text after the hard-coded amount. + m->realTextRect.left += 2; + else if (iSubItem != 0) + // In the case of subitem text without an image, we draw + // text one bitmap margin away from the left edge. + m->realTextRect.left += m->bitmapMargin; + + *mout = m; + return S_OK; +fail: + uiprivFree(m); + *mout = NULL; + return hr; +} diff --git a/windows/tabpage.cpp b/windows/tabpage.cpp index 7f95071d..c2584019 100644 --- a/windows/tabpage.cpp +++ b/windows/tabpage.cpp @@ -60,7 +60,7 @@ static INT_PTR CALLBACK dlgproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lPar if (uMsg == WM_WINDOWPOSCHANGED) { tp = (struct tabPage *) GetWindowLongPtrW(hwnd, DWLP_USER); tabPageRelayout(tp); - // pretend the dialog hasn't handled this just in case it needs to do something special + // pretend the dialog hasn't handled this just in case the system needs to do something special return FALSE; } @@ -78,15 +78,33 @@ static INT_PTR CALLBACK dlgproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lPar return FALSE; } +// because Windows doesn't really support resources in static libraries, we have to embed this directly; oh well +/* +// this is the dialog template used by tab pages; see windows/tabpage.c for details +rcTabPageDialog DIALOGEX 0, 0, 100, 100 +STYLE DS_CONTROL | WS_CHILD | WS_VISIBLE +EXSTYLE WS_EX_CONTROLPARENT +BEGIN + // nothing +END +*/ +static const uint8_t data_rcTabPageDialog[] = { + 0x01, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x04, 0x00, 0x50, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, + 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; +static_assert(ARRAYSIZE(data_rcTabPageDialog) == 32, "wrong size for resource rcTabPageDialog"); + struct tabPage *newTabPage(uiControl *child) { struct tabPage *tp; HRESULT hr; - tp = uiNew(struct tabPage); + tp = uiprivNew(struct tabPage); // unfortunately this needs to be a proper dialog for EnableThemeDialogTexture() to work; CreateWindowExW() won't suffice - if (CreateDialogParamW(hInstance, MAKEINTRESOURCE(rcTabPageDialog), + if (CreateDialogIndirectParamW(hInstance, (const DLGTEMPLATE *) data_rcTabPageDialog, utilWindow, dlgproc, (LPARAM) tp) == NULL) logLastError(L"error creating tab page"); @@ -114,10 +132,10 @@ void tabPageDestroy(struct tabPage *tp) uiWindowsControlSetParentHWND(uiWindowsControl(tp->child), NULL); // don't call EndDialog(); that's for the DialogBox() family of functions instead of CreateDialog() uiWindowsEnsureDestroyWindow(tp->hwnd); - uiFree(tp); + uiprivFree(tp); } -void tabPageMinimumSize(struct tabPage *tp, intmax_t *width, intmax_t *height) +void tabPageMinimumSize(struct tabPage *tp, int *width, int *height) { int mx, my; diff --git a/windows/text.cpp b/windows/text.cpp index 4beeccd7..eafbe714 100644 --- a/windows/text.cpp +++ b/windows/text.cpp @@ -10,7 +10,7 @@ WCHAR *windowTextAndLen(HWND hwnd, LRESULT *len) if (len != NULL) *len = n; // WM_GETTEXTLENGTH does not include the null terminator - text = (WCHAR *) uiAlloc((n + 1) * sizeof (WCHAR), "WCHAR[]"); + text = (WCHAR *) uiprivAlloc((n + 1) * sizeof (WCHAR), "WCHAR[]"); // note the comparison: the size includes the null terminator, but the return does not if (GetWindowTextW(hwnd, text, n + 1) != n) { logLastError(L"error getting window text"); @@ -35,10 +35,10 @@ void setWindowText(HWND hwnd, WCHAR *wtext) void uiFreeText(char *text) { - uiFree(text); + uiprivFree(text); } -intmax_t uiWindowsWindowTextWidth(HWND hwnd) +int uiWindowsWindowTextWidth(HWND hwnd) { LRESULT len; WCHAR *text; @@ -78,11 +78,11 @@ intmax_t uiWindowsWindowTextWidth(HWND hwnd) if (ReleaseDC(hwnd, dc) == 0) logLastError(L"error releasing DC"); - uiFree(text); + uiprivFree(text); return size.cx; noTextOrError: - uiFree(text); + uiprivFree(text); return 0; } @@ -93,7 +93,7 @@ char *uiWindowsWindowText(HWND hwnd) wtext = windowText(hwnd); text = toUTF8(wtext); - uiFree(wtext); + uiprivFree(wtext); return text; } @@ -103,5 +103,18 @@ void uiWindowsSetWindowText(HWND hwnd, const char *text) wtext = toUTF16(text); setWindowText(hwnd, wtext); - uiFree(wtext); + uiprivFree(wtext); +} + +int uiprivStricmp(const char *a, const char *b) +{ + WCHAR *wa, *wb; + int ret; + + wa = toUTF16(a); + wb = toUTF16(b); + ret = _wcsicmp(wa, wb); + uiprivFree(wb); + uiprivFree(wa); + return ret; } diff --git a/windows/uipriv_windows.hpp b/windows/uipriv_windows.hpp index 984ed0af..52d74c51 100644 --- a/windows/uipriv_windows.hpp +++ b/windows/uipriv_windows.hpp @@ -7,14 +7,15 @@ #include "compilerver.hpp" // ui internal window messages +// TODO make these either not messages or WM_USER-based, so we can be sane about reserving WM_APP enum { // redirected WM_COMMAND and WM_NOTIFY msgCOMMAND = WM_APP + 0x40, // start offset just to be safe msgNOTIFY, msgHSCROLL, msgQueued, - // TODO convert to a function like with container? msgD2DScratchPaint, + msgD2DScratchLButtonDown, }; // alloc.cpp @@ -28,17 +29,17 @@ extern BOOL runWM_HSCROLL(WPARAM wParam, LPARAM lParam, LRESULT *lResult); extern void issueWM_WININICHANGE(WPARAM wParam, LPARAM lParam); // utf16.cpp -#define emptyUTF16() ((WCHAR *) uiAlloc(1 * sizeof (WCHAR), "WCHAR[]")) -#define emptyUTF8() ((char *) uiAlloc(1 * sizeof (char), "char[]")) +#define emptyUTF16() ((WCHAR *) uiprivAlloc(1 * sizeof (WCHAR), "WCHAR[]")) +#define emptyUTF8() ((char *) uiprivAlloc(1 * sizeof (char), "char[]")) extern WCHAR *toUTF16(const char *str); extern char *toUTF8(const WCHAR *wstr); extern WCHAR *utf16dup(const WCHAR *orig); extern WCHAR *strf(const WCHAR *format, ...); extern WCHAR *vstrf(const WCHAR *format, va_list ap); -extern WCHAR *debugstrf(const WCHAR *format, ...); -extern WCHAR *debugvstrf(const WCHAR *format, va_list ap); extern char *LFtoCRLF(const char *lfonly); -extern void CRLFtoLF(const char *s); +extern void CRLFtoLF(char *s); +extern WCHAR *ftoutf16(double d); +extern WCHAR *itoutf16(int i); // debug.cpp // see http://stackoverflow.com/questions/14421656/is-there-widely-available-wide-character-variant-of-file @@ -50,11 +51,17 @@ extern void CRLFtoLF(const char *s); #define _wsn(m) _ws2n(m) #define debugargs const WCHAR *file, const WCHAR *line, const WCHAR *func extern HRESULT _logLastError(debugargs, const WCHAR *s); +#ifdef _MSC_VER #define logLastError(s) _logLastError(_ws(__FILE__), _wsn(__LINE__), _ws(__FUNCTION__), s) +#else +#define logLastError(s) _logLastError(_ws(__FILE__), _wsn(__LINE__), L"TODO none of the function name macros are macros in MinGW", s) +#endif extern HRESULT _logHRESULT(debugargs, const WCHAR *s, HRESULT hr); +#ifdef _MSC_VER #define logHRESULT(s, hr) _logHRESULT(_ws(__FILE__), _wsn(__LINE__), _ws(__FUNCTION__), s, hr) -extern void _implbug(debugargs, const WCHAR *format, ...); -#define implbug(...) _implbug(_ws(__FILE__), _wsn(__LINE__), _ws(__FUNCTION__), __VA_ARGS__) +#else +#define logHRESULT(s, hr) _logHRESULT(_ws(__FILE__), _wsn(__LINE__), L"TODO none of the function name macros are macros in MinGW", s, hr) +#endif // winutil.cpp extern int windowClassOf(HWND hwnd, ...); @@ -63,10 +70,12 @@ extern DWORD getStyle(HWND hwnd); extern void setStyle(HWND hwnd, DWORD style); extern DWORD getExStyle(HWND hwnd); extern void setExStyle(HWND hwnd, DWORD exstyle); -extern void clientSizeToWindowSize(HWND hwnd, intmax_t *width, intmax_t *height, BOOL hasMenubar); +extern void clientSizeToWindowSize(HWND hwnd, int *width, int *height, BOOL hasMenubar); extern HWND parentOf(HWND child); extern HWND parentToplevel(HWND child); extern void setWindowInsertAfter(HWND hwnd, HWND insertAfter); +extern HWND getDlgItem(HWND hwnd, int id); +extern void invalidateRect(HWND hwnd, RECT *r, BOOL erase); // text.cpp extern WCHAR *windowTextAndLen(HWND hwnd, LRESULT *len); @@ -86,8 +95,16 @@ extern const char *initUtilWindow(HICON hDefaultIcon, HCURSOR hDefaultCursor); extern void uninitUtilWindow(void); // main.cpp +// TODO how the hell did MSVC accept this without the second uiprivTimer??????? +typedef struct uiprivTimer uiprivTimer; +struct uiprivTimer { + int (*f)(void *); + void *data; +}; extern int registerMessageFilter(void); extern void unregisterMessageFilter(void); +extern void uiprivFreeTimer(uiprivTimer *t); +extern void uiprivUninitTimers(void); // parent.cpp extern void paintContainerBackground(HWND hwnd, HDC dc, RECT *paintRect); @@ -110,6 +127,8 @@ extern BOOL areaFilter(MSG *); extern ATOM registerWindowClass(HICON, HCURSOR); extern void unregisterWindowClass(void); extern void ensureMinimumWindowSize(uiWindow *); +extern void disableAllWindowsExcept(uiWindow *which); +extern void enableAllWindowsExcept(uiWindow *which); // container.cpp #define containerClass L"libui_uiContainerClass" @@ -124,10 +143,32 @@ struct tabPage { }; extern struct tabPage *newTabPage(uiControl *child); extern void tabPageDestroy(struct tabPage *tp); -extern void tabPageMinimumSize(struct tabPage *tp, intmax_t *width, intmax_t *height); +extern void tabPageMinimumSize(struct tabPage *tp, int *width, int *height); +// colordialog.cpp +struct colorDialogRGBA { + double r; + double g; + double b; + double a; +}; +extern BOOL showColorDialog(HWND parent, struct colorDialogRGBA *c); +// sizing.cpp +extern void getSizing(HWND hwnd, uiWindowsSizing *sizing, HFONT font); +// TODO move into a dedicated file abibugs.cpp when we rewrite the drawing code +extern D2D1_SIZE_F realGetSize(ID2D1RenderTarget *rt); // TODO #include "_uipriv_migrate.hpp" + +// draw.cpp +extern ID2D1DCRenderTarget *makeHDCRenderTarget(HDC dc, RECT *r); + +// image.cpp +extern IWICImagingFactory *uiprivWICFactory; +extern HRESULT uiprivInitImage(void); +extern void uiprivUninitImage(void); +extern IWICBitmap *uiprivImageAppropriateForDC(uiImage *i, HDC dc); +extern HRESULT uiprivWICToGDI(IWICBitmap *b, HDC dc, int width, int height, HBITMAP *hb); diff --git a/windows/utf16.cpp b/windows/utf16.cpp index 00abc6fd..131759e9 100644 --- a/windows/utf16.cpp +++ b/windows/utf16.cpp @@ -3,48 +3,42 @@ // see http://stackoverflow.com/a/29556509/3408572 -#define MBTWC(str, wstr, bufsiz) MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, bufsiz) - WCHAR *toUTF16(const char *str) { WCHAR *wstr; - int n; + WCHAR *wp; + size_t n; + uint32_t rune; if (*str == '\0') // empty string return emptyUTF16(); - n = MBTWC(str, NULL, 0); - if (n == 0) { - logLastError(L"error figuring out number of characters to convert to"); - return emptyUTF16(); - } - wstr = (WCHAR *) uiAlloc(n * sizeof (WCHAR), "WCHAR[]"); - if (MBTWC(str, wstr, n) != n) { - logLastError(L"error converting from UTF-8 to UTF-16"); - // and return an empty string - *wstr = L'\0'; + n = uiprivUTF8UTF16Count(str, 0); + wstr = (WCHAR *) uiprivAlloc((n + 1) * sizeof (WCHAR), "WCHAR[]"); + wp = wstr; + while (*str) { + str = uiprivUTF8DecodeRune(str, 0, &rune); + n = uiprivUTF16EncodeRune(rune, wp); + wp += n; } return wstr; } -#define WCTMB(wstr, str, bufsiz) WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, bufsiz, NULL, NULL) - char *toUTF8(const WCHAR *wstr) { char *str; - int n; + char *sp; + size_t n; + uint32_t rune; if (*wstr == L'\0') // empty string return emptyUTF8(); - n = WCTMB(wstr, NULL, 0); - if (n == 0) { - logLastError(L"error figuring out number of characters to convert to"); - return emptyUTF8(); - } - str = (char *) uiAlloc(n * sizeof (char), "char[]"); - if (WCTMB(wstr, str, n) != n) { - logLastError(L"error converting from UTF-16 to UTF-8"); - // and return an empty string - *str = '\0'; + n = uiprivUTF16UTF8Count(wstr, 0); + str = (char *) uiprivAlloc((n + 1) * sizeof (char), "char[]"); + sp = str; + while (*wstr) { + wstr = uiprivUTF16DecodeRune(wstr, 0, &rune); + n = uiprivUTF8EncodeRune(rune, sp); + sp += n; } return str; } @@ -55,34 +49,11 @@ WCHAR *utf16dup(const WCHAR *orig) size_t len; len = wcslen(orig); - out = (WCHAR *) uiAlloc((len + 1) * sizeof (WCHAR), "WCHAR[]"); + out = (WCHAR *) uiprivAlloc((len + 1) * sizeof (WCHAR), "WCHAR[]"); wcscpy_s(out, len + 1, orig); return out; } -// if recursing is TRUE, do NOT recursively call wstrf() in logHRESULT() -static WCHAR *strfcore(BOOL recursing, const WCHAR *format, va_list ap) -{ - va_list ap2; - WCHAR *buf; - size_t n; - HRESULT hr; - - if (*format == L'\0') - return emptyUTF16(); - - va_copy(ap2, ap); - n = _vscwprintf(format, ap2); - va_end(ap2); - n++; // terminating L'\0' - - buf = (WCHAR *) uiAlloc(n * sizeof (WCHAR), "WCHAR[]"); - // includes terminating L'\0' according to example in https://msdn.microsoft.com/en-us/library/xa1a1a6z.aspx - vswprintf_s(buf, n, format, ap); - - return buf; -} - WCHAR *strf(const WCHAR *format, ...) { va_list ap; @@ -96,24 +67,26 @@ WCHAR *strf(const WCHAR *format, ...) WCHAR *vstrf(const WCHAR *format, va_list ap) { - return strfcore(FALSE, format, ap); + va_list ap2; + WCHAR *buf; + size_t n; + + if (*format == L'\0') + return emptyUTF16(); + + va_copy(ap2, ap); + n = _vscwprintf(format, ap2); + va_end(ap2); + n++; // terminating L'\0' + + buf = (WCHAR *) uiprivAlloc(n * sizeof (WCHAR), "WCHAR[]"); + // includes terminating L'\0' according to example in https://msdn.microsoft.com/en-us/library/xa1a1a6z.aspx + vswprintf_s(buf, n, format, ap); + + return buf; } -WCHAR *debugstrf(const WCHAR *format, ...) -{ - va_list ap; - WCHAR *str; - - va_start(ap, format); - str = debugvstrf(format, ap); - va_end(ap); - return str; -} - -WCHAR *debugvstrf(const WCHAR *format, va_list ap) -{ - return strfcore(TRUE, format, ap); -} +// TODO merge the following two with the toUTF*()s? // Let's shove these utility routines here too. // Prerequisite: lfonly is UTF-8. @@ -124,7 +97,7 @@ char *LFtoCRLF(const char *lfonly) char *out; len = strlen(lfonly); - crlf = (char *) uiAlloc((len * 2 + 1) * sizeof (char), "char[]"); + crlf = (char *) uiprivAlloc((len * 2 + 1) * sizeof (char), "char[]"); out = crlf; for (i = 0; i < len; i++) { if (*lfonly == '\n') @@ -151,3 +124,26 @@ void CRLFtoLF(char *s) while (t != s) *t++ = '\0'; } + +// std::to_string() always uses %f; we want %g +// fortunately std::iostream seems to use %g by default so +WCHAR *ftoutf16(double d) +{ + std::wostringstream ss; + std::wstring s; + + ss << d; + s = ss.str(); // to be safe + return utf16dup(s.c_str()); +} + +// to complement the above +WCHAR *itoutf16(int i) +{ + std::wostringstream ss; + std::wstring s; + + ss << i; + s = ss.str(); // to be safe + return utf16dup(s.c_str()); +} diff --git a/windows/utilwin.cpp b/windows/utilwin.cpp index 992eea68..08183753 100644 --- a/windows/utilwin.cpp +++ b/windows/utilwin.cpp @@ -5,9 +5,10 @@ // It is not a message-only window, and it is always hidden and disabled. // Its roles: // - It is the initial parent of all controls. When a control loses its parent, it also becomes that control's parent. -// - It handles WM_QUERYENDSESSION and console end session requests. +// - It handles WM_QUERYENDSESSION requests. // - It handles WM_WININICHANGE and forwards the message to any child windows that request it. // - It handles executing functions queued to run by uiQueueMain(). +// TODO explain why it isn't message-only #define utilWindowClass L"libui_utilWindowClass" @@ -17,13 +18,14 @@ static LRESULT CALLBACK utilWindowWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, L { void (*qf)(void *); LRESULT lResult; + uiprivTimer *timer; if (handleParentMessages(hwnd, uMsg, wParam, lParam, &lResult) != FALSE) return lResult; switch (uMsg) { case WM_QUERYENDSESSION: - // TODO block handler - if (shouldQuit()) { + // TODO block handler (TODO figure out if this meant the Vista-style block handler or not) + if (uiprivShouldQuit()) { uiQuit(); return TRUE; } @@ -35,6 +37,14 @@ static LRESULT CALLBACK utilWindowWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, L qf = (void (*)(void *)) wParam; (*qf)((void *) lParam); return 0; + case WM_TIMER: + timer = (uiprivTimer *) wParam; + if (!(*(timer->f))(timer->data)) { + if (KillTimer(utilWindow, (UINT_PTR) timer) == 0) + logLastError(L"error calling KillTimer() to end uiTimer() procedure"); + uiprivFreeTimer(timer); + } + return 0; } return DefWindowProcW(hwnd, uMsg, wParam, lParam); } @@ -51,7 +61,8 @@ const char *initUtilWindow(HICON hDefaultIcon, HCURSOR hDefaultCursor) wc.hCursor = hDefaultCursor; wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1); if (RegisterClass(&wc) == 0) - return "registering utility window class"; + // see init.cpp for an explanation of the =s + return "=registering utility window class"; utilWindow = CreateWindowExW(0, utilWindowClass, L"libui utility window", @@ -59,7 +70,7 @@ const char *initUtilWindow(HICON hDefaultIcon, HCURSOR hDefaultCursor) 0, 0, 100, 100, NULL, NULL, hInstance, NULL); if (utilWindow == NULL) - return "creating utility window"; + return "=creating utility window"; // and just to be safe EnableWindow(utilWindow, FALSE); diff --git a/windows/winapi.hpp b/windows/winapi.hpp index ebd92271..92c08230 100644 --- a/windows/winapi.hpp +++ b/windows/winapi.hpp @@ -8,11 +8,10 @@ // TODO get rid of this #define INITGUID -// TODO see if we can remove this -#define _USE_MATH_DEFINES - // for the manifest +#ifndef _UI_STATIC #define ISOLATION_AWARE_ENABLED 1 +#endif // get Windows version right; right now Windows Vista // unless otherwise stated, all values from Microsoft's sdkddkver.h @@ -24,17 +23,28 @@ #define _WIN32_IE 0x0700 #define NTDDI_VERSION 0x06000000 +// The MinGW-w64 header has an unverified IDWriteTypography definition. +// TODO I can confirm this myself, but I don't know how long it will take for them to note my adjustments... Either way, I have to confirm this myself. +// TODO change the check from _MSC_VER to a MinGW-w64-specific check +// TODO keep track of what else is guarded by this +#ifndef _MSC_VER +#define __MINGW_USE_BROKEN_INTERFACE +#endif + #include // Microsoft's resource compiler will segfault if we feed it headers it was not designed to handle #ifndef RC_INVOKED #include #include +#include +#include #include #include #include #include #include +#include #include #include @@ -47,4 +57,8 @@ #include #include +#include +#include +#include +#include #endif diff --git a/windows/window.cpp b/windows/window.cpp index ca0e5c07..2ea5b7ce 100644 --- a/windows/window.cpp +++ b/windows/window.cpp @@ -14,6 +14,12 @@ struct uiWindow { void *onClosingData; int margined; BOOL hasMenubar; + void (*onContentSizeChanged)(uiWindow *, void *); + void *onContentSizeChangedData; + BOOL changingSize; + int fullscreen; + WINDOWPLACEMENT fsPrevPlacement; + int borderless; }; // from https://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing @@ -35,7 +41,6 @@ static void windowMargins(uiWindow *w, int *mx, int *my) static void windowRelayout(uiWindow *w) { - uiWindowsSizing sizing; int x, y, width, height; RECT r; int mx, my; @@ -64,7 +69,7 @@ static LRESULT CALLBACK windowWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARA CREATESTRUCTW *cs = (CREATESTRUCTW *) lParam; WINDOWPOS *wp = (WINDOWPOS *) lParam; MINMAXINFO *mmi = (MINMAXINFO *) lParam; - intmax_t width, height; + int width, height; LRESULT lResult; ww = GetWindowLongPtrW(hwnd, GWLP_USERDATA); @@ -82,13 +87,19 @@ static LRESULT CALLBACK windowWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARA // not a menu if (lParam != 0) break; - if (HIWORD(wParam) != 0) + // IsDialogMessage() will also generate IDOK and IDCANCEL when pressing Enter and Escape (respectively) on some controls, like EDIT controls + // swallow those too; they'll cause runMenuEvent() to panic + // TODO fix the root cause somehow + if (HIWORD(wParam) != 0 || LOWORD(wParam) <= IDCANCEL) break; runMenuEvent(LOWORD(wParam), uiWindow(w)); return 0; case WM_WINDOWPOSCHANGED: if ((wp->flags & SWP_NOSIZE) != 0) break; + if (w->onContentSizeChanged != NULL) // TODO figure out why this is happening too early + if (!w->changingSize) + (*(w->onContentSizeChanged))(w, w->onContentSizeChangedData); windowRelayout(w); return 0; case WM_GETMINMAXINFO: @@ -138,11 +149,17 @@ static int defaultOnClosing(uiWindow *w, void *data) return 0; } +static void defaultOnPositionContentSizeChanged(uiWindow *w, void *data) +{ + // do nothing +} + +static std::map windows; + static void uiWindowDestroy(uiControl *c) { uiWindow *w = uiWindow(c); - // TODO make sure all ports have the necessary verifications // first hide ourselves ShowWindow(w->hwnd, SW_HIDE); // now destroy the child @@ -154,15 +171,22 @@ static void uiWindowDestroy(uiControl *c) if (w->menubar != NULL) freeMenubar(w->menubar); // and finally free ourselves + windows.erase(w); uiWindowsEnsureDestroyWindow(w->hwnd); uiFreeControl(uiControl(w)); } uiWindowsControlDefaultHandle(uiWindow) -// TODO? -uiWindowsControlDefaultParent(uiWindow) -uiWindowsControlDefaultSetParent(uiWindow) -// end TODO + +uiControl *uiWindowParent(uiControl *c) +{ + return NULL; +} + +void uiWindowSetParent(uiControl *c, uiControl *parent) +{ + uiUserBugCannotSetParentOnToplevel("uiWindow"); +} static int uiWindowToplevel(uiControl *c) { @@ -183,7 +207,6 @@ static void uiWindowShow(uiControl *c) w->visible = 1; // just in case the window's minimum size wasn't recalculated already - // TODO is it needed? ensureMinimumWindowSize(w); if (w->shownOnce) { ShowWindow(w->hwnd, SW_SHOW); @@ -214,10 +237,9 @@ uiWindowsControlDefaultSyncEnableState(uiWindow) // TODO uiWindowsControlDefaultSetParentHWND(uiWindow) -static void uiWindowMinimumSize(uiWindowsControl *c, intmax_t *width, intmax_t *height) +static void uiWindowMinimumSize(uiWindowsControl *c, int *width, int *height) { uiWindow *w = uiWindow(c); - uiWindowsSizing sizing; int mx, my; *width = 0; @@ -253,6 +275,12 @@ static void uiWindowLayoutRect(uiWindowsControl *c, RECT *r) uiWindowsControlDefaultAssignControlIDZOrder(uiWindow) +static void uiWindowChildVisibilityChanged(uiWindowsControl *c) +{ + // TODO eliminate the redundancy + uiWindowsControlMinimumSizeChanged(c); +} + char *uiWindowTitle(uiWindow *w) { return uiWindowsWindowText(w->hwnd); @@ -264,12 +292,116 @@ void uiWindowSetTitle(uiWindow *w, const char *title) // don't queue resize; the caption isn't part of what affects layout and sizing of the client area (it'll be ellipsized if too long) } +// this is used for both fullscreening and centering +// see also https://blogs.msdn.microsoft.com/oldnewthing/20100412-00/?p=14353 and https://blogs.msdn.microsoft.com/oldnewthing/20050505-04/?p=35703 +static void windowMonitorRect(HWND hwnd, RECT *r) +{ + HMONITOR monitor; + MONITORINFO mi; + + monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); + ZeroMemory(&mi, sizeof (MONITORINFO)); + mi.cbSize = sizeof (MONITORINFO); + if (GetMonitorInfoW(monitor, &mi) == 0) { + logLastError(L"error getting window monitor rect"); + // default to SM_CXSCREEN x SM_CYSCREEN to be safe + r->left = 0; + r->top = 0; + r->right = GetSystemMetrics(SM_CXSCREEN); + r->bottom = GetSystemMetrics(SM_CYSCREEN); + return; + } + *r = mi.rcMonitor; +} + +void uiWindowContentSize(uiWindow *w, int *width, int *height) +{ + RECT r; + + uiWindowsEnsureGetClientRect(w->hwnd, &r); + *width = r.right - r.left; + *height = r.bottom - r.top; +} + +// TODO should this disallow too small? +void uiWindowSetContentSize(uiWindow *w, int width, int height) +{ + w->changingSize = TRUE; + clientSizeToWindowSize(w->hwnd, &width, &height, w->hasMenubar); + if (SetWindowPos(w->hwnd, NULL, 0, 0, width, height, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER) == 0) + logLastError(L"error resizing window"); + w->changingSize = FALSE; +} + +int uiWindowFullscreen(uiWindow *w) +{ + return w->fullscreen; +} + +void uiWindowSetFullscreen(uiWindow *w, int fullscreen) +{ + RECT r; + + if (w->fullscreen && fullscreen) + return; + if (!w->fullscreen && !fullscreen) + return; + w->fullscreen = fullscreen; + w->changingSize = TRUE; + if (w->fullscreen) { + ZeroMemory(&(w->fsPrevPlacement), sizeof (WINDOWPLACEMENT)); + w->fsPrevPlacement.length = sizeof (WINDOWPLACEMENT); + if (GetWindowPlacement(w->hwnd, &(w->fsPrevPlacement)) == 0) + logLastError(L"error getting old window placement"); + windowMonitorRect(w->hwnd, &r); + setStyle(w->hwnd, getStyle(w->hwnd) & ~WS_OVERLAPPEDWINDOW); + if (SetWindowPos(w->hwnd, HWND_TOP, + r.left, r.top, + r.right - r.left, r.bottom - r.top, + SWP_FRAMECHANGED | SWP_NOOWNERZORDER) == 0) + logLastError(L"error making window fullscreen"); + } else { + if (!w->borderless) // keep borderless until that is turned off + setStyle(w->hwnd, getStyle(w->hwnd) | WS_OVERLAPPEDWINDOW); + if (SetWindowPlacement(w->hwnd, &(w->fsPrevPlacement)) == 0) + logLastError(L"error leaving fullscreen"); + if (SetWindowPos(w->hwnd, NULL, + 0, 0, 0, 0, + SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER) == 0) + logLastError(L"error restoring window border after fullscreen"); + } + w->changingSize = FALSE; +} + +void uiWindowOnContentSizeChanged(uiWindow *w, void (*f)(uiWindow *, void *), void *data) +{ + w->onContentSizeChanged = f; + w->onContentSizeChangedData = data; +} + void uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *, void *), void *data) { w->onClosing = f; w->onClosingData = data; } +int uiWindowBorderless(uiWindow *w) +{ + return w->borderless; +} + +// TODO window should move to the old client position and should not have the extra space the borders left behind +// TODO extract the relevant styles from WS_OVERLAPPEDWINDOW? +void uiWindowSetBorderless(uiWindow *w, int borderless) +{ + w->borderless = borderless; + if (w->borderless) + setStyle(w->hwnd, getStyle(w->hwnd) & ~WS_OVERLAPPEDWINDOW); + else + if (!w->fullscreen) // keep borderless until leaving fullscreen + setStyle(w->hwnd, getStyle(w->hwnd) | WS_OVERLAPPEDWINDOW); +} + void uiWindowSetChild(uiWindow *w, uiControl *child) { if (w->child != NULL) { @@ -348,7 +480,7 @@ uiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar) NULL, NULL, hInstance, w); if (w->hwnd == NULL) logLastError(L"error creating window"); - uiFree(wtitle); + uiprivFree(wtitle); if (hasMenubar) { w->menubar = makeMenubar(); @@ -360,14 +492,16 @@ uiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar) setClientSize(w, width, height, hasMenubarBOOL, style, exstyle); uiWindowOnClosing(w, defaultOnClosing, NULL); + uiWindowOnContentSizeChanged(w, defaultOnPositionContentSizeChanged, NULL); + windows[w] = true; return w; } // this cannot queue a resize because it's called by the resize handler void ensureMinimumWindowSize(uiWindow *w) { - intmax_t width, height; + int width, height; RECT r; uiWindowsControlMinimumSize(uiWindowsControl(w), &width, &height); @@ -380,3 +514,23 @@ void ensureMinimumWindowSize(uiWindow *w) if (SetWindowPos(w->hwnd, NULL, 0, 0, width, height, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER) == 0) logLastError(L"error resizing window"); } + +void disableAllWindowsExcept(uiWindow *which) +{ + for (auto &w : windows) { + if (w.first == which) + continue; + EnableWindow(w.first->hwnd, FALSE); + } +} + +void enableAllWindowsExcept(uiWindow *which) +{ + for (auto &w : windows) { + if (w.first == which) + continue; + if (!uiControlEnabled(uiControl(w.first))) + continue; + EnableWindow(w.first->hwnd, TRUE); + } +} diff --git a/windows/winpublic.cpp b/windows/winpublic.cpp index 1cc9129d..397a3b54 100644 --- a/windows/winpublic.cpp +++ b/windows/winpublic.cpp @@ -23,7 +23,7 @@ void uiWindowsEnsureAssignControlIDZOrder(HWND hwnd, LONG_PTR *controlID, HWND * *insertAfter = hwnd; } -void uiWindowsEnsureMoveWindowDuringResize(HWND hwnd, intmax_t x, intmax_t y, intmax_t width, intmax_t height) +void uiWindowsEnsureMoveWindowDuringResize(HWND hwnd, int x, int y, int width, int height) { RECT r; diff --git a/windows/winutil.cpp b/windows/winutil.cpp index b4fa692e..507c5a3f 100644 --- a/windows/winutil.cpp +++ b/windows/winutil.cpp @@ -77,7 +77,7 @@ void setExStyle(HWND hwnd, DWORD exstyle) } // see http://blogs.msdn.com/b/oldnewthing/archive/2003/09/11/54885.aspx and http://blogs.msdn.com/b/oldnewthing/archive/2003/09/13/54917.aspx -void clientSizeToWindowSize(HWND hwnd, intmax_t *width, intmax_t *height, BOOL hasMenubar) +void clientSizeToWindowSize(HWND hwnd, int *width, int *height, BOOL hasMenubar) { RECT window; @@ -120,3 +120,35 @@ void setWindowInsertAfter(HWND hwnd, HWND insertAfter) if (SetWindowPos(hwnd, insertAfter, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOSIZE) == 0) logLastError(L"error reordering window"); } + +HWND getDlgItem(HWND hwnd, int id) +{ + HWND out; + + out = GetDlgItem(hwnd, id); + if (out == NULL) + logLastError(L"error getting dialog item handle"); + return out; +} + +void invalidateRect(HWND hwnd, RECT *r, BOOL erase) +{ + if (InvalidateRect(hwnd, r, erase) == 0) + logLastError(L"error invalidating window rect"); +} + +// that damn ABI bug is never going to escape me is it +D2D1_SIZE_F realGetSize(ID2D1RenderTarget *rt) +{ +#ifdef _MSC_VER + return rt->GetSize(); +#else + D2D1_SIZE_F size; + typedef D2D1_SIZE_F *(__stdcall ID2D1RenderTarget::* GetSizeF)(D2D1_SIZE_F *); + GetSizeF gs; + + gs = (GetSizeF) (&(rt->GetSize)); + (rt->*gs)(&size); + return size; +#endif +}