[
  {
    "path": ".appveyor.yml",
    "content": "version: 'build #{build}'\n\nenvironment:\n  matrix:\n    - arch: 386\n      libtype: shared\n      libfiles: libui.dll libui.lib\n      compiler: msvc2013\n      APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013\n    - arch: 386\n      libtype: static\n      libfiles: libui.lib\n      compiler: msvc2013\n      APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013\n    - arch: amd64\n      libtype: shared\n      libfiles: libui.dll libui.lib\n      compiler: msvc2013\n      APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013\n    - arch: amd64\n      libtype: static\n      libfiles: libui.lib\n      compiler: msvc2013\n      APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013\n    - arch: 386\n      libtype: static\n      libfiles: libui.lib\n      compiler: mingw\n      APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015\n    - arch: amd64\n      libtype: static\n      libfiles: libui.lib\n      compiler: mingw\n      APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015\n\nplatform:\n  - x64\n\n# Note: AppVeyor tries to be \"helpful\" and splits cmd.exe scripts into their constitutent lines to check their error codes. There is no way to switch this off; for true multi-line scripts we have to use PowerShell. But we need to use vcvarsall.bat, so that's out of the question.\ninstall:\n  # Set Python Version\n  - set \"PYTHON_ROOT=C:\\python37-x64\"\n  - if %arch%==386 ( set \"PYTHON_ROOT=C:\\python37\" )\n  - set \"PATH=%PYTHON_ROOT%;%PYTHON_ROOT%\\Scripts;%PATH%\"\n  # Install Latest Meson\n  - pip install meson\n  # Install Ninja\n  - powershell -Command \"Invoke-WebRequest https://github.com/ninja-build/ninja/releases/download/v1.9.0/ninja-win.zip -OutFile C:\\ninja-win.zip\"\n  - mkdir C:\\ninja\n  - powershell -Command \"Expand-Archive -LiteralPath C:\\ninja-win.zip -DestinationPath C:\\ninja\"\n  - set \"PATH=C:\\ninja;%PATH%\"\n  # Parameters for the build_script phase, to reduce their noise.\n  - set \"mingwPath=C:\\msys64\\mingw64\\bin\"\n  - set vcvarsallArch=x86\n  - if %arch%==386 ( set \"mingwPath=C:\\msys64\\mingw32\\bin\" )\n  - if %arch%==386 ( set vcvarsallArch=amd64 )\n\nbuild_script:\n  - if %compiler%==mingw ( set \"PATH=%mingwPath%;%PATH%\" )\n  - if not %compiler%==mingw ( call \"C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\vcvarsall.bat\" %vcvarsallArch% )\n  - meson setup build --buildtype=release --default-library=%libtype%\n  - ninja -C build\n\nafter_build:\n  - set \"version=%APPVEYOR_REPO_BRANCH%\"\n  - if %APPVEYOR_REPO_TAG%==true ( set \"version=%APPVEYOR_REPO_TAG_NAME%\" )\n  - set \"artifact=%version%-windows-%arch%-%compiler%-%libtype%\"\n  - cd build\\meson-out\n  # TODO msvc only?\n  - if %libtype%==static ( ren libui.a libui.lib )\n  - copy ..\\..\\ui.h .\n  - copy ..\\..\\ui_windows.h .\n  # remove unnecessary files\n  # TODO should we do this on Azure too?\n  - del libui.exp\n  - 7z a \"%APPVEYOR_BUILD_FOLDER%\\libui-%artifact%.zip\" %libfiles% ui.h ui_windows.h\n  - 7z a \"%APPVEYOR_BUILD_FOLDER%\\examples-%artifact%.zip\" controlgallery.exe cpp-multithread.exe datetime.exe drawtext.exe histogram.exe tester.exe timer.exe\n  - del ui.h ui_windows.h\n\nartifacts:\n  - path: libui-*.zip\n    name: libui\n  - path: examples-*.zip\n    name: examples\n\ndeploy:\n  provider: GitHub\n  artifact: libui, examples\n  auth_token:\n    secure: li92W7mFAC8HbAVeZN6Ugmo5H1GzKSjr6DXlMniLcCRspKmi2Nz1nlslSa+9sLfo\n  on:\n    appveyor_repo_tag: true # deploy on tag push only\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to libui\n\nlibui is an open source project that openly accepts contributions. I appreciate your help!\n\n## Rules for contributing code\n\nWhile 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.\n\n### Commit messages and pull request description\n\nlibui 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.\n\nYour 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.\n\n### Code formatting\n\nlibui 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.\n\nUse 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.\n\nExpressions 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.\n\nWhen breaking expressions into multiple lines, always break *after* an operator, such as `,` or `&&`.\n\nThere 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.\n\nIn 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.\n\n### Naming\n\nlibui uses camel-case for naming, with a handful of very specific exceptions (namely GObject method names, where GObject itself enforces the naming convention).\n\nAll 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`.\n\nPrivate 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.\n\nStatic functions and static objects do not have naming restrictions.\n\nAcronyms 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.\n\n### API documentation\n\n(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.)\n\n### Other commenting\n\n(TODO write this part)\n\n### Compatibility\n\nlibui 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.\n\nChoosing 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).\n\nGTK+ 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.\n\n(TODO talk about future.c/.cpp/.m files)\n\nAs for language compatibility, libui is written in C99. I have no intention of changing this.\n\nAs 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).\n\n**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.\n\n### Windows-specific notes\n\nThe Windows backend of libui is written in C++ using C++11.\n\nDespite using C++, please refrain from using the following:\n\n- using C++ in ui_windows.h (this file should still be C compatible)\n- smart pointers\n- namespaces\n- `using namespace`\n- ATL, MFC, WTL\n\nThe following are not recommended, for consistency with the rest of libui:\n\n- variable declarations anywhere in a function (keep them all at the top)\n- `for (int x...` (C++11 foreach syntax is fine, though)\n- omitting the `struct` on type names for ordinary structs\n\nThe format of a class should be\n\n```c++\nclass name : public ancestor {\n\tint privateVariable;\n\t// etc.\npublic:\n\t// public stuff here\n};\n```\n\n### GTK+-specific notes\n\nAvoid GNU-specific language features. I build with strict C99 conformance.\n\n### OS X-specific notes\n\nAvoid GNU-specific/clang-specific language features. I build with strict C99 conformance.\n\nlibui 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.\n\nTo 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\n\n```objective-c\n[[array objectAtIndex:i] method]\n```\n\nInstead, 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`)\n\nThe format of a class should be\n\n```objective-c\n@interface name : parent<protocols> {\n\t// ivars\n}\n// properties\n- (ret)method:(int)arg;\n// more methods\n@end\n\n@implementation name\n\n- (ret)method:(int)arg\n{\n\t// note the lack of semicolon\n}\n\n@end\n```\n"
  },
  {
    "path": "Compatibility.md",
    "content": "# Useful things in newer versions\n\n## Windows\n### Windows 7\nhttp://channel9.msdn.com/blogs/pdc2008/pc43\n\nTODO look up PDC 2008 talk \"new shell user interface\"\n\n- new animation and text engine\n- ribbon control (didn't this have some additional license?)\n- LVITEM.piColFmt\n\n### Windows 8\n\n### Windows 8.1\n\n### Windows 10\n\n## GTK+\nTODO what ships with Ubuntu Quantal (12.10)?\n\n### GTK+ 3.6\nships with: Ubuntu Raring (13.04)\n\n- GtkEntry and GtkTextView have input purposes and input hints for external input methods but do not change input themselves\n\t- according to Company, we connect to insert-text for that\n- GtkLevelBar\n- GtkMenuButton\n- **GtkSearchEntry**\n\n### GTK+ 3.8\nships with: Ubuntu Saucy (13.10)\n\nNot 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.\n\n### GTK+ 3.10\nships with: **Ubuntu Trusty (14.04 LTS)**\n<br>GLib version: 2.40\n\n- tab character stops in GtkEntry\n- GtkHeaderBar\n\t- intended for titlebar overrides; GtkInfoBar is what I keep thinking GtkHeaderBar is\n- **GtkListBox**\n- GtkRevealer for smooth animations of disclosure triangles\n- GtkSearchBar for custom search popups\n- **GtkStack and GtkStackSwitcher**\n- titlebar overrides (seems to be the hot new thing)\n\n### GTK+ 3.12\nships with: Ubuntu Utopic (14.10)\n<br>GLib version: 2.42\n\n- GtkActionBar (basically like the bottom-of-the-window toolbars in Mac programs)\n- gtk_get_locale_direction(), for internationalization\n- more control over GtkHeaderBar\n- **GtkPopover**\n\t- GtkPopovers on GtkMenuButtons\n- GtkStack signaling\n- **gtk_tree_path_new_from_indicesv()** (for when we add Table if we have trees too)\n\n### GTK+ 3.14\nships with: **Debian Jessie**, Ubuntu Vivid (15.04)\n<br>GLib version: Debian: 2.42, Ubuntu: 2.44\n\n- gestures\n- better GtkListbox selection handling\n- more style classes (TODO also prior?)\n- delayed switch changes on GtkSwitch\n\n### GTK+ 3.16\nships with: Ubuntu Wily (15.10)\n<br>GLib version: 2.46\n\n- gtk_clipboard_get_default() (???)\n- **GtkGLArea**\n- proper xalign and yalign for GtkLabel; should get rid of runtime deprecation warnings\n- better control of GtkListBox model-based creation (probably not relevant but)\n- GtkModelButton (for GActions; probably not relevant?)\n- wide handles on GtkPaned\n- GtkPopoverMenu\n- IPP paper names in GtkPaperSize (TODO will this be important for printing?)\n- multiple matches in GtkSearchEntry (TODO evaluate priority)\n- **GtkStackSidebar**\n-  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)\n- GtkTextView: extend-selection\n- GtkTextView: font fallbacks\n\n### GTK+ 3.18\n\n### GTK+ 3.20\n\n## Cocoa\n### Mac OS X 10.8\n\n- Foundation ([full details](https://developer.apple.com/library/mac/releasenotes/Foundation/RN-FoundationOlderNotes/#//apple_ref/doc/uid/TP40008080-TRANSLATED_CHAPTER_965-TRANSLATED_DEST_999B))\n\t- NSDateComponents supports leap months\n\t- NSNumberFormatter and NSDateFormatter default to 10.4 behavior by default (need to explicitly do this on 10.7)\n\t- **NSUserNotification and NSUserNotificationCenter for Growl-style notifications**\n\t- better linguistic triggers for Spanish and Italian\n\t- NSByteCountFormatter\n- AppKit ([full details](https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/#X10_8Notes))\n\t- view-based NSTableView/NSOutlineView have expansion tooltips\n\t- NSScrollView magnification\n\t- Quick Look events; TODO see if they conflict with keyboard handling in Area\n\t- NSPageController (maybe useful?)\n\t- not useful for package UI, but may be useful for a new library (probably not by me): NSSharingService\n\t- 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\n\t- NSTextAlternatives\n\t- -[NSOpenGLContext setFullScreen] now ineffective\n\t- +[NSColor underPageBackgroundColor]\n\n### Mac OS X 10.9\n\n- Foundation ([full details](https://developer.apple.com/library/mac/releasenotes/Foundation/RN-Foundation/))\n\t- system-provided progress reporting/cancellation support\n\t- NSURLComponents\n\t- **NSCalendar, NSDateFormatter, and NSNumberFormatter are now thread-safe**\n\t- various NSCalendar and NSDateComponents improvements\n- AppKit ([full details](https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKit/))\n\t- sheet handling is now block-based, queued, and in NSWindow; the delegate-based NSApplication API will still exist, except without the queue\n\t- similar changes to NSAlert\n\t- **return value changes to NSAlert**\n\t- window visibility APIs (occlusion)\n\t- NSApplicationActivationPolicyAccessory\n\t- fullscreen toolbar behavior changes\n\t- status items for multiple menu bars\n\t- better NSSharingService support\n\t- a special accelerated scrolling mode, Responsive Scrolling; won't matter for us since I plan to support the scroll wheel and it won't\n\t- NSScrollView live scrolling notifications\n\t- NSScrollView floating (anchored/non-scrolling) subviews\n\t- better multimonitor support\n\t- better key-value observing for NSOpenPanel/NSSavePanel (might want to look this up to see if we can override some other juicy details... TODO)\n\t- better accessory view key-view handling in NSOpenPanel/NSSavePanel\n\t- NSAppearance\n\t- **-[NSTableView moveRowAtIndex:toIndex:] bug regarding first responders fixed**\n\t- view-specific RTL overrides\n\n### Mac OS X 10.10\n\n### Mac OS X 10.11\n* **NSLayoutGuide**\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2014 Pietro Gagliardi\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n(this is called the MIT License or Expat License; see http://www.opensource.org/licenses/MIT)\n"
  },
  {
    "path": "NEWS.md",
    "content": "# Old News\n\n* **27 November 2016**\n\t* Decided to split the table stuff into its own branch. It will be developed independently of everything else, along with a few other features.\n\n* **2 November 2016**\n\t* 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.\n\n* **31 October 2016**\n\t* @krakjoe noticed that I accidentally used thread-unsafe code in uiQueueMain() on Unix. Fixed.\n\n* **24 October 2016**\n\t* `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.\n\n* **22 October 2016**\n\t* 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.\n\n* **21 October 2016**\n\t* `uiDrawTextWeightUltraBold` is now spelled correctly. Thanks to @krakjoe.\n\n* **18 June 2016**\n\t* 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!\n\n* **17 June 2016**\n\t* **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.\n\t* Please help [plan out a better menu API](https://github.com/andlabs/libui/issues/152).\n\t* `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.\n\t* **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.\n\t* Added `uiNewVerticalSeparator()` to complement `uiNewHorizontalSeparator()`.\n\n* **16 June 2016**\n\t* 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.).\n\t* Added `uiWindowFullscreen()` and `uiWindowSetFullscreen()` to allow making fullscreen uiWindows, taking advantage of OS facilities for fullscreen and without changing the screen resolution (!).\n\t* Added `uiWindowBorderless()` and `uiWindowSetBorderless()` for allowing borderless uiWindows.\n\t* 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.)\n\t* Added `uiProgressBarValue()` and allowed passing -1 to `uiProgressBarSetValue()` to make an indeterminate progress bar. Thanks to @emersion.\n\n* **15 June 2016**\n\t* Added `uiFormDelete()`; thanks to @emersion.\n\t* Added `uiWindowPosition()`, `uiWindowSetPosition()`, `uiWindowCenter()`, and `uiWindowOnPositionChanged()`, methods for manipulating uiWindow position.\n\n* **14 June 2016**\n\t* 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.\n\t* The same has been done on the Windows side as well.\n\t* Hiding and showing controls and padding calculations are now correct on Windows at long last.\n\t* Hiding a control in a uiForm now hides its label on all platforms.\n\n* **13 June 2016**\n\t* `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.\n\n* **12 June 2016**\n\t* 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\n\n* **8 June 2016**\n\t* Added `uiForm`, a new container control that arranges controls vertically, with properly aligned labels on each. Have fun!\n\n* **6 June 2016**\n\t* Added `uiRadioButtonsSelected()`, `uiRadioButtonsSetSelected()`, and `uiRadioButtonsOnSelected()` to control selection of a radio button and catch an event when such a thing happens.\n\n* **5 June 2016**\n\t* **Alpha 3.1 is here.** This was a much-needed update to Alpha 3 that changes a few things:\n\t\t* **The build system is now cmake.** cmake 2.8.11 or higher is needed.\n\t\t* Static linking is now fully possible.\n\t\t* MinGW linking is back, but static only.\n\t* Added `uiNewPasswordEntry()`, which creates a new `uiEntry` suitable for entering passwords.\n\t* 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.\n\n* **29 May 2016**\n\t* **Alpha 3 is here!** Get it [here](https://github.com/andlabs/libui/releases/tag/alpha3).\n\t* The next packaged release will introduce:\n\t\t* uiGrid, another way to lay out controls, a la GtkGrid\n\t\t* uiOpenGLArea, a way to render OpenGL content in a libui uiArea\n\t\t* uiTable, a data grid control that may or may not have tree facilities (if it does, it will be called uiTree instead)\n\t\t* a complete, possibly rewritten, drawing and text rendering infrastructure\n\t* Thanks to @pcwalton, we can now statically link libui! Simply do `make STATIC=1` instead of just `make`.\n\t\t* 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.\n\n* **28 May 2016**\n\t* As promised, **the minimum system requirements are now OS X 10.8 and GTK+ 3.10 for OS X and Unix, respectively**.\n\n* **26 May 2016**\n\t* 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`.\n\n* **25 May 2016**\n\t* 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.\n\n* **24 May 2016**\n\t* You can now help choose [a potential new build system for libui](https://github.com/andlabs/libui/issues/62).\n\t* Tomorrow I will decide if OS X 10.7 will also be dropped alongside GTK+ 3.4-3.8 this Saturday. Stay tuned.\n\t* As promised, `uiCombobox` is now split into `uiCombobox` for non-editable comboboxes and `uiEditableCombobox` for editable comboboxes. Mind the function changes as well :)\n\t* 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).\n\n* **23 May 2016**\n\t* Fixed surrogate pair drawing on OS X.\n\n* **22 May 2016**\n\t* 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).\n\t* 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.\n\t* Removed `uiControlVerifyDestroy()`; that is now part of `uiFreeControl()` itself.\n\t* 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.\n\t* Fixed uiMultilineEntry not properly having line breaks on Windows.\n\t* 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.)\n\t* uiAreas on Windows and some internal Direct2D areas now respond to `WM_PRINTCLIENT` properly, which should hopefully increase the quality of screenshots.\n\t* 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. :(\n\t* 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.\n\t* uiSpinbox and uiSlider now merely swap their min and max if min ≥ max. They will no longer panic and do nothing, respectively.\n\t* Matrix scaling will no longer leave the matrix in an invalid state on OS X and GTK+.\n\t* `uiMultilineEntrySetText()` and `uiMutlilineEntryAppend()` on GTK+ no longer fire `OnChanged()` events.\n\n* **21 May 2016**\n\t* I will now post announcements and updates here.\n\t* 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.\n\t* You can decide if I should also drop OS X 10.7 [here](https://github.com/andlabs/libui/issues/46).\n"
  },
  {
    "path": "README.md",
    "content": "# libui: a portable GUI library for C\n\nThis README is being written.<br>\n[![Build Status, Azure Pipelines](https://dev.azure.com/andlabs/libui/_apis/build/status/andlabs.libui?branchName=master)](https://dev.azure.com/andlabs/libui/_build/latest?definitionId=1&branchName=master)<br>\n[![Build Status, AppVeyor](https://ci.appveyor.com/api/projects/status/ouyk78c52mmisa31/branch/master?svg=true)](https://ci.appveyor.com/project/andlabs/libui/branch/master)\n\n## Status\n\nIt 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.\n\nlibui 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:\n\n- trees\n- clipboard support, including drag and drop\n- more and better dialogs\n- printing\n- accessibility for uiArea and custom controls\n- document-based programs\n- tighter OS integration (especially for document-based programs), to allow programs to fully feel native, rather than merely look and act native\n- better support for standard dialogs and features (search bars, etc.)\n- OpenGL support\n\nIn 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.\n\nFurthermore, 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.\n\nBut 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!\n\n## News\n\n*Note that today's entry (Eastern Time) may be updated later today.*\n\n* **7 April 2019**\n\t* **The build system has been switched to Meson.** See below for instructions. This change was made because the previous build system, CMake, caused countless headaches over trivial issues. Meson was chosen due to how unproblematic setting up libui's build just right was, as well as having design goals that are by coincidence closely aligned with what libui wants.\n\t* Travis CI has been replaced with Azure Pipelines and much of the AppVeyor CI configuration was integrated into the Azure Pipelines configuration. This shouldn't affect most developers.\n\n* **1 September 2018**\n\t* **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.\n\t* Alpha 4.1 also tries to make everything properly PIC-enabled.\n\n* **10 August 2018**\n\t* **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!\n\t* Alpha 4 should hopefully also include automated binary releases via CI. Thanks to those who helped set that up!\n\n* **8 August 2018**\n\t* 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!\n\n* **30 May 2018**\n\t* 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.\n\n* **16 May 2018**\n\t* 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.\n\n* **13 May 2018**\n\t* Added new functions to work with uiDateTimePickers: `uiDateTimePickerTime()`, `uiDateTimePickerSetTime()`, and `uiDateTimePickerOnChanged()`. These operate on standard `<time.h>` `struct tm`s. Thanks @cody271!\n\t* Release builds on Windows with MSVC should be fixed now; thanks @l0calh05t, @slahn, @mischnic, and @zentner-kyle.\n\n* **12 May 2018**\n\t* 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.\n\n* **2 May 2018**\n\t* 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.\n\n* **18 April 2018**\n\t* Introduced a new `uiTimer()` function for running code on a timer on the main thread. (Thanks to @cody271.)\n\t* 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.\n\n* **18 March 2018**\n\t* 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.\n\t* 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.\n\t* Also introduced a formal set of contribution guidelines, see `CONTRIBUTING.md` for details. They are still WIP.\n\n* **17 February 2018**\n\t* The longstanding Enter+Escape crashes on Windows have finally been fixed (thanks to @lxn).\n\t* **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).\n\t* Alpha 3.5 also includes a new control gallery example. The screenshots below have not been updated yet.\n\n*Old announcements can be found in the NEWS.md file.*\n\n## Runtime Requirements\n\n* Windows: Windows Vista SP2 with Platform Update or newer\n* Unix: GTK+ 3.10 or newer\n* Mac OS X: OS X 10.8 or newer\n\n## Build Requirements\n\n* All platforms:\n\t* [Meson](https://mesonbuild.com/) 0.48.0 or newer\n\t* Any of Meson's backends; this section assumes you are using [Ninja](https://ninja-build.org/), but there is no reason the other backends shouldn't work.\n* Windows: either\n\t* Microsoft Visual Studio 2013 or newer (2013 is needed for `va_copy()`) — you can build either a static or a shared library\n\t* 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:\n\t\t* [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\n* Unix: nothing else specific\n* Mac OS X: nothing else specific, so long as you can build Cocoa programs\n\n## Building\n\nlibui uses only [the standard Meson build options](https://mesonbuild.com/Builtin-options.html), so a libui build can be set up just like any other:\n\n```\n$ # you must be in the top-level libui directory, otherwise this won't work\n$ meson setup build [options]\n$ ninja -C build\n```\n\nOnce this completes, everything will be under `build/meson-out/`. (Note that unlike the previous build processes, everything is built by default, including tests and examples.)\n\nThe most important options are:\n\n* `--buildtype=(debug|release|...)` controls the type of build made; the default is `debug`. For a full list of valid values, consult [the Meson documentation](https://mesonbuild.com/Running-Meson.html).\n* `--default-library=(shared|static)` controls whether libui is built as a shared library or a static library; the default is `shared`. You currently cannot specify `both`, as the build process changes depending on the target type (though I am willing to look into changing things if at all possible).\n* `-Db_sanitize=which` allows enabling the chosen [sanitizer](https://github.com/google/sanitizers) on a system that supports sanitizers. The list of supported values is in [the Meson documentation](https://mesonbuild.com/Builtin-options.html#base-options).\n* `--backend=backend` allows using the specified `backend` for builds instead of `ninja` (the default). A list of supported values is in [the Meson documentation](https://mesonbuild.com/Builtin-options.html#universal-options).\n\nMost other built-in options will work, though keep in mind there are a handful of options that cannot be overridden because libui depends on them holding a specific value; if you do override these, though, libui will warn you when you run `meson`.\n\nThe Meson website and documentation has more in-depth usage instructions.\n\nFor the sake of completeness, I should note that the default value of `--layout` is `flat`, not the usual `mirror`. This is done both to make creating the release archives easier as well as to reduce the chance that shared library builds will fail to start on Windows because the DLL is in another directory. You can always specify this manually if you want.\n\nBackends other than `ninja` should work, but are untested by me.\n\n## Installation\n\nMeson also supports installing from source; if you use Ninja, just do\n\n```\n$ ninja -C build install\n```\n\nWhen running `meson`, the `--prefix` option will set the installation prefix. [The Meson documentation](https://mesonbuild.com/Builtin-options.html#universal-options) has more information, and even lists more fine-grained options that you can use to control the installation.\n\n#### Arch Linux\n\nCan be built from AUR: https://aur.archlinux.org/packages/libui-git/\n\n## Documentation\n\nNeeds to be written. Consult `ui.h` and the examples for details for now.\n\n## Language Bindings\n\nlibui 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.\n\nOther people have made bindings to other languages:\n\nLanguage | Bindings\n--- | ---\nC++ | [libui-cpp](https://github.com/billyquith/libui-cpp), [cpp-libui-qtlike](https://github.com/aoloe/cpp-libui-qtlike)\nC# / .NET Framework | [LibUI.Binding](https://github.com/NattyNarwhal/LibUI.Binding)\nC# / .NET Core | [DevZH.UI](https://github.com/noliar/DevZH.UI), [SharpUI](https://github.com/benpye/sharpui/), [TCD.UI](https://github.com/tacdevel/tcdfx)\nCHICKEN Scheme | [wasamasa/libui](https://github.com/wasamasa/libui)\nCommon Lisp | [jinwoo/cl-ui](https://github.com/jinwoo/cl-ui)\nCrystal | [libui.cr](https://github.com/Fusion/libui.cr), [hedron](https://github.com/Qwerp-Derp/hedron)\nD | [DerelictLibui (flat API)](https://github.com/Extrawurst/DerelictLibui), [libuid (object-oriented)](https://github.com/mogud/libuid)\nEuphoria | [libui-euphoria](https://github.com/ghaberek/libui-euphoria)\nHarbour | [hbui](https://github.com/rjopek/hbui)\nHaskell | [haskell-libui](https://github.com/beijaflor-io/haskell-libui)\nJavaScript/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)\nJulia | [Libui.jl](https://github.com/joa-quim/Libui.jl)\nKotlin | [kotlin-libui](https://github.com/msink/kotlin-libui)\nLua | [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)\nNim | [ui](https://github.com/nim-lang/ui)\nPerl6 | [perl6-libui](https://github.com/Garland-g/perl6-libui)\nPHP | [ui](https://github.com/krakjoe/ui)\nPython | [pylibui](https://github.com/joaoventura/pylibui)\nRuby | [libui-ruby](https://github.com/jamescook/libui-ruby), [libui](https://github.com/kojix2/libui)\nRust | [libui-rs](https://github.com/rust-native-ui/libui-rs)\nScala | [scalaui](https://github.com/lolgab/scalaui)\nSwift | [libui-swift](https://github.com/sclukey/libui-swift)\n\n## Frequently Asked Questions\n\n### Why does my program start in the background on OS X if I run from the command line?\nOS 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\".\n\nWhen 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.\n\nSee 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).\n\n## Contributing\n\nSee `CONTRIBUTING.md`.\n\n## Screenshots\n\nFrom examples/controlgallery:\n\n![Windows](examples/controlgallery/windows.png)\n\n![Unix](examples/controlgallery/unix.png)\n\n![OS X](examples/controlgallery/darwin.png)\n"
  },
  {
    "path": "TODO.md",
    "content": "- make sure the last line of text layouts include leading\n\n- documentation notes:\n\t- static binaries do not link system libraries, meaning apps still depend on shared GTK+, etc.\n\t- ui*Buttons are NOT compatible with uiButton functions\n\n- more robust layout handling\n\t- uiFormTie() for ensuring multiple uiForms have the same label area widths\n\t- uiSizeGroup for size groups (GtkSizeGroup on GTK+, auto layout constraints on OS X; consider adding after 10.8 is gone)\n\n- windows: should the initial hwndInsertAfter be HWND_BOTTOM for what we want?\n\n- windows: document the rules for controls and containers\n\n- windows: document the minimum size change propagation system\n\n- provisions for controls that cannot be grown? especiailly for windows\n\n- LC_VERSION_MIN_MACOSX has the 10.11 SDK; see if we can knock it down to 10.8 too; OS version is fine\n\t- apply the OS version stuff to the test program and examples too\n\t- what about micro versions (10.8.x)? force 10.8.0?\n\n- 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)\n\n- on OS X, edit shortcuts like command-C working require that the menu entries be defined, or so it seems, even for NSAlert\n\t- other platforms?\n\n- make sure all OS X event handlers that use target-action set the action to NULL when the target is unset\n\n- provide a way to get the currently selected uiTab page? set?\n\n- 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\n\t- it won't remove that outright, but it'll help\n\n- add an option to the test program to run page7b as an independent test in its own window\n\t- same for page7c\n\n- http://blogs.msdn.com/b/oldnewthing/archive/2004/01/12/57833.aspx provide a DEF file on Windows\n\n- all ports: update state when adding a control to a parent\n- should uiWindowsSizing be computed against the window handle, not the parent?\n\n- DPI awareness on windows\n\n- http://stackoverflow.com/questions/4543087/applicationwillterminate-and-the-dock-but-wanting-to-cancel-this-action\n\nultimately:\n- MAYBE readd lifetime handling/destruction blocking\n- related? [12:25] <ZeroOne> And the blue outline on those buttons [ALL clicked buttons on Windows 7] won't go away\n\t- I get this too\n\t\t- not anymore\n- SWP_NOCOPYBITS to avoid button redraw issues on Windows when not in tab, but only when making resize faster\n- secondary side alignment control in uiBox\n- Windows: don't abort if a cleanup function fails?\n\n- 32-bit Mac OS X support (requires lots of code changes)\n\t- change the build system to be more receptive to arch changes\n\nnotes to self\n- explicitly document label position at top-left corner\n- explicitly document that if number of radio buttons >= 1 there will always be a selection\n- mark that uiControlShow() on a uiWindow() will bring to front and give keyboard focus because of OS X\n\t- make sure ShowWindow() is sufficient for zorder on Windows\n- document that you CAN use InsertAt functions to insert at the first invalid index, even if the array is empty\n- note that uiTabInsertAt() does NOT change the current tab page (it may change its index if inserting before the current page)\n- note that the default action for uiWindowOnClosing() is to return 0 (keep the window open)\n- note that uiInitOptions should be initialized to zero\n- explicitly document that uiCheckboxSetChecked() and uiEntrySetText() do not fire uiCheckboxOnToggled() and uiEntryOnChanged(), respectively\n- note that if a menu is requested on systems with menubars on windows but no menus are defined, the result is a blank menubar, with whatever that means left up to the OS to decide\n- note that handling of multiple consecutive separators in menus, leading separators in menus, and trailing separators in menus are all OS-defined\n- note that uiDrawMatrixInvert() does not change the matrix if it fails\n- note that the use of strings that are not strictly valid UTF-8 results in undefined behavior\n\n- test RTL\n\t- automate RTL\n- 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+)\n\t- either that or keep using stock items\n\n- http://blogs.msdn.com/b/oldnewthing/archive/2014/02/26/10503148.aspx\n\n- build optimizations\n\n- use http://www.appveyor.com/ to do Windows build CI since people want CI\n\n\n\n\n\n\n- consider just having the windows backend in C++\n\t- consider having it all in C++\n\n\n\ndon't forget LONGTERMs as well\n\nnotes\n- http://blogs.msdn.com/b/oldnewthing/archive/2004/03/29/101121.aspx on accelerators\n\n- group and tab should act as if they have no child if the child is hidden\non windows\n\n\n\n- a way to do recursive main loops\n\t- how do we handle 0 returns from non-recursive uiMainStep() calls that aren't the main loop? (event handlers, for instance)\n- should repeated calls to uiMainStep() after uiQuit() return 0 reliably? this will be needed for non-recursive loops\n\nhttp://stackoverflow.com/questions/38338426/meaning-of-ampersand-in-rc-files/38338841?noredirect=1#comment64093084_38338841\n\nlabel shortcut keys\n\n- remove whining from source code\n\n[01:41:47]  <vrishab>\tHi. does pango support \"fgalpha\". I see that foreground=\"112233xx\" works ( alpha=xx ), but fgalpha is a no-op\n[01:52:29]  <vrishab>\tpango_attr_foreground_alpha_new (32767) seems to be called in either case, but only the \"foreground\" attr works\n[01:56:09] \tlolek (lolek@ip-91-244-230-76.simant.pl) joined the channel\n[01:57:48]  <vrishab>\tok. seems like \"foreground\" is mandatory attr, 1. \"foreground-without-alpha\" + \"alpha\" works 2. \"foreground-with-alpha\" works. 3. \"alpha\" alone doesn\n[01:57:52]  <vrishab>\t't work\n[01:58:29]  <vrishab>\tIs there a way to just specify alpha on the current foreground color ?\n[02:00:23] \tlolek (lolek@ip-91-244-230-76.simant.pl) left the channel\n[02:07:41] \tmjog (mjog@uniwide-pat-pool-129-94-8-98.gw.unsw.edu.au) left IRC (Quit: mjog)\n[02:08:10] \tseb128 (seb128@53542B83.cm-6-5a.dynamic.ziggo.nl) joined the channel\n[02:12:37]  <andlabs>\thuh\n[02:12:41]  <andlabs>\twhat version of pango?\n[02:13:05]  <vrishab>\tthe latest .\n[02:15:00]  <vrishab>\t1.40.3\n[02:20:46]  <andlabs>\tI'll ahve to keep this in mind then, thanks\n[02:20:59]  <andlabs>\tif only there was a cairo-specific attribute for alpha...\n\nFONT LOADING\n\n[00:10:08]  <hergertme>\tandlabs: 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\n[00:13:12] \tmrmcq2u_ (mrmcq2u@109.79.53.90) joined the channel\n[00:14:59] \tmrmcq2u (mrmcq2u@109.79.73.102) left IRC (Ping timeout: 181 seconds)\n[00:15:19]  <andlabs>\thergertme: no, which is why I was asking =P\n[00:15:30]  <andlabs>\tI would have dug down if I could ensure at least something about the backends a GTK+ 3 program uses\n[00:15:39]  <andlabs>\ton all platforms except windows and os x\n[00:16:11]  <hergertme>\tto the best of my (partially outdated, given pace of foss) knowledge there isn't an api to load from memory\n[00:16:28]  <hergertme>\tyou can possibly make a tmpdir and put a temp file in there\n[00:16:52]  <hergertme>\tand load that as your font dir in your FcConfig, so any PangoFontDescription would point to that one font, no matter what\n[00:17:18]  <hergertme>\t(using the API layed out in that link)\n[00:18:18] \tdsr1014__ (dsr1014@c-73-72-102-18.hsd1.il.comcast.net) joined the channel\n[00:35:18] \tsimukis_ (simukis@78-60-58-6.static.zebra.lt) left IRC (Quit: simukis_)\n[00:35:48] \tdreamon_ (dreamon@ppp-188-174-49-41.dynamic.mnet-online.de) joined the channel\n[00:40:09] \tsamtoday_ (samtoday@114-198-116-132.dyn.iinet.net.au) joined the channel\n[00:40:32] \tmjog (mjog@120.18.225.46) joined the channel\n[00:40:38]  <andlabs>\thergertme: not necessarily fontconfig\n[00:40:45]  <andlabs>\tit can be with ft2 or xft I guess\n[00:40:55]  <andlabs>\tespecially since I want the API NOT to make the font part of the font panel\n[00:42:07]  <hergertme>\twhat sort of deprecated code are you trying to support?\n[00:42:35]  <hergertme>\tboth of those are deprecated in pango fwiw\n[00:43:06]  <hergertme>\ton Linux im pretty sure we use FC everywhere these days\n[00:44:46]  <hergertme>\t(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)\n[00:49:14] \tvasaikar (vasaikar@125.16.97.121) joined the channel\n[00:50:14] \tkarlt (karl@2400:e780:801:224:f121:e611:d139:e70e) left IRC (Client exited)\n[00:50:49] \tkarlt (karl@2400:e780:801:224:f121:e611:d139:e70e) joined the channel\n[00:51:43] \tPioneerAxon (PioneerAxo@122.171.61.146) left IRC (Ping timeout: 180 seconds)\n[00:57:47] \tPioneerAxon (PioneerAxo@106.201.37.181) joined the channel\n[01:03:01] \tkarlt (karl@2400:e780:801:224:f121:e611:d139:e70e) left IRC (Ping timeout: 181 seconds)\n[01:05:49] \tmuhannad (muhannad@95.218.26.152) left IRC (Quit: muhannad)\n[01:07:51]  <andlabs>\thergertme: hm\n[01:07:54]  <andlabs>\tall right, thanks\n[01:08:05]  <andlabs>\thergertme: fwiw right now my requirement is 3.10\n[01:10:47]  <hergertme>\tah, well you'll probably be missing the neccesary font API on gtk_widget\n[01:11:04]  <hergertme>\tbut pango should be fine even back as far as https://developer.gnome.org/pango/1.28/PangoFcFontMap.html\n[01:11:56]  <andlabs>\tgood\n[01:12:04]  <andlabs>\tbecause this is for custom drawing into a DrawingArea\n[01:14:12]  <hergertme>\tpresumably 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)\n[01:15:53]  <hergertme>\tFcConfigAppFontAddFile() <-- that API\n[01:16:30]  <hergertme>\tgreat, and they don't say what version the API was added in teh docs\nfunction: ide_editor_map_bin_add()\n\n- 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\n- consider a uiAnticipateDoubleClick() or uiDoubleClickTime() (for a uiQueueTimer()) or something: https://blogs.msdn.microsoft.com/oldnewthing/20041015-00/?p=37553\n\n- determine whether MSGF_USER is for and if it's correct for our uiArea message filter (if we have one)\n\n- source file encoding and MSVC compiler itself? https://stackoverflow.com/questions/20518040/how-can-i-get-the-directwrite-padwrite-sample-to-work\n\t- also need to worry about object file and output encoding...\n\t- this also names the author of the padwrite sample\n\n- OpenType features TODOs\n\t- https://stackoverflow.com/questions/32545675/what-are-the-default-typography-settings-used-by-idwritetextlayout\n\t- feature/shaping interaction rules for arabic: https://www.microsoft.com/typography/OpenTypeDev/arabic/intro.htm\n\t- other stuff, mostly about UIs and what users expect to be able to set\n\t\t- https://klim.co.nz/blog/towards-an-ideal-opentype-user-interface/\n\t\t- https://libregraphicsmeeting.org/2016/designing-for-many-applications-opentype-features-ui/\n\t\t\t- https://www.youtube.com/watch?v=wEyDhsH076Y\n\t\t\t- https://twitter.com/peter_works\n\t\t- http://ilovetypography.com/2014/10/22/better-ui-for-better-typography-adobe-petition/\n\t\t- http://silgraphite.sourceforge.net/ui/studynote.html\n\n- add NXCOMPAT (DEP awareness) to the Windows builds\n\t- and ASLR too? or is that not a linker setting\n\nOS X: embedding an Info.plist into a binary directly\nhttps://www.objc.io/issues/6-build-tools/mach-o-executables/\nTODO will this let Dictation work?\nTODO investigate ad-hoc codesigning\n\nhttps://blogs.msdn.microsoft.com/oldnewthing/20040112-00/?p=41083 def files for decoration (I forget if I said this earlier)\n\nTODO ClipCursor() stuff; probably not useful for libui but still\nhttps://blogs.msdn.microsoft.com/oldnewthing/20140102-00/?p=2183\nhttps://blogs.msdn.microsoft.com/oldnewthing/20061117-03/?p=28973\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/ms648383(v=vs.85).aspx\n\nhttps://cmake.org/Wiki/CMake_Useful_Variables\nset(CMAKE_SHARED_LINKER_FLAGS \"-Wl,--no-undefined\")\nOn 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). \n(I used to have something like this back when I used makefiles; did it convert in? I forget)\n\nlook into these for the os x port\nhttps://developer.apple.com/documentation/appkit/view_management/nseditor?language=objc\nhttps://developer.apple.com/documentation/appkit/view_management/nseditorregistration?language=objc\n\nfor future versions of the os x port\nhttps://developer.apple.com/documentation/appkit/nslayoutguide?language=objc and anchors\nhttps://developer.apple.com/documentation/appkit/nsuserinterfacecompression?language=objc https://developer.apple.com/documentation/appkit/nsuserinterfacecompressionoptions?language=objc\nthough at some point we'll be able to use NSStackView and NSGridView directly, so...\n\nCocoa PDFs\nhttps://developer.apple.com/documentation/appkit/nspdfimagerep?language=objc\nhttps://developer.apple.com/documentation/coregraphics?language=objc\nhttps://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_pagination/osxp_pagination.html#//apple_ref/doc/uid/20001051-119037\nhttps://developer.apple.com/documentation/appkit/nsprintoperation/1529269-pdfoperationwithview?language=objc\nhttps://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_printapps/osxp_printapps.html#//apple_ref/doc/uid/20000861-BAJBFGED\nhttps://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_printingapi/osxp_printingapi.html#//apple_ref/doc/uid/10000083i-CH2-SW2\nhttps://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_printinfo/osxp_printinfo.html#//apple_ref/doc/uid/20000864-BAJBFGED\nhttps://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_printlayoutpanel/osxp_printlayoutpanel.html#//apple_ref/doc/uid/20000863-BAJBFGED\nhttps://developer.apple.com/documentation/appkit/nspagelayout?language=objc\nhttps://developer.apple.com/documentation/appkit/nsprintinfo?language=objc\nhttps://developer.apple.com/documentation/applicationservices/core_printing?language=objc\nhttps://developer.apple.com/documentation/applicationservices/1463247-pmcreatesession?language=objc\nhttps://developer.apple.com/documentation/applicationservices/pmprintsession?language=objc\nhttps://developer.apple.com/documentation/applicationservices/1460101-pmsessionbegincgdocumentnodialog?language=objc\nhttps://developer.apple.com/documentation/applicationservices/1463416-pmsessionbeginpagenodialog?language=objc\nhttps://developer.apple.com/documentation/applicationservices/1506831-anonymous/kpmdestinationprocesspdf?language=objc\nhttps://developer.apple.com/documentation/applicationservices/1461960-pmcreategenericprinter?language=objc\nhttps://developer.apple.com/documentation/applicationservices/1460101-pmsessionbegincgdocumentnodialog?language=objc\nhttps://developer.apple.com/documentation/applicationservices/1464527-pmsessionenddocumentnodialog?language=objc\nhttps://developer.apple.com/documentation/applicationservices/1461952-pmsessiongetcggraphicscontext?language=objc\nhttps://developer.apple.com/library/content/technotes/tn2248/_index.html\nhttps://developer.apple.com/library/content/samplecode/PMPrinterPrintWithFile/Introduction/Intro.html\nhttps://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_aboutprinting/osxp_aboutprt.html\n\n- run os x code with `OBJC_DEBUG_MISSING_POOLS=YES` and other `OBJC_HELP=YES` options\n\t- turn off the autorelease pool to make sure we're not autoreleasing improperly\n\nTODO investigate -Weverything in clang alongside -Wall in MSVC (and in gcc too maybe...)\n\nmac os x accessibility\n- https://developer.apple.com/documentation/appkit/nsworkspace/1524656-accessibilitydisplayshoulddiffer?language=objc\n- https://developer.apple.com/documentation/appkit/nsworkspace/1526290-accessibilitydisplayshouldincrea?language=objc\n- https://developer.apple.com/documentation/appkit/nsworkspace/1533006-accessibilitydisplayshouldreduce?language=objc\n\nuiEntry disabling bugs http://www.cocoabuilder.com/archive/cocoa/215525-nstextfield-bug-can-be.html\nuiMultilineEntry disabling https://developer.apple.com/library/content/qa/qa1461/_index.html\n\nmore TODOs:\n- make no guarantee about buildability of feature branches\n"
  },
  {
    "path": "_abort/windowevents/darwin_window.m",
    "content": "struct uiWindow {\n\t// constraints\n\tvoid (*onPositionChanged)(uiWindow *, void *);\n\tvoid *onPositionChangedData;\n\tBOOL suppressPositionChanged;\n\t// onContentSizeChanged\n};\n\n@interface windowDelegateClass : NSObject<NSWindowDelegate> {\n// windowShouldClose:\n- (void)windowDidMove:(NSNotification *)note;\n// windowDidResize:\n@end\n\n@implementation windowDelegateClass\n\n// - (BOOL)windowShouldClose:(id)sender\n\n// TODO doesn't happen live\n- (void)windowDidMove:(NSNotification *)note\n{\n\tuiWindow *w;\n\n\tw = [self lookupWindow:((NSWindow *) [note object])];\n\tif (!w->suppressPositionChanged)\n\t\t(*(w->onPositionChanged))(w, w->onPositionChangedData);\n}\n\n// - (void)windowDidResize:(NSNotification *)note\n\n// void uiWindowSetTitle(uiWindow *w, const char *title)\n\nvoid uiWindowPosition(uiWindow *w, int *x, int *y)\n{\n\tNSScreen *screen;\n\tNSRect r;\n\n\tr = [w->window frame];\n\t*x = r.origin.x;\n\t// this is the right screen to use; thanks mikeash in irc.freenode.net/#macdev\n\t// -mainScreen is useless for positioning (it's just the key window's screen)\n\t// and we use -frame, not -visibleFrame, for dealing with absolute positions\n\tscreen = (NSScreen *) [[NSScreen screens] objectAtIndex:0];\n\t*y = ([screen frame].size.height - r.origin.y) - r.size.height;\n}\n\nvoid uiWindowSetPosition(uiWindow *w, int x, int y)\n{\n\t// -[NSWindow setFrameTopLeftPoint:] is acting weird so...\n\tNSRect r;\n\tNSScreen *screen;\n\n\t// this fires windowDidMove:\n\tw->suppressPositionChanged = YES;\n\tr = [w->window frame];\n\tr.origin.x = x;\n\tscreen = (NSScreen *) [[NSScreen screens] objectAtIndex:0];\n\tr.origin.y = [screen frame].size.height - (y + r.size.height);\n\t[w->window setFrameOrigin:r.origin];\n\tw->suppressPositionChanged = NO;\n}\n\nvoid uiWindowCenter(uiWindow *w)\n{\n\tw->suppressPositionChanged = YES;\n\t[w->window center];\n\tw->suppressPositionChanged = NO;\n}\n\nvoid uiWindowOnPositionChanged(uiWindow *w, void (*f)(uiWindow *, void *), void *data)\n{\n\tw->onPositionChanged = f;\n\tw->onPositionChangedData = data;\n}\n\n// void uiWindowContentSize(uiWindow *w, int *width, int *height)\n\n// static int defaultOnClosing(uiWindow *w, void *data)\n\nstatic void defaultOnPositionContentSizeChanged(uiWindow *w, void *data)\n{\n\t// do nothing\n}\n\nuiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar)\n{\n//\tuiWindowOnClosing(w, defaultOnClosing, NULL);\n\tuiWindowOnPositionChanged(w, defaultOnPositionContentSizeChanged, NULL);\n//\tuiWindowOnContentSizeChanged(w, defaultOnPositionContentSizeChanged, NULL);\n}\n"
  },
  {
    "path": "_abort/windowevents/page15.c",
    "content": "static uiSpinbox *x, *y;\n\nstatic void moveX(uiSpinbox *s, void *data)\n{\n\tuiWindow *w = uiWindow(data);\n\tint xp, yp;\n\n\tuiWindowPosition(w, &xp, &yp);\n\txp = uiSpinboxValue(x);\n\tuiWindowSetPosition(w, xp, yp);\n}\n\nstatic void moveY(uiSpinbox *s, void *data)\n{\n\tuiWindow *w = uiWindow(data);\n\tint xp, yp;\n\n\tuiWindowPosition(w, &xp, &yp);\n\typ = uiSpinboxValue(y);\n\tuiWindowSetPosition(w, xp, yp);\n}\n\nstatic void updatepos(uiWindow *w)\n{\n\tint xp, yp;\n\n\tuiWindowPosition(w, &xp, &yp);\n\tuiSpinboxSetValue(x, xp);\n\tuiSpinboxSetValue(y, yp);\n}\n\nstatic void center(uiButton *b, void *data)\n{\n\tuiWindow *w = uiWindow(data);\n\n\tuiWindowCenter(w);\n\tupdatepos(w);\n}\n\nvoid onMove(uiWindow *w, void *data)\n{\n\tprintf(\"move\\n\");\n\tupdatepos(w);\n}\n\nuiBox *makePage15(uiWindow *w)\n{\n\thbox = newHorizontalBox();\n\t// 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\n\tuiBoxAppend(page15, uiControl(hbox), 0);\n\n\tuiBoxAppend(hbox, uiControl(uiNewLabel(\"Position\")), 0);\n\tx = uiNewSpinbox(INT_MIN, INT_MAX);\n\tuiBoxAppend(hbox, uiControl(x), 1);\n\ty = uiNewSpinbox(INT_MIN, INT_MAX);\n\tuiBoxAppend(hbox, uiControl(y), 1);\n\tbutton = uiNewButton(\"Center\");\n\tuiBoxAppend(hbox, uiControl(button), 0);\n\n\tuiSpinboxOnChanged(x, moveX, w);\n\tuiSpinboxOnChanged(y, moveY, w);\n\tuiButtonOnClicked(button, center, w);\n\tuiWindowOnPositionChanged(w, onMove, NULL);\n\tupdatepos(w);\n}\n"
  },
  {
    "path": "_abort/windowevents/ui.h",
    "content": "// uiWindowSetTitle\n_UI_EXTERN void uiWindowPosition(uiWindow *w, int *x, int *y);\n_UI_EXTERN void uiWindowSetPosition(uiWindow *w, int x, int y);\n_UI_EXTERN void uiWindowCenter(uiWindow *w);\n_UI_EXTERN void uiWindowOnPositionChanged(uiWindow *w, void (*f)(uiWindow *, void *), void *data);\n// uiWindowContentSize\n"
  },
  {
    "path": "_abort/windowevents/unix_window.c",
    "content": "struct uiWindow {\n//\tvoid *onClosingData;\n\tvoid (*onPositionChanged)(uiWindow *, void *);\n\tvoid *onPositionChangedData;\n\tgboolean changingPosition;\n//\tvoid (*onContentSizeChanged)(uiWindow *, void *);\n};\n\n// static gboolean onClosing(GtkWidget *win, GdkEvent *e, gpointer data)\n\nstatic gboolean onConfigure(GtkWidget *win, GdkEvent *e, gpointer data)\n{\n\tuiWindow *w = uiWindow(data);\n\n\t// there doesn't seem to be a way to determine if only moving or only resizing is happening :/\n\tif (w->changingPosition)\n\t\tw->changingPosition = FALSE;\n\telse\n\t\t(*(w->onPositionChanged))(w, w->onPositionChangedData);\n\t// always continue handling\n\treturn FALSE;\n}\n\n// static void onSizeAllocate(GtkWidget *widget, GdkRectangle *allocation, gpointer data)\n\n// static int defaultOnClosing(uiWindow *w, void *data)\n\nstatic void defaultOnPositionContentSizeChanged(uiWindow *w, void *data)\n{\n\t// do nothing\n}\n\n// static void uiWindowDestroy(uiControl *c)\n\n// void uiWindowSetTitle(uiWindow *w, const char *title)\n\n// TODO allow specifying either as NULL on all platforms\nvoid uiWindowPosition(uiWindow *w, int *x, int *y)\n{\n\tgint rx, ry;\n\n\tgtk_window_get_position(w->window, &rx, &ry);\n\t*x = rx;\n\t*y = ry;\n}\n\nvoid uiWindowSetPosition(uiWindow *w, int x, int y)\n{\n\tw->changingPosition = TRUE;\n\tgtk_window_move(w->window, x, y);\n\t// gtk_window_move() is asynchronous\n\t// we need to wait for a configure-event\n\t// thanks to hergertme in irc.gimp.net/#gtk+\n\twhile (w->changingPosition)\n\t\tif (!uiMainStep(1))\n\t\t\tbreak;\t\t// stop early if uiQuit() called\n}\n\nvoid uiWindowCenter(uiWindow *w)\n{\n\tgint x, y;\n\tGtkAllocation winalloc;\n\tGdkWindow *gdkwin;\n\tGdkScreen *screen;\n\tGdkRectangle workarea;\n\n\tgtk_widget_get_allocation(w->widget, &winalloc);\n\tgdkwin = gtk_widget_get_window(w->widget);\n\tscreen = gdk_window_get_screen(gdkwin);\n\tgdk_screen_get_monitor_workarea(screen,\n\t\tgdk_screen_get_monitor_at_window(screen, gdkwin),\n\t\t&workarea);\n\n\tx = (workarea.width - winalloc.width) / 2;\n\ty = (workarea.height - winalloc.height) / 2;\n\t// TODO move up slightly? see what Mutter or GNOME Shell or GNOME Terminal do(es)?\n\tuiWindowSetPosition(w, x, y);\n}\n\n// TODO this and size changed get set during uiWindowDestroy\nvoid uiWindowOnPositionChanged(uiWindow *w, void (*f)(uiWindow *, void *), void *data)\n{\n\tw->onPositionChanged = f;\n\tw->onPositionChangedData = data;\n}\n\n// void uiWindowContentSize(uiWindow *w, int *width, int *height)\n\nuiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar)\n{\n//\tg_signal_connect(w->widget, \"delete-event\", G_CALLBACK(onClosing), w);\n\tg_signal_connect(w->widget, \"configure-event\", G_CALLBACK(onConfigure), w);\n//\tg_signal_connect(w->childHolderWidget, \"size-allocate\", G_CALLBACK(onSizeAllocate), w);\n//\tuiWindowOnClosing(w, defaultOnClosing, NULL);\n\tuiWindowOnPositionChanged(w, defaultOnPositionContentSizeChanged, NULL);\n//\tuiWindowOnContentSizeChanged(w, defaultOnPositionContentSizeChanged, NULL);\n}\n"
  },
  {
    "path": "_abort/windowevents/windows_window.cpp",
    "content": "struct uiWindow {\n//\tBOOL hasMenubar;\n\tvoid (*onPositionChanged)(uiWindow *, void *);\n\tvoid *onPositionChangedData;\n\tBOOL changingPosition;\t\t// to avoid triggering the above when programmatically doing this\n//\tvoid (*onContentSizeChanged)(uiWindow *, void *);\n};\n\nstatic LRESULT CALLBACK windowWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)\n{\n\tcase WM_WINDOWPOSCHANGED:\n\t\tif ((wp->flags & SWP_NOMOVE) == 0)\n\t\t\tif (!w->changingPosition)\n\t\t\t\t(*(w->onPositionChanged))(w, w->onPositionChangedData);\n\t\t\t// and continue anyway\n//\t\tif ((wp->flags & SWP_NOSIZE) != 0)\n}\n\n// static int defaultOnClosing(uiWindow *w, void *data)\n\nstatic void defaultOnPositionContentSizeChanged(uiWindow *w, void *data)\n{\n\t// do nothing\n}\n\n// static std::map<uiWindow *, bool> windows;\n\n// void uiWindowSetTitle(uiWindow *w, const char *title)\n\nvoid uiWindowPosition(uiWindow *w, int *x, int *y)\n{\n\tRECT r;\n\n\tuiWindowsEnsureGetWindowRect(w->hwnd, &r);\n\t*x = r.left;\n\t*y = r.top;\n}\n\nvoid uiWindowSetPosition(uiWindow *w, int x, int y)\n{\n\tw->changingPosition = TRUE;\n\tif (SetWindowPos(w->hwnd, NULL, x, y, 0, 0, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER) == 0)\n\t\tlogLastError(L\"error moving window\");\n\tw->changingPosition = FALSE;\n}\n\n// static void windowMonitorRect(HWND hwnd, RECT *r)\n\n// TODO use the work rect instead?\nvoid uiWindowCenter(uiWindow *w)\n{\n\tRECT wr, mr;\n\tint x, y;\n\tLONG wwid, mwid;\n\tLONG wht, mht;\n\n\tuiWindowsEnsureGetWindowRect(w->hwnd, &wr);\n\twindowMonitorRect(w->hwnd, &mr);\n\twwid = wr.right - wr.left;\n\tmwid = mr.right - mr.left;\n\tx = (mwid - wwid) / 2;\n\twht = wr.bottom - wr.top;\n\tmht = mr.bottom - mr.top;\n\ty = (mht - wht) / 2;\n\t// 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\n\t// so just move 5% of the way up\n\t// TODO should this be on the work area?\n\t// TODO is this calculation correct?\n\ty -= y / 20;\n\tuiWindowSetPosition(w, x, y);\n}\n\nvoid uiWindowOnPositionChanged(uiWindow *w, void (*f)(uiWindow *, void *), void *data)\n{\n\tw->onPositionChanged = f;\n\tw->onPositionChangedData = data;\n}\n\n// void uiWindowContentSize(uiWindow *w, int *width, int *height)\n\nuiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar)\n{\n//\tuiWindowOnClosing(w, defaultOnClosing, NULL);\n\tuiWindowOnPositionChanged(w, defaultOnPositionContentSizeChanged, NULL);\n//\tuiWindowOnContentSizeChanged(w, defaultOnPositionContentSizeChanged, NULL);\n}\n"
  },
  {
    "path": "_doc/area.md",
    "content": "# uiArea\n\nuiArea is a uiControl that provides a canvas you can draw on. It receives keyboard and mouse events, supports scrolling, is DPI aware, and has several other useful features. A uiArea consists of the drawing area itself and horizontal and vertical scrollbars.\n\n## The Area Handler\n\nA uiArea is driven by an *area handler*. An area handler is an object with several methods that uiArea calls to do certain tasks. To create an area handler, simply have a structure whose first member is of type `uiAreaHandler`:\n\n```c\nstruct uiAreaHandler {\n\tvoid (*Draw)(uiAreaHandler *h, uiArea *a, uiAreaDrawParams *p);\n\tvoid (*HScrollConfig)(uiAreaHandler *h, uiArea *a, uiAreaScrollConfig *c);\n\tvoid (*VScrollConfig)(uiAreaHandler *h, uiArea *a, uiAreaScrollConfig *c);\n}\n```\n\n## Drawing\n\nUnlike drawing canvas controls in other toolkits, uiArea does **not** have a fixed size. The coordinate (0, 0) is always the top-left corner of the drawing area, regardless of how big the uiArea is in the current window or where the scrollbars presently are. Instead, you simulate a size by setting the scrollbar bounds, and you are given the current scrolling positions to base your drawing with.\n\nThe visible drawing area is called the *content area* by the drawing machinery.\n\nTODO have a diagram.\n\nWhen a part of the uiArea needs to be redrawn, the area handler's `Draw()` method is called. It receives the area handler, the uiArea, and a structure of parameters necessary for drawing.\n\n```c\nstruct uiAreaDrawParams {\n\tuiDrawContext *context;\n\n\tintmax_t contentWidth;\n\tintmax_t contentHeight;\n\n\tintmax_t hscrollpos;\n\tintmax_t vscrollpos;\n\n\tintmax_t clipX;\n\tintmax_t cilpY;\n\tintmax_t clipWidth;\n\tintmax_t clipHeight;\n\n\tTODO dpiX;\n\tTODO dpiY;\n};\n```\n\n`context` is the drawing context; see drawing.md for details.\n\n`contentWidth` and `contentHeight` is the current width and height of the content area. `hscrollpos` and `vscrollpos` are the current horizontal and vertical positions of the scrollbars, in units defined by the scrollbar configurations; see below.\n\n`clipX`, `clipY`, `clipWidth`, and `clipHeight` define a rectangle, in content area coordinates, that the OS has requested to be redrawn. You can use this to optimize your drawing by only drawing where drawing is needed; the OS may choose to drop any drawing done outside the clip rectangle.\n\n`dpiX` and `dpiY` are the uiArea's current DPI in the X and Y directions, respectively. Do not save these values; they are not guaranteed to stay the same once `Draw()` returns.\n"
  },
  {
    "path": "_doc/areahandler",
    "content": "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.\n"
  },
  {
    "path": "_doc/draw.md",
    "content": "# The Drawing Model\n\n> Note: This model is not exclusive to libui; it is also applicable to many 2D graphics libraries, such as Direct2D, cairo, and Core Graphics.\n\n## The Coordinate System and Points\n\nIn the traditional way we think of drawing, we think of rendering onto a plane of pixels. The pixels have a fixed size, and coordinates refer to the entire space that a pixel occupies.\n\nFor instance, in the traditional model, the coordinate system looks like\n\nTODO image\n\nand when we say \"draw a line from (0, 0) to (5, 5) exclusive\", we mean \"fill the spaces that are occupied by the pixels at (0, 0), (1, 1), (2, 2), (3, 3), and (4, 4)\":\n\nTODO image\n\nUgh. With pixels as big as the ones TODO.\n\nBut now let's pretend we're working in a coordinate system where the point at (x, y) corresponds strictly to the top-left corner of the area that a pixel occupies.\n\nTODO image\n\nIn this model, when we say \"draw a line from (0, 0) to (5, 5)\", we mean \"draw a straight line filling every pixel that we cross if we traced a line from the top-left corner of what we used to call the pixel at (0, 0 to the top-left corner of what we used to call the pixel at (5, 5)\":\n\nTODO image\n\nTODO.\n\nThere are both technical and non-technical reasons for following this model. The technical reason is that implementing certain drawing operations, such as filling shapes, is much easier if we do things this way. The [cairo FAQ](http://www.cairographics.org/FAQ/#sharp_lines) explains in more detail. The non-technical reason has to do with DPI independence.\n\n## DPI Independence vs. DPI Awareness\n\nAn upcoming trend in computing is the high-resolution display. These displays fit more dots in the same area that older screens could. The conventional term for the number of dots that fit in a given area is the \"dots per inch\", or DPI, measure.\n\nA naive approach to writing programs for these new displays is to think \"well, if I just take the DPI and only use it in calculations where I need to deal with real-world measurements such as inches, rendering pure pixels as I always have, I should be fine\". This kind of design is centered around *DPI awareness*. I know, I used to believe this too. But here's a little secret: this is wrong! A common myth about high-resolution monitors among non-technical people is that it makes the stuff on screen smaller. The mindset I just described causes this: TODO\n\nInstead, what we want out of a high-resolution display is *to show a more detailed view of the same image in the same space*. [The first image on Apple''s discussion of the topic](https://developer.apple.com/library/mac/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Art/backing_store_2x.png) is the perfect example. On the left, you see a low-resolution monitor. Notice how big chunks of the shapes go into the boxes. When the code that maps points to pixels runs, it can't have two colors in one square, so it has to decide what color to use. TODO\n\nTODO\n- talk about how this relates to the OpenGL unit cube\n- talk about the various names (point, user space coordinate, device-independent pixel)\n- talk about \"scaling\"\n"
  },
  {
    "path": "_doc/drawtext",
    "content": "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\n\nuiDrawTextLayoutExtents: 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\n\n\nfont matching is closest match but the search method is OS defined\n\n\nweight names in libui do not necessarily line up with their OS names\n\n\nuiDrawFontHandle() may not return a unique handle per instance\n\n"
  },
  {
    "path": "_doc/export/coretext",
    "content": "font features are not provided by the collection and have to be added when asking for a font\nit does preserve when going from CTFont to CTFontDescriptor\nfeature 17 has no flags in the header but can also hold small caps info\nif a feature is present, it is ignored; other features will still show up\n\tat least in the case of kLetterCaseTrait and kLowerCaseTrait\n"
  },
  {
    "path": "_doc/export/ctweights",
    "content": "// pseudo-go\n\nfunc (f *CTFont) IsRegistered() bool {\n\tn := f.Attribute(kCTFontRegistrationScopeAttribute)\n\tif n == nil {\n\t\treturn false\n\t}\n\treturn n.(*CFNumber).Uint32Value() == kCTFontManagerScopeNone\n}\n\n// this type is in libFontRegistry.dylib; functions like x_list.Prepend() are called things like x_list_prepend() there\ntype x_list struct {\n\tData\t\tinterface{}\n\tNext\t\t*x_list\n}\n\nfunc (x *x_list) Prepend(data interface{}) *x_list {\n\ty := malloc(sizeof (x_list))\n\tif y != nil {\n\t\ty.data = data\n\t\ty.next = x\n\t\treturn y\n\t}\n\treturn x\n}\n\nfunc (x *x_list) Reverse() *x_list {\n\tif x == nil {\n\t\treturn nil\n\t}\n\n\tvar old, next *x_list\n\n\tnext = nil\n\tfor {\n\t\told = x\n\t\tx = old.next\n\t\told.next = next\n\t\tnext = old\n\t\tif x == nil {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn old\n}\n\nfunc (x *x_list) Concat(y *x_list) *x_list {\n\tif x == nil {\n\t\treturn y\n\t}\n\tstart := x\n\tz := x\n\tfor {\n\t\tx = z\n\t\tz = z.next\n\t\tif z == nil {\n\t\t\tbreak\n\t\t}\n\t}\n\tx.next = y\n\treturn start\n}\n\n// based on CoreGraphics dylib's _CGFontCopyName\n// note that this is different from the public API function CGFontCopyPostScriptName() (which is font type-independent)\n// 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\nconst (\n\tkCGFontNameKeyPostScriptName = 0x6\n\tkCGFontNameKeyPreferredSubfamily = 0x11\n\tkCGFontNameKeyFontSubfamily = 0x2\n\tkCGFontNameKeyFullName = 0x4\n\tkCGFontNameKeyPreferredFamily = 0x10\n\tkCGFontNameKeyFontFamily = 0x1\n)\nfunc (f *CGFont) CopyName(key int) (string, bool) {\n\ttable := f.TableForTag('name')\n\tb := table.Bytes()\n\tn := table.Len()\n\n\t// this code looks weird, but we're imitating the assembly, or the effective effects thereof\n\toffCount := uint16(0)\n\toffStringOffset := uint16(2)\n\tif n > 1 {\n\t\toffCount = 2\n\t\toffStringOffset = 4\n\t}\n\n\tcount := uint16(0)\n\tif int(offCount) <= n {\n\t\tcount = uint16be(b[offCount:offCount + 2])\n\t}\n\n\toffNameRecord := offStringOffset + 2\n\tstringOffset := uint16(0)\n\tif int(offNameRecord) <= n {\n\t\tstringOffset = uint16be(b[offStringOffset:offStringOffset + 2])\n\t}\n\n\ttype NameRecord struct {\n\t\tPlatformID\t\tuint16\n\t\tPlatformSpecificID\tuint16\n\t\tLanguageID\t\tuint16\n\t\tNameID\t\t\tuint16\n\t\tLength\t\t\tuint16\n\t\tOffset\t\t\tuint16\n\t}\n\n\tvar nameList *x_list\n\n\taddrStrings := offNameRecords + (12 * count)\n\tif addrStrings != stringOffset {\n\t\tgoto hasLanguageTags\n\t}\n\tpos := offNameRecords\n\tif count == 0 {\n\t\t// TODO note assembly logic here\n\t} else {\n\t\tfor {\n\t\t\tvar nr NameRecord\n\n\t\t\tnr.PlatformID = 0\n\t\t\tnext := pos + 2\n\t\t\tif int(next) <= n {\n\t\t\t\tnr.PlatformID = uint16be(b[pos:pos + 2])\n\t\t\t\tpos = next\n\t\t\t}\n\n\t\t\tnr.PlatformSpecificID = 0\n\t\t\tnext = pos + 2\n\t\t\tif int(next) <= n {\n\t\t\t\tnr.PlatformSpecificID = uint16be(b[pos:pos + 2])\n\t\t\t\tpos = next\n\t\t\t}\n\n\t\t\tnr.LanguageID = 0\n\t\t\tnext = pos + 2\n\t\t\tif int(next) <= n {\n\t\t\t\tnr.LanguageID = uint16be(b[pos:pos + 2])\n\t\t\t\tpos = next\n\t\t\t}\n\n\t\t\tnr.NameID = 0\n\t\t\tnext = pos + 2\n\t\t\tif int(next) <= n {\n\t\t\t\tnr.NameID = uint16be(b[pos:pos + 2])\n\t\t\t\tpos = next\n\t\t\t}\n\n\t\t\tnr.Length = 0\n\t\t\tnext = pos + 2\n\t\t\tif int(next) <= n {\n\t\t\t\tnr.Length = uint16be(b[pos:pos + 2])\n\t\t\t\tpos = next\n\t\t\t}\n\n\t\t\tnr.Offset = 0\n\t\t\tnext = pos + 2\n\t\t\tif int(next) <= n {\n\t\t\t\tnr.Offset = uint16be(b[pos:pos + 2])\n\t\t\t\tpos = next\n\t\t\t}\n\n\t\t\tstrpos := stringOffset + nr.Offset\n\t\t\tif strpos >= n {\n\t\t\t\t// TODO put comment about imitating the assembly comparisons here\n\t\t\t} else {\n\t\t\t\trealLen := nr.Length\n\t\t\t\tstrend = strpos + nr.Length\n\t\t\t\tif strend > n {\n\t\t\t\t\trealLen = nr.Length - strpos\n\t\t\t\t\tstrend = strpos + realLen\n\t\t\t\t}\n\t\t\t\tb := malloc(12 + realLen + 1)\n\t\t\t\tif b != nil {\n\t\t\t\t\tname := (*sfnt_name_t)(b)\n\t\t\t\t\tname.PlatformID = nr.PlatformID\n\t\t\t\t\tname.PlatformSpecificID = nr.PlatformSpecificID\n\t\t\t\t\tname.LanguageID = nr.LanguageID\n\t\t\t\t\tname.NameID = nr.NameID\n\t\t\t\t\tname.Length = realLen\n\t\t\t\t\tmemcpy(&(name.Name), b[strpos:strend], realLen)\n\t\t\t\t\tname.Name[realLen] = 0\n\t\t\t\t\tnameList = nameList.Prepend(name)\n\t\t\t\t}\n\t\t\t}\n\t\t\tcount--\n\t\t\tif count == 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\tnameList = nameList.Reverse()\n\nhasLanguageTags:\n\tadd_localized_names := func(platformID uint16, platformSpecificID uint16, to *x_list) *x_list {\n\t\tout := (*x_list)(nil)\n\t\tif nameList == nil {\n\t\t\txx TODO logic verbatim etc.\n\t\t} else {\n\t\t\tx := nameList\n\t\t\tfor {\n\t\t\t\tname := (*sfnt_name_t)(x.data)\n\t\t\t\tif name.PlatformID != platformID {\n\t\t\t\t\txx TODO\n\t\t\t\t} else {\n\t\t\t\t\tif platformSpecificID == 0xFFFF || name.PlatformSpecificID == platformSpecificID {\n\t\t\t\t\t\tout = out.Prepend(name)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tx = x.next\n\t\t\t\tif x == nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tout = out.Reverse()\n\t\treturn to.Concat(out)\n\t}\n\tlocalized := (*x_list)(nil)\n\tlocalized = add_localized_names(0x1, 0xFFFF, localized)\n\tlocalized = add_localized_names(0, 0xFFFF, localized)\n\tlocalized = add_localized_names(0x3, 0xFFFF, localized)\n\tlocalized = add_localized_names(0x1, 0, localized)\n\tlocalized = add_localized_names(0x3, 0x9, localized)\n\tlocalized = add_localized_names(0x3, 0x409, localized)\n\n\tsysLocale := CFLocaleGetSystem()\n\t\n}\n\n// based on libFontRegistry.dylib's __ZNK8OS2Table15DetermineWeightERf — OS2Table::DetermineWeight(float&) const\nfunc RegistryDetermineOS2Weight(table *CFData) (float32, bool) {\n\tif table == nil {\n\t\treturn 0, false\n\t}\n\tif table.Len() < 78 {\n\t\treturn 0, false\n\t}\n\n\tb := table.Bytes()\n\tusWeightClass := uint16be(b[4:6])\n\tif usWeightClass >= 10 {\n\t\t// do nothing; we are preserving the original asm comparisons\n\t} else {\n\t\tusWeightClass *= 100\n\t}\n\t/* TODO:\n000000000000b37e         mov        dx, word [rax+4]\n000000000000b382         mov        cx, dx\n000000000000b385         rol        cx, 0x8\n000000000000b389         movzx      esi, cx\n000000000000b38c         imul       ecx, ecx, 100\n000000000000b38f         cmp        esi, 10\n000000000000b392         cmovae     cx, si\n000000000000b396         test       dx, dx\n000000000000b399         cmove      cx, si\n\twhat's the function of the last two instructions? */\n\n\t// note that this is an unsigned comparison, so underflow will result in a number > 998\n\t// the effect is the same as (usWeightClass == 0) || (usWeightClass >= 1000)\n\tif (usWeightClass - 1) > 998 {\n\t\t// note the - 2 here; the switch cases below reflect that!\n\t\t// also note that b[0x22] and panose will be unsigned, so underflow will result in a number > 9\n\t\tpanose := b[0x22] - 2\n\t\tif panose > 9 {\n\t\t\treturn 0, false\n\t\t}\n\t\tswitch panose {\n\t\tcase 0:\n\t\t\treturn float32as(-0.500000, 0xbf000000), true\n\t\tcase 1:\n\t\t\treturn float32as(-0.400000, 0xbecccccd), true\n\t\tcase 2:\n\t\t\t// yes, this returns false; I don't know why\n\t\t\treturn float32as(-0.300000, 0xbe99999a), false\n\t\tcase 3:\n\t\t\treturn float32as(-0.230000, 0xbe6b851f), true\n\t\tcase 4:\n\t\t\treturn float32as(0.230000, 0x3e6b851f), true\n\t\tcase 5:\n\t\t\treturn float32as(0.250000, 0x3e800000), true\n\t\tcase 6:\n\t\t\treturn float32as(0.400000, 0x3ecccccd), true\n\t\tcase 7:\n\t\t\treturn float32as(0.560000, 0x3f0f5c29), true\n\t\tcase 8:\n\t\t\treturn float32as(0.620000, 0x3f1eb852), true\n\t\tcase 9:\n\t\t\treturn float32as(0.800000, 0x3f4ccccd), true\n\t\t}\n\t\t// should not reach here\n\t}\n\n\t// let's mimic the assembly here too\n\t// the gotos avoid the massive if nesting\n\t// also note I'm using Go idioms and not saying \"else return\", imagine those if you must\n\tif usWeightClass > 100 {\n\t\tif usWeightClass > 200 {\n\t\t\tgoto do201AndUp\n\t\t}\n\t\treturn float32as(-0.500000, 0xbf000000), true\n\t}\n\treturn float32as(-0.800000, 0xbf4ccccd), true\n\ndo201AndUp:\n\tif usWeightClass > 300 {\n\t\tif usWeightClass > 400 {\n\t\t\tgoto do401AndUp\n\t\t}\n\t\treturn float32as(0.000000, 0x0), true\n\t}\n\treturn float32as(-0.400000, 0xbecccccd), true\n\ndo401AndUp:\n\tif usWeightClass > 500 {\n\t\tif usWeightClass > 600 {\n\t\t\tgoto do601AndUp\n\t\t}\n\t\treturn float32as(0.250000, 0x3e800000), true\n\t}\n\treturn float32as(0.230000, 0x3e6b851f), true\n\ndo601AndUp:\n\tif usWeightClass > 700 {\n\t\tif usWeightClass > 800 {\n\t\t\tgoto do801AndUp\n\t\t}\n\t\treturn float32as(0.500000, 0x3f000000), true\n\t}\n\treturn float32as(0.400000, 0x3ecccccd), true\n\ndo801AndUp:\n\tif usWeightClass > 900 {\n\t\tif usWeightClass > 950 {\n\t\t\treturn float32(0.800000, 0x3f4ccccd), true\n\t\t}\n\t\treturn float32(0.750000, 0x3f400000), true\n\t}\n\treturn float32as(0.620000, 0x3f1eb852), true\n}\n\n// based on libFontRegistry.dylib's __ZN11TFontTraitsC2EP6CGFontRK13TFontMetadata — TFontTraits::TFontTraits(CGFont*, TFontMetadata const&)\nfunc (f *Font) WeightFromFontRegistry32() float32 {\n\tvar weight float32\n\tvar hasWeight bool = false\n\n\tcgfont := f.CGFont()\n\tif f.RegistryHasMetadata() {\n\t\twv := f.RegistryMetadataValueForKey(\"MTD_Typeface_Weight_VisualDescriptor\")\n\t\tif wv != nil {\n\t\t\tif wn, ok := wv.(string); ok {\n\t\t\t\t// note: uses CFStringCompare(0)\n\t\t\t\tswitch wn {\n\t\t\t\tcase \"reg\":\n\t\t\t\t\tweight = float32as(0.000000, 0x0)\n\t\t\t\t\thasWeight = true\n\t\t\t\tcase \"semi\":\n\t\t\t\t\tweight = float32as(0.300000, 0x3e99999a)\n\t\t\t\t\thasWeight = true\n\t\t\t\tcase \"bold\":\n\t\t\t\t\tweight = float32as(0.400000, 0x3ecccccd)\n\t\t\t\t\thasWeight = true\n\t\t\t\tcase \"light\":\n\t\t\t\t\tweight = float32as(-0.400000, 0xbecccccd)\n\t\t\t\t\thasWeight = true\n\t\t\t\tcase \"med\":\n\t\t\t\t\tweight = float32as(0.230000, 0x3e6b851f)\n\t\t\t\t\thasWeight = true\n\t\t\t\tcase \"heavy\":\n\t\t\t\t\tweight = float32as(0.560000, 0x3f0f5c29)\n\t\t\t\t\thasWeight = true\n\t\t\t\tcase \"black\":\n\t\t\t\t\tweight = float32as(0.620000, 0x3f1eb852)\n\t\t\t\t\thasWeight = true\n\t\t\t\tcase \"thin\":\n\t\t\t\t\tweight = float32as(-0.600000, 0xbf19999a)\n\t\t\t\t\thasWeight = true\n\t\t\t\tcase \"ulight\":\n\t\t\t\t\tweight = float32as(-0.800000, 0xbf4ccccd)\n\t\t\t\t\thasWeight = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tcgpsname, ok := cgfont.CopyName(kCGFontNameKeyPostScriptName)\n\tif ok {\n\t\t// note: uses CFStringCompare(0)\n\t\tswitch cgpsname {\n\t\tcase \"LucidaGrande\",\n\t\t\t\".LucidaGrandeUI\",\n\t\t\t\".Keyboard\":\n\t\t\tweight = float32as(0.000000, 0x0)\n\t\t\thasWeight = true\n\t\tcase \"STHeiti\":\n\t\t\tweight = float32as(0.240000, 0x3e75c28f)\n\t\t\thasWeight = true\n\t\tcase \"STXihei\":\n\t\t\tweight = float32as(-0.100000, 0xbdcccccd)\n\t\t\thasWeight = true\n\t\tcase \"TimesNewRomanPSMT\":\n\t\t\tweight = float32as(0.000000, 0x0)\n\t\t\thasWeight = true\n\t\t}\n\t}\n\n\tstyleGlossaryStrings := []int{\n\t\tkCGFontNameKeyPreferredSubfamily,\n\t\tkCGFontNameKeyFontSubfamily,\n\t\tkCGFontNameKeyFullName,\n\t\tkCGFontNameKeyPreferredFamily,\n\t\tkCGFontNameKeyFontFamily,\n\t}\n\tweightNameMap := []struct {\n\t\tkey\t\tstring\n\t\tval\t\tfloat32\n\t}{\n\t\t{ \"Ultra Light\", float32as(-0.800000f, 0xbf4ccccd) },\n\t\t{ \"Ultra Black\", float32as(0.750000f, 0x3f400000) },\n\t\t{ \"Extra Light\", float32as(-0.500000f, 0xbf000000) },\n\t\t{ \"UltraBlack\", float32as(0.750000f, 0x3f400000) },\n\t\t{ \"ExtraBlack\", float32as(0.800000f, 0x3f4ccccd) },\n\t\t{ \"UltraLight\", float32as(-0.800000f, 0xbf4ccccd) },\n\t\t{ \"ExtraLight\", float32as(-0.500000f, 0xbf000000) },\n\t\t{ \"Ultra Thin\", float32as(-0.800000f, 0xbf4ccccd) },\n\t\t{ \"Extra Thin\", float32as(-0.800000f, 0xbf4ccccd) },\n\t\t{ \"Heavy Face\", float32as(0.560000f, 0x3f0f5c29) },\n\t\t{ \"Semi Light\", float32as(-0.200000f, 0xbe4ccccd) },\n\t\t{ \"Extra Bold\", float32as(0.500000f, 0x3f000000) },\n\t\t{ \"Ultra Bold\", float32as(0.700000f, 0x3f333333) },\n\t\t{ \"HeavyFace\", float32as(0.560000f, 0x3f0f5c29) },\n\t\t{ \"ExtraBold\", float32as(0.500000f, 0x3f000000) },\n\t\t{ \"UltraBold\", float32as(0.700000f, 0x3f333333) },\n\t\t{ \"Ext Black\", float32as(0.800000f, 0x3f4ccccd) },\n\t\t{ \"SemiLight\", float32as(-0.200000f, 0xbe4ccccd) },\n\t\t{ \"Demi Bold\", float32as(0.250000f, 0x3e800000) },\n\t\t{ \"Semi Bold\", float32as(0.300000f, 0x3e99999a) },\n\t\t{ \"Ext Light\", float32as(-0.500000f, 0xbf000000) },\n\t\t{ \"Ext Bold\", float32as(0.500000f, 0x3f000000) },\n\t\t{ \"DemiBold\", float32as(0.250000f, 0x3e800000) },\n\t\t{ \"SemiBold\", float32as(0.300000f, 0x3e99999a) },\n\t\t{ \"HairLine\", float32as(-0.800000f, 0xbf4ccccd) },\n\t\t{ \"Ext Thin\", float32as(-0.800000f, 0xbf4ccccd) },\n\t\t{ \"Medium\", float32as(0.230000f, 0x3e6b851f) },\n\t\t{ \"Poster\", float32as(0.800000f, 0x3f4ccccd) },\n\t\t{ \"Light\", float32as(-0.400000f, 0xbecccccd) },\n\t\t{ \"Ultra\", float32as(0.500000f, 0x3f000000) },\n\t\t{ \"Heavy\", float32as(0.560000f, 0x3f0f5c29) },\n\t\t{ \"Extra\", float32as(0.500000f, 0x3f000000) },\n\t\t{ \"Black\", float32as(0.620000f, 0x3f1eb852) },\n\t\t{ \"Super\", float32as(0.620000f, 0x3f1eb852) },\n\t\t{ \"Obese\", float32as(0.850000f, 0x3f59999a) },\n\t\t{ \"Lite\", float32as(-0.400000f, 0xbecccccd) },\n\t\t{ \"Book\", float32as(-0.230000f, 0xbe6b851f) },\n\t\t{ \"Demi\", float32as(0.250000f, 0x3e800000) },\n\t\t{ \"Semi\", float32as(0.300000f, 0x3e99999a) },\n\t\t{ \"Thin\", float32as(-0.500000f, 0xbf000000) },\n\t\t{ \"Bold\", float32as(0.400000f, 0x3ecccccd) },\n\t\t{ \"Nord\", float32as(0.800000f, 0x3f4ccccd) },\n\t\t{ \"Fat\", float32as(0.750000f, 0x3f400000) },\n\t\t{ \"W1\", float32as(-0.230000f, 0xbe6b851f) },\n\t\t{ \"W2\", float32as(-0.500000f, 0xbf000000) },\n\t\t{ \"W3\", float32as(-0.230000f, 0xbe6b851f) },\n\t\t{ \"W4\", float32as(0.000000f, 0x0) },\n\t\t{ \"W5\", float32as(0.230000f, 0x3e6b851f) },\n\t\t{ \"W6\", float32as(0.300000f, 0x3e99999a) },\n\t\t{ \"W7\", float32as(0.440000f, 0x3ee147ae) },\n\t\t{ \"W8\", float32as(0.540000f, 0x3f0a3d71) },\n\t\t{ \"W9\", float32as(0.620000f, 0x3f1eb852) },\n\t}\n\tfor _, key := range styleGlossaryStrings {\n\t\tif hasWeight {\n\t\t\tbreak\n\t\t}\n\t\tstr, ok := cgfont.CopyName(key)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, m := range weightNameMap {\n\t\t\tif str.FindWithOptions(m.key, CFRangeMake(0, str.CFStringLength()), kCFCompareCaseInsensitive | kCFCompareBackwards | kCFCompareNonliteral, nil) {\n\t\t\t\tweight = m.val\n\t\t\t\thasWeight = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif !hasWeight {\n\t\tos2table := cgfont.TableForTag('OS/2')\n\t\tweight, hasWeight = RegistryDetermineOS2Weight(os2table)\n\t}\n\n\tif !hasWeight {\n\t\theadtable := cgfont.TableForTag('head')\n\t\tif headtable != nil {\n\t\t\tif headtable.Len() >= 54 {\n\t\t\t\tb := headtable.Bytes()\n\t\t\t\tif (b[0x2d] & 1) != 0 {\n\t\t\t\t\tweight = float32as(0.400000, 0x3ecccccd)\n\t\t\t\t\thasWeight = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tstyleGlossaryAbbreviationKeys := []int{\n\t\tkCGFontNameKeyPreferredSubfamily,\n\t\tkCGFontNameKeyFontSubfamily,\n\t}\n\tabbreviatedWeightNameMap := []struct {\n\t\tkey\t\tstring\n\t\tval\t\tfloat32\n\t}{\n\t\t{ \"EL\", float32as(-0.200000, 0xbe4ccccd) },\n\t\t{ \"EB\", float32as(0.500000, 0x3f000000) },\n\t\t{ \"SB\", float32as(0.300000, 0x3e99999a) },\n\t\t{ \"UH\", float32as(0.800000, 0x3f4ccccd) },\n\t\t{ \"U\", float32as(0.700000, 0x3f333333) },\n\t\t{ \"L\", float32as(-0.400000, 0xbecccccd) },\n\t\t{ \"H\", float32as(0.560000, 0x3f0f5c29) },\n\t\t{ \"B\", float32as(0.400000, 0x3ecccccd) },\n\t\t{ \"M\", float32as(0.230000, 0x3e6b851f) },\n\t\t{ \"R\", float32as(0.000000, 0x0) },\n\t}\n\tif !hasWeight {\n\t\tfor _, key := range styleGlossaryAbbreviationStrings {\n\t\t\tstr, ok := cgfont.CopyName(key)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfor _, m := range abbreviatedWeightNameMap {\n\t\t\t\tif str.Compare(m.key, kCFCompareCaseInsensitive) == kCFCompareEqualTo {\n\t\t\t\t\tweight = m.val\n\t\t\t\t\thasWeight = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif hasWeight {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif !hasWeight {\n\t\treturn float32as(0.000000, 0x0)\n\t}\n\treturn weight\n}\n\n// 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\nfunc (f *Font) WeightFromFontRegistry() float64 {\n\treturn CFNumberWithFloat32(f.WeightFromFontRegistry32()).Float64Value()\n}\n\n// based on CoreText dylib's __Z13WeightOfClasst — WeightOfClass(unsigned short)\nfunc CoreText_WeightOfClass(usWeightClass uint16) float64 {\n\tif usWeightClass >= 11 {\n\t\t// do nothing; we are preserving the original asm comparisons\n\t\t// and yes, this one is 11, but the one above is 10\n\t} else {\n\t\tusWeightClass *= 100\n\t}\n\n\t// figure out what two floats our weight will be between\n\ti := usWeightClass / 100\n\tj := i + 1\n\tif j > 10 {\n\t\tj = 10\n\t}\n\tb := float64(i * 100)\n\tc := float64(j * 100)\n\n\ta := float64(0)\n\tif b != c {\n\t\ta = float64(usWeightClass)\n\t\ta -= b\n\t\tc -= b\n\t\ta /= c\n\t}\n\tscales := []float32{\n\t\tfloat32as(-1.000000, 0xbf800000),\n\t\tfloat32as(-0.700000, 0xbf333333),\n\t\tfloat32as(-0.500000, 0xbf000000),\n\t\tfloat32as(-0.230000, 0xbe6b851f),\n\t\tfloat32as(0.000000, 0x0),\n\t\tfloat32as(0.200000, 0x3e4ccccd),\n\t\tfloat32as(0.300000, 0x3e99999a),\n\t\tfloat32as(0.400000, 0x3ecccccd),\n\t\tfloat32as(0.600000, 0x3f19999a),\n\t\tfloat32as(0.800000, 0x3f4ccccd),\n\t\tfloat32as(1.000000, 0x3f800000),\n\t}\n\tc = float64(scale[i])\n\tb = float64[scale[j])\n\treturn fma(a, b, c)\n}\n\n// based on CoreText dylib's __ZL33CreateTraitsByStyleGlossaryStringPK10__CFString — CreateTraitsByStyleGlossaryString(__CFString const*)\nfunc CoreText_WeightByStyleGlossaryString(str string) (weight float64, ok bool) {\n\tstr.Fold(kCFCompareCaseInsensitive, nil)\n\tweightNameMap := []struct {\n\t\tkey\t\tstring\n\t\tval\t\tfloat32\n\t}{\n\t\t{ \"ultra light\", float32as(-0.800000, 0xbf4ccccd) },\n\t\t{ \"ultra black\", float32as(0.750000, 0x3f400000) },\n\t\t{ \"extra light\", float32as(-0.500000, 0xbf000000) },\n\t\t{ \"ultralight\", float32as(-0.800000, 0xbf4ccccd) },\n\t\t{ \"ultrablack\", float32as(0.750000, 0x3f400000) },\n\t\t{ \"extrablack\", float32as(0.800000, 0x3f4ccccd) },\n\t\t{ \"extralight\", float32as(-0.500000, 0xbf000000) }\n\t\t{ \"heavy face\", float32as(0.560000, 0x3f0f5c29) },\n\t\t{ \"semi light\", float32as(-0.200000, 0xbe4ccccd) },\n\t\t{ \"extra bold\", float32as(0.500000, 0x3f000000) },\n\t\t{ \"ultra bold\", float32as(0.700000, 0x3f333333) },\n\t\t{ \"heavyface\", float32as(0.560000, 0x3f0f5c29) },\n\t\t{ \"extrabold\", float32as(0.500000, 0x3f000000) },\n\t\t{ \"ultrabold\", float32as(0.700000, 0x3f333333) },\n\t\t{ \"semilight\", float32as(-0.200000, 0xbe4ccccd) },\n\t\t{ \"demi bold\", float32as(0.250000, 0x3e800000) },\n\t\t{ \"semi bold\", float32as(0.300000, 0x3e99999a) },\n\t\t{ \"demibold\", float32as(0.250000, 0x3e800000) },\n\t\t{ \"semibold\", float32as(0.300000, 0x3e99999a) },\n\t\t{ \"hairline\", float32as(-0.700000, 0xbf333333) },\n\t\t{ \"medium\", float32as(0.230000, 0x3e6b851f) },\n\t\t{ \"poster\", float32as(0.800000, 0x3f4ccccd) },\n\t\t{ \"light\", float32as(-0.400000, 0xbecccccd) },\n\t\t{ \"heavy\", float32as(0.560000, 0x3f0f5c29) },\n\t\t{ \"extra\", float32as(0.500000, 0x3f000000) },\n\t\t{ \"black\", float32as(0.620000, 0x3f1eb852) },\n\t\t{ \"super\", float32as(0.620000, 0x3f1eb852) },\n\t\t{ \"obese\", float32as(0.850000, 0x3f59999a) },\n\t\t{ \"lite\", float32as(-0.400000, 0xbecccccd) },\n\t\t{ \"book\", float32as(-0.230000, 0xbe6b851f) },\n\t\t{ \"demi\", float32as(0.250000, 0x3e800000) },\n\t\t{ \"semi\", float32as(0.300000, 0x3e99999a) },\n\t\t{ \"thin\", float32as(-0.500000, 0xbf000000) },\n\t\t{ \"bold\", float32as(0.400000, 0x3ecccccd) },\n\t\t{ \"nord\", float32as(0.800000, 0x3f4ccccd) },\n\t\t{ \"fat\", float32as(0.750000, 0x3f400000) },\n\t\t{ \"w1\", float32as(-0.700000, 0xbf333333) },\n\t\t{ \"w2\", float32as(-0.500000, 0xbf000000) },\n\t\t{ \"w3\", float32as(-0.230000, 0xbe6b851f) },\n\t\t{ \"w4\", float32as(0.000000, 0x0) },\n\t\t{ \"w5\", float32as(0.230000, 0x3e6b851f) },\n\t\t{ \"w6\", float32as(0.300000, 0x3e99999a) },\n\t\t{ \"w7\", float32as(0.440000, 0x3ee147ae) },\n\t\t{ \"w8\", float32as(0.540000, 0x3f0a3d71) },\n\t\t{ \"w9\", float32as(0.620000, 0x3f1eb852) },\n\t}\n\tfor _, m := range weightNameMap {\n\t\tif strstr(str, m.key) != nil {\n\t\t\tval := CFNumberWithFloat32(m.val)\n\t\t\treturn val.Float64Value(), true\n\t\t}\n\t}\n\treturn 0, false\n}\n\n// based on CoreText dylib's __ZNK9TBaseFont29CreateTraitsValuesPerFontInfoEP12MetadataFlag — TBaseFont::CreateTraitsValuesPerFontInfo(MetadataFlag*) const\nfunc (f *CTFont) Weight() float64 {\n\tif f.IsRegistered() {\n\t\treturn f.WeightFromFontRegistry()\n\t}\n\n\tweight := float64as(2.0, 0x4000000000000000)\n\tebx := -1\n\thasWeight := false\n\n\tname := f.Name(kCTFontPostScriptNameKey)\n\tif name != nil {\n\t\tswitch *name {\n\t\tcase \"LucidaGrande\":\n\t\t\tweight = float64as(0.000000, 0x0)\n\t\t\thasWeight = true\n\t\tcase \".LucidaGrandeUI\":\n\t\t\tweight = float64as(0.000000, 0x0)\n\t\t\thasWeight = true\n\t\tcase \"STHeiti\":\n\t\t\tweight = float64as(0.240000, 0x3fceb851eb851eb8)\n\t\t\thasWeight = true\n\t\tcase \"STXihei\":\n\t\t\tweight = float64as(-0.100000, 0xbfb999999999999a)\n\t\t\thasWeight = true\n\t\tcase \"TimesNewRomanPSMT\":\n\t\t\tweight = float64as(0.000000, 0x0)\n\t\t\thasWeight = true\n\t\t// there is one more hardcoded case, for \"Times-Roman\", but that will only set the class style, not the weight\n\t\t}\n\t}\n\n\tos2table := f.Table('OS/2')\n\tif os2table != nil {\n\t\tif !hasWeight {\n\t\t\tvar usWeightClass uint16\n\n\t\t\tvalid := false\n\t\t\tif os2table.Len() > 77 {\n\t\t\t\tb := os2table.Bytes()\n\t\t\t\tusWeightClass = uint16be(b[4:6])\n\t\t\t\tif usWeightClass > 1000 {\n\t\t\t\t\tweight = 0\n\t\t\t\t\thasWeight = false\n\t\t\t\t} else {\n\t\t\t\t\tvalid = true\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tusWeightClass = 0\n\t\t\t\tvalid = true\n\t\t\t}\n\t\t\tif valid {\n\t\t\t\tweight = CoreText_WeightOfClass(usWeightClass)\n\t\t\t\thasWeight = true\n\t\t\t}\n\t\t}\n\t}\n\n\tstyleGlossaryNames := []string{\n\t\tkCTFontSubFamilyNameKey,\n\t\tkCTFontFullNameKey,\n\t\tkCTFontFamilyNameKey,\n\t}\n\tfor _, key := range styleGlossaryNames {\n\t\tname := f.Name(key)\n\t\tif name == nil {\n\t\t\tcontinue\n\t\t}\n\t\tcandidate, ok := CoreText_WeightByStyleGlossaryString(*name)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif !hasWeight {\n\t\t\tweight = candidate\n\t\t\thasWeight = true\n\t\t}\n\t}\n\n\tif hasWeight {\n\t\treturn weight\n\t}\n\treturn 0\n}\n\nfunc (f *Font) ShouldEnableBoldSymbolicTrait() bool {\n\tif f.IsRegistered() {\n\t\treturn f.ShouldEnableBoldSymbolicTraitFromRegistry()\n\t}\n\tno := f.Weight() <= float64as(0.239000, 0x3fce978d4fdf3b64)\n\treturn !no\n}\n"
  },
  {
    "path": "_doc/export/ctweightsannotated",
    "content": "-0.100000  0xbdcccccd          registered postscript name \"STXihei\"\n-0.100000  0xbfb999999999999a  unregistered postscript name \"STXihei\"\n-0.200000  0xbe4ccccd          registered subfamily abbr \"EL\"\n-0.200000  0xbe4ccccd          \"Semi Light\"\n-0.200000  0xbe4ccccd          \"SemiLight\"\n-0.200000  0xbe4ccccd          \"semi light\"\n-0.200000  0xbe4ccccd          \"semilight\"\n-0.230000  0xbe6b851f          \"Book\"\n-0.230000  0xbe6b851f          \"W1\"\n-0.230000  0xbe6b851f          \"W3\"\n-0.230000  0xbe6b851f          \"book\"\n-0.230000  0xbe6b851f          \"w3\"\n-0.230000  0xbe6b851f          panose 5\n-0.400000  0xbecccccd          registered subfamily abbr \"L\"\n-0.400000  0xbecccccd          \"Light\"\n-0.400000  0xbecccccd          \"Lite\"\n-0.400000  0xbecccccd          \"light\"\n-0.400000  0xbecccccd          \"light\"\n-0.400000  0xbecccccd          \"lite\"\n-0.400000  0xbecccccd          registered OS2 weights 3, 201 - 300\n-0.400000  0xbecccccd          panose 3\n-0.500000  0xbf000000          \"Ext Light\"\n-0.500000  0xbf000000          \"Extra Light\"\n-0.500000  0xbf000000          \"ExtraLight\"\n-0.500000  0xbf000000          \"Thin\"\n-0.500000  0xbf000000          \"W2\"\n-0.500000  0xbf000000          \"extra light\"\n-0.500000  0xbf000000          \"extralight\"\n-0.500000  0xbf000000          \"thin\"\n-0.500000  0xbf000000          \"w2\"\n-0.500000  0xbf000000          registered OS2 weights 2, 101 - 200\n-0.500000  0xbf000000          panose 2\n-0.600000  0xbf19999a          \"thin\"\n-0.700000  0xbf333333          \"hairline\"\n-0.700000  0xbf333333          \"w1\"\n-0.800000  0xbf4ccccd          \"Ext Thin\"\n-0.800000  0xbf4ccccd          \"Extra Thin\"\n-0.800000  0xbf4ccccd          \"HairLine\"\n-0.800000  0xbf4ccccd          \"Ultra Light\"\n-0.800000  0xbf4ccccd          \"Ultra Thin\"\n-0.800000  0xbf4ccccd          \"UltraLight\"\n-0.800000  0xbf4ccccd          \"ulight\"\n-0.800000  0xbf4ccccd          \"ultra light\"\n-0.800000  0xbf4ccccd          \"ultralight\"\n-0.800000  0xbf4ccccd          registered OS2 weights 1, 10 - 100\n0.000000   0x0                 registered postscript name \".Keyboard\"\n0.000000   0x0                 registered postscript name \".LucidaGrandeUI\"\n0.000000   0x0                 unregistered postscript name \".LucidaGrandeUI\"\n0.000000   0x0                 registered postscript name \"LucidaGrande\"\n0.000000   0x0                 unregistered postscript name \"LucidaGrande\"\n0.000000   0x0                 registered subfamily abbr \"R\"\n0.000000   0x0                 registered postscript name \"TimesNewRomanPSMT\"\n0.000000   0x0                 unregistered postscript name \"TimesNewRomanPSMT\"\n0.000000   0x0                 \"W4\"\n0.000000   0x0                 \"reg\"\n0.000000   0x0                 \"w4\"\n0.000000   0x0                 registered OS2 weights 4, 301 - 400\n0.000000   0x0                 default\n0.230000   0x3e6b851f          registered OS2 weights 5, 401 - 500\n0.230000   0x3e6b851f          registered subfamily abbr \"M\"\n0.230000   0x3e6b851f          \"Medium\"\n0.230000   0x3e6b851f          \"W5\"\n0.230000   0x3e6b851f          \"med\"\n0.230000   0x3e6b851f          \"medium\"\n0.230000   0x3e6b851f          \"w5\"\n0.230000   0x3e6b851f          panose 6\n0.240000   0x3e75c28f          registered postscript name \"STHeiti\"\n0.240000   0x3fceb851eb851eb8  unregistered postscript name \"STHeiti\"\n0.250000   0x3e800000          \"Demi Bold\"\n0.250000   0x3e800000          \"Demi\"\n0.250000   0x3e800000          \"DemiBold\"\n0.250000   0x3e800000          \"demi bold\"\n0.250000   0x3e800000          \"demi\"\n0.250000   0x3e800000          \"demibold\"\n0.250000   0x3e800000          registered OS2 weights 6, 501 - 600\n0.250000   0x3e800000          panose 7\n0.300000   0x3e99999a          registered subfamily abbr \"SB\"\n0.300000   0x3e99999a          \"Semi Bold\"\n0.300000   0x3e99999a          \"Semi\"\n0.300000   0x3e99999a          \"SemiBold\"\n0.300000   0x3e99999a          \"W6\"\n0.300000   0x3e99999a          \"semi bold\"\n0.300000   0x3e99999a          \"semi\"\n0.300000   0x3e99999a          \"semi\"\n0.300000   0x3e99999a          \"semibold\"\n0.300000   0x3e99999a          \"w6\"\n0.400000   0x3ecccccd          registered subfamily abbr \"B\"\n0.400000   0x3ecccccd          \"Bold\"\n0.400000   0x3ecccccd          \"bold\"\n0.400000   0x3ecccccd          \"bold\"\n0.400000   0x3ecccccd          'head'[0x2D] & 1\n0.400000   0x3ecccccd          registered OS2 weights 7, 601 - 700\n0.400000   0x3ecccccd          panose 8\n0.440000   0x3ee147ae          \"W7\"\n0.440000   0x3ee147ae          \"w7\"\n0.500000   0x3f000000          registered subfamily abbr \"EB\"\n0.500000   0x3f000000          \"Ext Bold\"\n0.500000   0x3f000000          \"Extra Bold\"\n0.500000   0x3f000000          \"Extra\"\n0.500000   0x3f000000          \"ExtraBold\"\n0.500000   0x3f000000          \"Ultra\"\n0.500000   0x3f000000          \"extra bold\"\n0.500000   0x3f000000          \"extra\"\n0.500000   0x3f000000          \"extrabold\"\n0.500000   0x3f000000          registered OS2 weights 8, 701 - 800\n0.540000   0x3f0a3d71          \"W8\"\n0.540000   0x3f0a3d71          \"w8\"\n0.560000   0x3f0f5c29          registered subfamily abbr \"H\"\n0.560000   0x3f0f5c29          \"Heavy Face\"\n0.560000   0x3f0f5c29          \"Heavy\"\n0.560000   0x3f0f5c29          \"HeavyFace\"\n0.560000   0x3f0f5c29          \"heavy face\"\n0.560000   0x3f0f5c29          \"heavy\"\n0.560000   0x3f0f5c29          \"heavy\"\n0.560000   0x3f0f5c29          \"heavyface\"\n0.560000   0x3f0f5c29          panose 9\n0.620000   0x3f1eb852          \"Black\"\n0.620000   0x3f1eb852          \"Super\"\n0.620000   0x3f1eb852          \"W9\"\n0.620000   0x3f1eb852          \"black\"\n0.620000   0x3f1eb852          \"black\"\n0.620000   0x3f1eb852          \"super\"\n0.620000   0x3f1eb852          \"w9\"\n0.620000   0x3f1eb852          registered OS2 weights 9, 801 - 900\n0.620000   0x3f1eb852          panose 10\n0.700000   0x3f333333          registered subfamily abbr \"U\"\n0.700000   0x3f333333          \"Ultra Bold\"\n0.700000   0x3f333333          \"UltraBold\"\n0.700000   0x3f333333          \"ultra bold\"\n0.700000   0x3f333333          \"ultrabold\"\n0.750000   0x3f400000          \"Fat\"\n0.750000   0x3f400000          \"Ultra Black\"\n0.750000   0x3f400000          \"UltraBlack\"\n0.750000   0x3f400000          \"fat\"\n0.750000   0x3f400000          \"ultra black\"\n0.750000   0x3f400000          \"ultrablack\"\n0.750000   0x3f400000          registered OS2 weights 901 - 950\n0.800000   0x3f4ccccd          \"Ext Black\"\n0.800000   0x3f4ccccd          \"ExtraBlack\"\n0.800000   0x3f4ccccd          \"Nord\"\n0.800000   0x3f4ccccd          \"Poster\"\n0.800000   0x3f4ccccd          registered subfamily abbr \"UH\"\n0.800000   0x3f4ccccd          \"extrablack\"\n0.800000   0x3f4ccccd          \"nord\"\n0.800000   0x3f4ccccd          \"poster\"\n0.800000   0x3f4ccccd          registered OS2 weights 951 - 999\n0.800000   0x3f4ccccd          panose 11\n0.850000   0x3f59999a          \"Obese\"\n0.850000   0x3f59999a          \"obese\"\n"
  },
  {
    "path": "_doc/export/ctweightscombined",
    "content": "-0.100000\n\tpostscript 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)\n\n-0.200000\n\tsubfamily abbreviation \"EL\" (???????????)\n\tstyle string contains \"Semi Light\"\n\tstyle string contains \"SemiLight\"\n\n-0.230000\n\tstyle string contains \"Book\"\n\tstyle string contains \"W1\" (registered fonts only; probably a typo and -0.7 was expected instead)\n\tstyle string contains \"W3\"\n\tpanose 5\n\n-0.400000\n\tsubfamily abbreviation \"L\"\n\tstyle string contains \"Light\"\n\tstyle string contains \"Lite\"\n\tATSD style \"light\"\n\tOS2 weights 3, 201 - 300\n\tpanose 3\n\n-0.500000\n\tstyle string contains \"Ext Light\"\n\tstyle string contains \"Extra Light\"\n\tstyle string contains \"ExtraLight\"\n\tstyle string contains \"Thin\"\n\tstyle string contains \"W2\"\n\tOS2 weights 2, 101 - 200\n\tpanose 2\n\n-0.600000\n\tATSD style \"thin\"\n\n-0.700000\n\tstyle string contains \"hairline\"\n\tstyle string contains \"w1\"\n\n-0.800000\n\tstyle string contains \"Ext Thin\"\n\tstyle string contains \"Extra Thin\"\n\tstyle string contains \"HairLine\"\n\tstyle string contains \"Ultra Light\"\n\tstyle string contains \"Ultra Thin\"\n\tstyle string contains \"UltraLight\"\n\tATSD style \"ulight\"\n\tOS2 weights 1, 10 - 100\n\n0.000000\n\tostscript name \".Keyboard\"\n\tpostscript name \".LucidaGrandeUI\"\n\tpostscript name \"LucidaGrande\"\n\tsubfamily abbreviation \"R\"\n\tpostscript name \"TimesNewRomanPSMT\"\n\tstyle string contains \"W4\"\n\tATSD style \"reg\"\n\tOS2 weights 4, 301 - 400\n\tdefault\n\n0.230000\n\tOS2 weights 5, 401 - 500\n\tsubfamily abbreviation \"M\"\n\tstyle string contains \"Medium\"\n\tstyle string contains \"W5\"\n\tATSD style \"med\"\n\tpanose 6\n\n0.240000\n\tpostscript name \"STHeiti\"\n\n0.250000\n\tstyle string contains \"Demi Bold\"\n\tstyle string contains \"Demi\"\n\tstyle string contains \"DemiBold\"\n\tOS2 weights 6, 501 - 600\n\tpanose 7\n\n0.300000\n\tsubfamily abbreviation \"SB\"\n\tstyle string contains \"Semi Bold\"\n\tstyle string contains \"Semi\"\n\tstyle string contains \"SemiBold\"\n\tstyle string contains \"W6\"\n\tATSD style \"semi\"\n\n0.400000\n\tsubfamily abbreviation \"B\"\n\tstyle string contains \"Bold\"\n\tATSD style \"bold\"\n\t('head' table byte 0x2D) & 1 != 0\n\tOS2 weights 7, 601 - 700\n\tpanose 8\n\n0.440000\n\tstyle string contains \"W7\"\n\n0.500000\n\tsubfamily abbreviation \"EB\"\n\tstyle string contains \"Ext Bold\"\n\tstyle string contains \"Extra Bold\"\n\tstyle string contains \"Extra\"\n\tstyle string contains \"ExtraBold\"\n\tstyle string contains \"Ultra\"\n\tOS2 weights 8, 701 - 800\n\n0.540000\n\tstyle string contains \"W8\"\n\n0.560000\n\tsubfamily abbreviation \"H\"\n\tstyle string contains \"Heavy Face\"\n\tstyle string contains \"Heavy\"\n\tstyle string contains \"HeavyFace\"\n\tATSD style \"heavy\"\n\tpanose 9\n\n0.620000\n\tstyle string contains \"Black\"\n\tstyle string contains \"Super\"\n\tstyle string contains \"W9\"\n\tATSD style \"black\"\n\tOS2 weights 9, 801 - 900\n\tpanose 10\n\n0.700000\n\tsubfamily abbreviation \"U\"\n\tstyle string contains \"Ultra Bold\"\n\tstyle string contains \"UltraBold\"\n\n0.750000\n\tstyle string contains \"Fat\"\n\tstyle string contains \"Ultra Black\"\n\tstyle string contains \"UltraBlack\"\n\tOS2 weights 901 - 950\n\n0.800000\n\tstyle string contains \"Ext Black\"\n\tstyle string contains \"ExtraBlack\"\n\tstyle string contains \"Nord\"\n\tstyle string contains \"Poster\"\n\tsubfamily abbreviation \"UH\"\n\tOS2 weights 951 - 999\n\tpanose 11\n\n0.850000\n\tstyle string contains \"Obese\"\n"
  },
  {
    "path": "_doc/export/ctweightsprocessed",
    "content": "-0.100000  0xbdcccccd          \"STXihei\"\n-0.100000  0xbfb999999999999a  \"STXihei\"\n-0.200000  0xbe4ccccd          \"EL\"\n-0.200000  0xbe4ccccd          \"Semi Light\"\n-0.200000  0xbe4ccccd          \"SemiLight\"\n-0.200000  0xbe4ccccd          \"semi light\"\n-0.200000  0xbe4ccccd          \"semilight\"\n-0.230000  0xbe6b851f          \"Book\"\n-0.230000  0xbe6b851f          \"W1\"\n-0.230000  0xbe6b851f          \"W3\"\n-0.230000  0xbe6b851f          \"book\"\n-0.230000  0xbe6b851f          \"w3\"\n-0.230000  0xbe6b851f          panose 5\n-0.400000  0xbecccccd          \"L\"\n-0.400000  0xbecccccd          \"Light\"\n-0.400000  0xbecccccd          \"Lite\"\n-0.400000  0xbecccccd          \"light\"\n-0.400000  0xbecccccd          \"light\"\n-0.400000  0xbecccccd          \"lite\"\n-0.400000  0xbecccccd          3, 201 - 300\n-0.400000  0xbecccccd          panose 3\n-0.500000  0xbf000000          \"Ext Light\"\n-0.500000  0xbf000000          \"Extra Light\"\n-0.500000  0xbf000000          \"ExtraLight\"\n-0.500000  0xbf000000          \"Thin\"\n-0.500000  0xbf000000          \"W2\"\n-0.500000  0xbf000000          \"extra light\"\n-0.500000  0xbf000000          \"extralight\"\n-0.500000  0xbf000000          \"thin\"\n-0.500000  0xbf000000          \"w2\"\n-0.500000  0xbf000000          2, 101 - 200\n-0.500000  0xbf000000          panose 2\n-0.600000  0xbf19999a          \"thin\"\n-0.700000  0xbf333333          \"hairline\"\n-0.700000  0xbf333333          \"w1\"\n-0.800000  0xbf4ccccd          \"Ext Thin\"\n-0.800000  0xbf4ccccd          \"Extra Thin\"\n-0.800000  0xbf4ccccd          \"HairLine\"\n-0.800000  0xbf4ccccd          \"Ultra Light\"\n-0.800000  0xbf4ccccd          \"Ultra Thin\"\n-0.800000  0xbf4ccccd          \"UltraLight\"\n-0.800000  0xbf4ccccd          \"ulight\"\n-0.800000  0xbf4ccccd          \"ultra light\"\n-0.800000  0xbf4ccccd          \"ultralight\"\n-0.800000  0xbf4ccccd          1, 10 - 100\n0.000000   0x0                 \".Keyboard\"\n0.000000   0x0                 \".LucidaGrandeUI\"\n0.000000   0x0                 \".LucidaGrandeUI\"\n0.000000   0x0                 \"LucidaGrande\"\n0.000000   0x0                 \"LucidaGrande\"\n0.000000   0x0                 \"R\"\n0.000000   0x0                 \"TimesNewRomanPSMT\"\n0.000000   0x0                 \"TimesNewRomanPSMT\"\n0.000000   0x0                 \"W4\"\n0.000000   0x0                 \"reg\"\n0.000000   0x0                 \"w4\"\n0.000000   0x0                 4, 301 - 400\n0.000000   0x0                 default\n0.230000   0x3e6b851f          5, 401 - 500\n0.230000   0x3e6b851f          \"M\"\n0.230000   0x3e6b851f          \"Medium\"\n0.230000   0x3e6b851f          \"W5\"\n0.230000   0x3e6b851f          \"med\"\n0.230000   0x3e6b851f          \"medium\"\n0.230000   0x3e6b851f          \"w5\"\n0.230000   0x3e6b851f          panose 6\n0.240000   0x3e75c28f          \"STHeiti\"\n0.240000   0x3fceb851eb851eb8  \"STHeiti\"\n0.250000   0x3e800000          \"Demi Bold\"\n0.250000   0x3e800000          \"Demi\"\n0.250000   0x3e800000          \"DemiBold\"\n0.250000   0x3e800000          \"demi bold\"\n0.250000   0x3e800000          \"demi\"\n0.250000   0x3e800000          \"demibold\"\n0.250000   0x3e800000          6, 501 - 600\n0.250000   0x3e800000          panose 7\n0.300000   0x3e99999a          \"SB\"\n0.300000   0x3e99999a          \"Semi Bold\"\n0.300000   0x3e99999a          \"Semi\"\n0.300000   0x3e99999a          \"SemiBold\"\n0.300000   0x3e99999a          \"W6\"\n0.300000   0x3e99999a          \"semi bold\"\n0.300000   0x3e99999a          \"semi\"\n0.300000   0x3e99999a          \"semi\"\n0.300000   0x3e99999a          \"semibold\"\n0.300000   0x3e99999a          \"w6\"\n0.400000   0x3ecccccd          \"B\"\n0.400000   0x3ecccccd          \"Bold\"\n0.400000   0x3ecccccd          \"bold\"\n0.400000   0x3ecccccd          \"bold\"\n0.400000   0x3ecccccd          'head'[0x2D] & 1\n0.400000   0x3ecccccd          7, 601 - 700\n0.400000   0x3ecccccd          panose 8\n0.440000   0x3ee147ae          \"W7\"\n0.440000   0x3ee147ae          \"w7\"\n0.500000   0x3f000000          \"EB\"\n0.500000   0x3f000000          \"Ext Bold\"\n0.500000   0x3f000000          \"Extra Bold\"\n0.500000   0x3f000000          \"Extra\"\n0.500000   0x3f000000          \"ExtraBold\"\n0.500000   0x3f000000          \"Ultra\"\n0.500000   0x3f000000          \"extra bold\"\n0.500000   0x3f000000          \"extra\"\n0.500000   0x3f000000          \"extrabold\"\n0.500000   0x3f000000          8, 701 - 800\n0.540000   0x3f0a3d71          \"W8\"\n0.540000   0x3f0a3d71          \"w8\"\n0.560000   0x3f0f5c29          \"H\"\n0.560000   0x3f0f5c29          \"Heavy Face\"\n0.560000   0x3f0f5c29          \"Heavy\"\n0.560000   0x3f0f5c29          \"HeavyFace\"\n0.560000   0x3f0f5c29          \"heavy face\"\n0.560000   0x3f0f5c29          \"heavy\"\n0.560000   0x3f0f5c29          \"heavy\"\n0.560000   0x3f0f5c29          \"heavyface\"\n0.560000   0x3f0f5c29          panose 9\n0.620000   0x3f1eb852          \"Black\"\n0.620000   0x3f1eb852          \"Super\"\n0.620000   0x3f1eb852          \"W9\"\n0.620000   0x3f1eb852          \"black\"\n0.620000   0x3f1eb852          \"black\"\n0.620000   0x3f1eb852          \"super\"\n0.620000   0x3f1eb852          \"w9\"\n0.620000   0x3f1eb852          9, 801 - 900\n0.620000   0x3f1eb852          panose 10\n0.700000   0x3f333333          \"U\"\n0.700000   0x3f333333          \"Ultra Bold\"\n0.700000   0x3f333333          \"UltraBold\"\n0.700000   0x3f333333          \"ultra bold\"\n0.700000   0x3f333333          \"ultrabold\"\n0.750000   0x3f400000          \"Fat\"\n0.750000   0x3f400000          \"Ultra Black\"\n0.750000   0x3f400000          \"UltraBlack\"\n0.750000   0x3f400000          \"fat\"\n0.750000   0x3f400000          \"ultra black\"\n0.750000   0x3f400000          \"ultrablack\"\n0.750000   0x3f400000          901 - 950\n0.800000   0x3f4ccccd          \"Ext Black\"\n0.800000   0x3f4ccccd          \"ExtraBlack\"\n0.800000   0x3f4ccccd          \"Nord\"\n0.800000   0x3f4ccccd          \"Poster\"\n0.800000   0x3f4ccccd          \"UH\"\n0.800000   0x3f4ccccd          \"extrablack\"\n0.800000   0x3f4ccccd          \"nord\"\n0.800000   0x3f4ccccd          \"poster\"\n0.800000   0x3f4ccccd          951 - 999\n0.800000   0x3f4ccccd          panose 11\n0.850000   0x3f59999a          \"Obese\"\n0.850000   0x3f59999a          \"obese\"\n"
  },
  {
    "path": "_doc/export/ctweightsraw",
    "content": "0.000000\t0x0\t\"reg\"\n0.300000\t0x3e99999a\t\"semi\"\n0.400000\t0x3ecccccd\t\"bold\"\n-0.400000\t0xbecccccd\t\"light\"\n0.230000\t0x3e6b851f\t\"med\"\n0.560000\t0x3f0f5c29\t\"heavy\"\n0.620000\t0x3f1eb852\t\"black\"\n-0.600000\t0xbf19999a\t\"thin\"\n-0.800000\t0xbf4ccccd\t\"ulight\"\n0.000000\t0x0\t\"LucidaGrande\"\n0.000000\t0x0\t\".LucidaGrandeUI\"\n0.000000\t0x0\t\".Keyboard\"\n0.240000\t0x3e75c28f\t\"STHeiti\"\n-0.100000\t0xbdcccccd\t\"STXihei\"\n0.000000\t0x0\t\"TimesNewRomanPSMT\"\n-0.800000\t0xbf4ccccd\t\"Ultra Light\"\n0.750000\t0x3f400000\t\"Ultra Black\"\n-0.500000\t0xbf000000\t\"Extra Light\"\n0.750000\t0x3f400000\t\"UltraBlack\"\n0.800000\t0x3f4ccccd\t\"ExtraBlack\"\n-0.800000\t0xbf4ccccd\t\"UltraLight\"\n-0.500000\t0xbf000000\t\"ExtraLight\"\n-0.800000\t0xbf4ccccd\t\"Ultra Thin\"\n-0.800000\t0xbf4ccccd\t\"Extra Thin\"\n0.560000\t0x3f0f5c29\t\"Heavy Face\"\n-0.200000\t0xbe4ccccd\t\"Semi Light\"\n0.500000\t0x3f000000\t\"Extra Bold\"\n0.700000\t0x3f333333\t\"Ultra Bold\"\n0.560000\t0x3f0f5c29\t\"HeavyFace\"\n0.500000\t0x3f000000\t\"ExtraBold\"\n0.700000\t0x3f333333\t\"UltraBold\"\n0.800000\t0x3f4ccccd\t\"Ext Black\"\n-0.200000\t0xbe4ccccd\t\"SemiLight\"\n0.250000\t0x3e800000\t\"Demi Bold\"\n0.300000\t0x3e99999a\t\"Semi Bold\"\n-0.500000\t0xbf000000\t\"Ext Light\"\n0.500000\t0x3f000000\t\"Ext Bold\"\n0.250000\t0x3e800000\t\"DemiBold\"\n0.300000\t0x3e99999a\t\"SemiBold\"\n-0.800000\t0xbf4ccccd\t\"HairLine\"\n-0.800000\t0xbf4ccccd\t\"Ext Thin\"\n0.230000\t0x3e6b851f\t\"Medium\"\n0.800000\t0x3f4ccccd\t\"Poster\"\n-0.400000\t0xbecccccd\t\"Light\"\n0.500000\t0x3f000000\t\"Ultra\"\n0.560000\t0x3f0f5c29\t\"Heavy\"\n0.500000\t0x3f000000\t\"Extra\"\n0.620000\t0x3f1eb852\t\"Black\"\n0.620000\t0x3f1eb852\t\"Super\"\n0.850000\t0x3f59999a\t\"Obese\"\n-0.400000\t0xbecccccd\t\"Lite\"\n-0.230000\t0xbe6b851f\t\"Book\"\n0.250000\t0x3e800000\t\"Demi\"\n0.300000\t0x3e99999a\t\"Semi\"\n-0.500000\t0xbf000000\t\"Thin\"\n0.400000\t0x3ecccccd\t\"Bold\"\n0.800000\t0x3f4ccccd\t\"Nord\"\n0.750000\t0x3f400000\t\"Fat\"\n-0.230000\t0xbe6b851f\t\"W1\"\n-0.500000\t0xbf000000\t\"W2\"\n-0.230000\t0xbe6b851f\t\"W3\"\n0.000000\t0x0\t\"W4\"\n0.230000\t0x3e6b851f\t\"W5\"\n0.300000\t0x3e99999a\t\"W6\"\n0.440000\t0x3ee147ae\t\"W7\"\n0.540000\t0x3f0a3d71\t\"W8\"\n0.620000\t0x3f1eb852\t\"W9\"\n-0.800000\t0xbf4ccccd\t1, 10 - 100\n-0.500000\t0xbf000000\t2, 101 - 200\n-0.400000\t0xbecccccd\t3, 201 - 300\n0.000000\t0x0\t4, 301 - 400\n0.230000\t0x3e6b851f\t5, 401 - 500\n0.250000\t0x3e800000\t6, 501 - 600\n0.400000\t0x3ecccccd\t7, 601 - 700\n0.500000\t0x3f000000\t8, 701 - 800\n0.620000\t0x3f1eb852\t9, 801 - 900\n0.750000\t0x3f400000\t901 - 950\n0.800000\t0x3f4ccccd\t951 - 999\n-0.500000\t0xbf000000\tpanose 2\n-0.400000\t0xbecccccd\tpanose 3\n-0.230000\t0xbe6b851f\tpanose 5\n0.230000\t0x3e6b851f\tpanose 6\n0.250000\t0x3e800000\tpanose 7\n0.400000\t0x3ecccccd\tpanose 8\n0.560000\t0x3f0f5c29\tpanose 9\n0.620000\t0x3f1eb852\tpanose 10\n0.800000\t0x3f4ccccd\tpanose 11\n0.400000\t0x3ecccccd\t'head'[0x2D] & 1\n-0.200000\t0xbe4ccccd\t\"EL\"\n0.500000\t0x3f000000\t\"EB\"\n0.300000\t0x3e99999a\t\"SB\"\n0.800000\t0x3f4ccccd\t\"UH\"\n0.700000\t0x3f333333\t\"U\"\n-0.400000\t0xbecccccd\t\"L\"\n0.560000\t0x3f0f5c29\t\"H\"\n0.400000\t0x3ecccccd\t\"B\"\n0.230000\t0x3e6b851f\t\"M\"\n0.000000\t0x0\t\"R\"\n0.000000\t0x0\tdefault\n0.000000\t0x0\t\"LucidaGrande\"\n0.000000\t0x0\t\".LucidaGrandeUI\"\n0.240000\t0x3fceb851eb851eb8\t\"STHeiti\"\n-0.100000\t0xbfb999999999999a\t\"STXihei\"\n0.000000\t0x0\t\"TimesNewRomanPSMT\"\n-0.800000\t0xbf4ccccd\t\"ultra light\"\n0.750000\t0x3f400000\t\"ultra black\"\n-0.500000\t0xbf000000\t\"extra light\"\n-0.800000\t0xbf4ccccd\t\"ultralight\"\n0.750000\t0x3f400000\t\"ultrablack\"\n0.800000\t0x3f4ccccd\t\"extrablack\"\n-0.500000\t0xbf000000\t\"extralight\"\n0.560000\t0x3f0f5c29\t\"heavy face\"\n-0.200000\t0xbe4ccccd\t\"semi light\"\n0.500000\t0x3f000000\t\"extra bold\"\n0.700000\t0x3f333333\t\"ultra bold\"\n0.560000\t0x3f0f5c29\t\"heavyface\"\n0.500000\t0x3f000000\t\"extrabold\"\n0.700000\t0x3f333333\t\"ultrabold\"\n-0.200000\t0xbe4ccccd\t\"semilight\"\n0.250000\t0x3e800000\t\"demi bold\"\n0.300000\t0x3e99999a\t\"semi bold\"\n0.250000\t0x3e800000\t\"demibold\"\n0.300000\t0x3e99999a\t\"semibold\"\n-0.700000\t0xbf333333\t\"hairline\"\n0.230000\t0x3e6b851f\t\"medium\"\n0.800000\t0x3f4ccccd\t\"poster\"\n-0.400000\t0xbecccccd\t\"light\"\n0.560000\t0x3f0f5c29\t\"heavy\"\n0.500000\t0x3f000000\t\"extra\"\n0.620000\t0x3f1eb852\t\"black\"\n0.620000\t0x3f1eb852\t\"super\"\n0.850000\t0x3f59999a\t\"obese\"\n-0.400000\t0xbecccccd\t\"lite\"\n-0.230000\t0xbe6b851f\t\"book\"\n0.250000\t0x3e800000\t\"demi\"\n0.300000\t0x3e99999a\t\"semi\"\n-0.500000\t0xbf000000\t\"thin\"\n0.400000\t0x3ecccccd\t\"bold\"\n0.800000\t0x3f4ccccd\t\"nord\"\n0.750000\t0x3f400000\t\"fat\"\n-0.700000\t0xbf333333\t\"w1\"\n-0.500000\t0xbf000000\t\"w2\"\n-0.230000\t0xbe6b851f\t\"w3\"\n0.000000\t0x0\t\"w4\"\n0.230000\t0x3e6b851f\t\"w5\"\n0.300000\t0x3e99999a\t\"w6\"\n0.440000\t0x3ee147ae\t\"w7\"\n0.540000\t0x3f0a3d71\t\"w8\"\n0.620000\t0x3f1eb852\t\"w9\"\n"
  },
  {
    "path": "_doc/export/ctweightvalues",
    "content": "registered font, preexisting metadata weight\n\"reg\":\tfloat32as(0.000000, 0x0)\n\"semi\":\tfloat32as(0.300000, 0x3e99999a)\n\"bold\":\tfloat32as(0.400000, 0x3ecccccd)\n\"light\":\tfloat32as(-0.400000, 0xbecccccd)\n\"med\":\tfloat32as(0.230000, 0x3e6b851f)\n\"heavy\":\tfloat32as(0.560000, 0x3f0f5c29)\n\"black\":\tfloat32as(0.620000, 0x3f1eb852)\n\"thin\":\tfloat32as(-0.600000, 0xbf19999a)\n\"ulight\":\tfloat32as(-0.800000, 0xbf4ccccd)\n\nregistered font, postscript name (probably only for TrueType and OpenType) special cases, overrides the above\n\"LucidaGrande\":\t\t\tfloat32as(0.000000, 0x0)\n\".LucidaGrandeUI\":\t\t\tfloat32as(0.000000, 0x0)\n\".Keyboard\":\t\t\t\tfloat32as(0.000000, 0x0)\n\"STHeiti\":\t\t\t\t\tfloat32as(0.240000, 0x3e75c28f)\n\"STXihei\":\t\t\t\t\tfloat32as(-0.100000, 0xbdcccccd)\n\"TimesNewRomanPSMT\":\t\tfloat32as(0.000000, 0x0)\n\nregistered 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)\n\"Ultra Light\":\tfloat32as(-0.800000, 0xbf4ccccd)\n\"Ultra Black\":\tfloat32as(0.750000, 0x3f400000)\n\"Extra Light\":\tfloat32as(-0.500000, 0xbf000000)\n\"UltraBlack\":\tfloat32as(0.750000, 0x3f400000)\n\"ExtraBlack\":\tfloat32as(0.800000, 0x3f4ccccd)\n\"UltraLight\":\tfloat32as(-0.800000, 0xbf4ccccd)\n\"ExtraLight\":\tfloat32as(-0.500000, 0xbf000000)\n\"Ultra Thin\":\tfloat32as(-0.800000, 0xbf4ccccd)\n\"Extra Thin\":\tfloat32as(-0.800000, 0xbf4ccccd)\n\"Heavy Face\":\tfloat32as(0.560000, 0x3f0f5c29)\n\"Semi Light\":\tfloat32as(-0.200000, 0xbe4ccccd)\n\"Extra Bold\":\tfloat32as(0.500000, 0x3f000000)\n\"Ultra Bold\":\tfloat32as(0.700000, 0x3f333333)\n\"HeavyFace\":\tfloat32as(0.560000, 0x3f0f5c29)\n\"ExtraBold\":\tfloat32as(0.500000, 0x3f000000)\n\"UltraBold\":\tfloat32as(0.700000, 0x3f333333)\n\"Ext Black\":\tfloat32as(0.800000, 0x3f4ccccd)\n\"SemiLight\":\tfloat32as(-0.200000, 0xbe4ccccd)\n\"Demi Bold\":\tfloat32as(0.250000, 0x3e800000)\n\"Semi Bold\":\tfloat32as(0.300000, 0x3e99999a)\n\"Ext Light\":\tfloat32as(-0.500000, 0xbf000000)\n\"Ext Bold\":\tfloat32as(0.500000, 0x3f000000)\n\"DemiBold\":\tfloat32as(0.250000, 0x3e800000)\n\"SemiBold\":\tfloat32as(0.300000, 0x3e99999a)\n\"HairLine\":\tfloat32as(-0.800000, 0xbf4ccccd)\n\"Ext Thin\":\tfloat32as(-0.800000, 0xbf4ccccd)\n\"Medium\":\tfloat32as(0.230000, 0x3e6b851f)\n\"Poster\":\t\tfloat32as(0.800000, 0x3f4ccccd)\n\"Light\":\t\tfloat32as(-0.400000, 0xbecccccd)\n\"Ultra\":\t\tfloat32as(0.500000, 0x3f000000)\n\"Heavy\":\t\tfloat32as(0.560000, 0x3f0f5c29)\n\"Extra\":\t\tfloat32as(0.500000, 0x3f000000)\n\"Black\":\t\tfloat32as(0.620000, 0x3f1eb852)\n\"Super\":\t\tfloat32as(0.620000, 0x3f1eb852)\n\"Obese\":\t\tfloat32as(0.850000, 0x3f59999a)\n\"Lite\":\t\tfloat32as(-0.400000, 0xbecccccd)\n\"Book\":\t\tfloat32as(-0.230000, 0xbe6b851f)\n\"Demi\":\t\tfloat32as(0.250000, 0x3e800000)\n\"Semi\":\t\tfloat32as(0.300000, 0x3e99999a)\n\"Thin\":\t\tfloat32as(-0.500000, 0xbf000000)\n\"Bold\":\t\tfloat32as(0.400000, 0x3ecccccd)\n\"Nord\":\t\tfloat32as(0.800000, 0x3f4ccccd)\n\"Fat\":\t\tfloat32as(0.750000, 0x3f400000)\n\"W1\":\t\tfloat32as(-0.230000, 0xbe6b851f)\n\"W2\":\t\tfloat32as(-0.500000, 0xbf000000)\n\"W3\":\t\tfloat32as(-0.230000, 0xbe6b851f)\n\"W4\":\t\tfloat32as(0.000000, 0x0)\n\"W5\":\t\tfloat32as(0.230000, 0x3e6b851f)\n\"W6\":\t\tfloat32as(0.300000, 0x3e99999a)\n\"W7\":\t\tfloat32as(0.440000, 0x3ee147ae)\n\"W8\":\t\tfloat32as(0.540000, 0x3f0a3d71)\n\"W9\":\t\tfloat32as(0.620000, 0x3f1eb852)\n\nregistered font, OS2 weights; table length >= 78\n1, 10 - 100:\tfloat32as(-0.800000, 0xbf4ccccd)\n2, 101 - 200:\tfloat32as(-0.500000, 0xbf000000)\n3, 201 - 300:\tfloat32as(-0.400000, 0xbecccccd)\n4, 301 - 400:\tfloat32as(0.000000, 0x0)\n5, 401 - 500:\tfloat32as(0.230000, 0x3e6b851f)\n6, 501 - 600:\tfloat32as(0.250000, 0x3e800000)\n7, 601 - 700:\tfloat32as(0.400000, 0x3ecccccd)\n8, 701 - 800:\tfloat32as(0.500000, 0x3f000000)\n9, 801 - 900:\tfloat32as(0.620000, 0x3f1eb852)\n901 - 950:\tfloat32as(0.750000, 0x3f400000)\n951 - 999:\tfloat32as(0.800000, 0x3f4ccccd)\n0, 1000:\t\tpanose\npanose 2:\t\tfloat32as(-0.500000, 0xbf000000)\npanose 3:\t\tfloat32as(-0.400000, 0xbecccccd)\npanose 4:\t\t!!!! see note\tshould be float32as(-0.300000, 0xbe99999a) but is treated as invalid instead due to returning false instead of true\npanose 5:\t\tfloat32as(-0.230000, 0xbe6b851f)\npanose 6:\t\tfloat32as(0.230000, 0x3e6b851f)\npanose 7:\t\tfloat32as(0.250000, 0x3e800000)\npanose 8:\t\tfloat32as(0.400000, 0x3ecccccd)\npanose 9:\t\tfloat32as(0.560000, 0x3f0f5c29)\npanose 10:\tfloat32as(0.620000, 0x3f1eb852)\npanose 11:\tfloat32as(0.800000, 0x3f4ccccd)\n\nregistered font, head table, low bit of byte 0x2D\n'head'[0x2D] & 1:\t\tfloat32as(0.400000, 0x3ecccccd)\n\nregistered font, abbreviated weight glossary, checks for (possibly in TrueType and OpenType only) in order: preferred subfamily, family; case-insensitive strict comparison\n\"EL\":\t\tfloat32as(-0.200000, 0xbe4ccccd)\n\"EB\":\t\tfloat32as(0.500000, 0x3f000000)\n\"SB\":\t\tfloat32as(0.300000, 0x3e99999a)\n\"UH\":\tfloat32as(0.800000, 0x3f4ccccd)\n\"U\":\t\tfloat32as(0.700000, 0x3f333333)\n\"L\":\t\tfloat32as(-0.400000, 0xbecccccd)\n\"H\":\t\tfloat32as(0.560000, 0x3f0f5c29)\n\"B\":\t\tfloat32as(0.400000, 0x3ecccccd)\n\"M\":\t\tfloat32as(0.230000, 0x3e6b851f)\n\"R\":\t\tfloat32as(0.000000, 0x0)\n\nregistered font\ndefault:\tfloat32as(0.000000, 0x0)\n\n// based on CoreText dylib's __Z13WeightOfClasst — WeightOfClass(unsigned short)\nfunc CoreText_WeightOfClass(usWeightClass uint16) float64 {\n\tif usWeightClass >= 11 {\n\t\t// do nothing; we are preserving the original asm comparisons\n\t\t// and yes, this one is 11, but the one above is 10\n\t} else {\n\t\tusWeightClass *= 100\n\t}\n\n\t// figure out what two floats our weight will be between\n\ti := usWeightClass / 100\n\tj := i + 1\n\tif j > 10 {\n\t\tj = 10\n\t}\n\tb := float64(i * 100)\n\tc := float64(j * 100)\n\n\ta := float64(0)\n\tif b != c {\n\t\ta = float64(usWeightClass)\n\t\ta -= b\n\t\tc -= b\n\t\ta /= c\n\t}\n\tscales := []float32{\n\t\tfloat32as(-1.000000, 0xbf800000),\n\t\tfloat32as(-0.700000, 0xbf333333),\n\t\tfloat32as(-0.500000, 0xbf000000),\n\t\tfloat32as(-0.230000, 0xbe6b851f),\n\t\tfloat32as(0.000000, 0x0),\n\t\tfloat32as(0.200000, 0x3e4ccccd),\n\t\tfloat32as(0.300000, 0x3e99999a),\n\t\tfloat32as(0.400000, 0x3ecccccd),\n\t\tfloat32as(0.600000, 0x3f19999a),\n\t\tfloat32as(0.800000, 0x3f4ccccd),\n\t\tfloat32as(1.000000, 0x3f800000),\n\t}\n\tc = float64(scale[i])\n\tb = float64[scale[j])\n\treturn fma(a, b, c)\n}\n\nunregistered font: kCTFontPostScriptNameKey defaults\n\"LucidaGrande\":\t\t\tfloat64as(0.000000, 0x0)\n\".LucidaGrandeUI\":\t\t\tfloat64as(0.000000, 0x0)\n\"STHeiti\":\t\t\t\t\tfloat64as(0.240000, 0x3fceb851eb851eb8)\n\"STXihei\":\t\t\t\t\tfloat64as(-0.100000, 0xbfb999999999999a)\n\"TimesNewRomanPSMT\":\t\tfloat64as(0.000000, 0x0)\n\n\n\tos2table := f.Table('OS/2')\n\tif os2table != nil {\n\t\tif !hasWeight {\n\t\t\tvar usWeightClass uint16\n\n\t\t\tvalid := false\n\t\t\tif os2table.Len() > 77 {\n\t\t\t\tb := os2table.Bytes()\n\t\t\t\tusWeightClass = uint16be(b[4:6])\n\t\t\t\tif usWeightClass > 1000 {\n\t\t\t\t\tweight = 0\n\t\t\t\t\thasWeight = false\n\t\t\t\t} else {\n\t\t\t\t\tvalid = true\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tusWeightClass = 0\n\t\t\t\tvalid = true\n\t\t\t}\n\t\t\tif valid {\n\t\t\t\tweight = CoreText_WeightOfClass(usWeightClass)\n\t\t\t\thasWeight = true\n\t\t\t}\n\t\t}\n\t}\n\n\nunregistered font, style glossary, checks against kCTFontSubFamilyNameKey, kCTFontFullNameKey, kCTFontFamilyNameKey; case-insensitive (folded by Unicode rules) strstr()\n\"ultra light\":\tfloat32as(-0.800000, 0xbf4ccccd)\n\"ultra black\":\tfloat32as(0.750000, 0x3f400000)\n\"extra light\":\tfloat32as(-0.500000, 0xbf000000)\n\"ultralight\":\tfloat32as(-0.800000, 0xbf4ccccd)\n\"ultrablack\":\tfloat32as(0.750000, 0x3f400000)\n\"extrablack\":\tfloat32as(0.800000, 0x3f4ccccd)\n\"extralight\":\tfloat32as(-0.500000, 0xbf000000)\n\"heavy face\":\tfloat32as(0.560000, 0x3f0f5c29)\n\"semi light\":\tfloat32as(-0.200000, 0xbe4ccccd)\n\"extra bold\":\tfloat32as(0.500000, 0x3f000000)\n\"ultra bold\":\tfloat32as(0.700000, 0x3f333333)\n\"heavyface\":\tfloat32as(0.560000, 0x3f0f5c29)\n\"extrabold\":\tfloat32as(0.500000, 0x3f000000)\n\"ultrabold\":\tfloat32as(0.700000, 0x3f333333)\n\"semilight\":\tfloat32as(-0.200000, 0xbe4ccccd)\n\"demi bold\":\tfloat32as(0.250000, 0x3e800000)\n\"semi bold\":\tfloat32as(0.300000, 0x3e99999a)\n\"demibold\":\tfloat32as(0.250000, 0x3e800000)\n\"semibold\":\tfloat32as(0.300000, 0x3e99999a)\n\"hairline\":\t\tfloat32as(-0.700000, 0xbf333333)\n\"medium\":\tfloat32as(0.230000, 0x3e6b851f)\n\"poster\":\t\tfloat32as(0.800000, 0x3f4ccccd)\n\"light\":\t\tfloat32as(-0.400000, 0xbecccccd)\n\"heavy\":\t\tfloat32as(0.560000, 0x3f0f5c29)\n\"extra\":\t\tfloat32as(0.500000, 0x3f000000)\n\"black\":\t\tfloat32as(0.620000, 0x3f1eb852)\n\"super\":\t\tfloat32as(0.620000, 0x3f1eb852)\n\"obese\":\t\tfloat32as(0.850000, 0x3f59999a)\n\"lite\":\t\tfloat32as(-0.400000, 0xbecccccd)\n\"book\":\t\tfloat32as(-0.230000, 0xbe6b851f)\n\"demi\":\t\tfloat32as(0.250000, 0x3e800000)\n\"semi\":\t\tfloat32as(0.300000, 0x3e99999a)\n\"thin\":\t\tfloat32as(-0.500000, 0xbf000000)\n\"bold\":\t\tfloat32as(0.400000, 0x3ecccccd)\n\"nord\":\t\tfloat32as(0.800000, 0x3f4ccccd)\n\"fat\":\t\t\tfloat32as(0.750000, 0x3f400000)\n\"w1\":\t\tfloat32as(-0.700000, 0xbf333333)\n\"w2\":\t\tfloat32as(-0.500000, 0xbf000000)\n\"w3\":\t\tfloat32as(-0.230000, 0xbe6b851f)\n\"w4\":\t\tfloat32as(0.000000, 0x0)\n\"w5\":\t\tfloat32as(0.230000, 0x3e6b851f)\n\"w6\":\t\tfloat32as(0.300000, 0x3e99999a)\n\"w7\":\t\tfloat32as(0.440000, 0x3ee147ae)\n\"w8\":\t\tfloat32as(0.540000, 0x3f0a3d71)\n\"w9\":\t\tfloat32as(0.620000, 0x3f1eb852)\n\nfunc (f *Font) ShouldEnableBoldSymbolicTrait() bool {\n\tif f.IsRegistered() {\n\t\treturn f.ShouldEnableBoldSymbolicTraitFromRegistry()\n\t}\n\tno := f.Weight() <= float64as(0.239000, 0x3fce978d4fdf3b64)\n\treturn !no\n}\n"
  },
  {
    "path": "_doc/export/ctwidths",
    "content": "xx pseudo-go\n\nfunc (f *CTFont) RegistryWidth32() float32 {\n\tmetadata visual descriptors\n\t{ \"med\", float32as(0.000000, 0x0) },\n\t{ \"cond\", float32as(-0.200000, 0xbe4ccccd) },\n\t{ \"ext\", float32as(0.200000, 0x3e4ccccd) },\n\n\tstyle dictionary\n\t{ \"Extra Compressed\", float32as(-0.700000, 0xbf333333) },\n\t{ \"Ultra Compressed\", float32as(-0.700000, 0xbf333333) },\n\t{ \"Ultra Condensed\", float32as(-0.700000, 0xbf333333) },\n\t{ \"Extra Condensed\", float32as(-0.500000, 0xbf000000) },\n\t{ \"Extra Extended\", float32as(0.400000, 0x3ecccccd) },\n\t{ \"Ext Compressed\", float32as(-0.700000, 0xbf333333) },\n\t{ \"Ultra Expanded\", float32as(0.800000, 0x3f4ccccd) },\n\t{ \"Ultra Extended\", float32as(0.800000, 0x3f4ccccd) },\n\t{ \"Extra Expanded\", float32as(0.400000, 0x3ecccccd) },\n\txx TODO this is weird, but correct...\n\t{ \"Semi Condensed\", float32as(-0.700000, 0xbf333333) },\n\t{ \"Semi Condensed\", float32as(-0.100000, 0xbdcccccd) },\n\txx end TODO\n\t{ \"Ext Condensed\", float32as(-0.500000, 0xbf000000) },\n\t{ \"SemiCondensed\", float32as(-0.100000, 0xbdcccccd) },\n\t{ \"ExtraExpanded\", float32as(0.400000, 0x3ecccccd) },\n\t{ \"Semi Expanded\", float32as(0.100000, 0x3dcccccd) },\n\t{ \"Semi Extended\", float32as(0.100000, 0x3dcccccd) },\n\t{ \"Ext Expanded\", float32as(0.400000, 0x3ecccccd) },\n\t{ \"Ext Extended\", float32as(0.400000, 0x3ecccccd) },\n\t{ \"SemiExpanded\", float32as(0.100000, 0x3dcccccd) },\n\t{ \"Extra Narrow\", float32as(-0.500000, 0xbf000000) },\n\t{ \"ExtraNarrow\", float32as(-0.500000, 0xbf000000) },\n\t{ \"Extra Wide\", float32as(0.800000, 0x3f4ccccd) },\n\t{ \"Ultra Cond\", float32as(-0.700000, 0xbf333333) },\n\t{ \"Compressed\", float32as(-0.500000, 0xbf000000) },\n\t{ \"Extra Cond\", float32as(-0.500000, 0xbf000000) },\n\t{ \"Semi Cond\", float32as(-0.100000, 0xbdcccccd) },\n\t{ \"Condensed\", float32as(-0.200000, 0xbe4ccccd) },\n\t{ \"ExtraWide\", float32as(0.800000, 0x3f4ccccd) },\n\t{ \"Extended\", float32as(0.200000, 0x3e4ccccd) },\n\t{ \"Expanded\", float32as(0.200000, 0x3e4ccccd) },\n\t{ \"Ext Cond\", float32as(-0.500000, 0xbf000000) },\n\t{ \"Narrow\", float32as(-0.400000 , 0xbecccccd) },\n\t{ \"Compact\", float32as(-0.400000, 0xbecccccd) },\n\t{ \"Cond\", float32as(-0.200000, 0xbe4ccccd) },\n\t{ \"Wide\", float32as(0.600000, 0x3f19999a) },\n\t{ \"Thin\", float32as(-0.700000, 0xbf333333) },\n\n\tget os2 table\n\tif os2table.Len() >= 78 {\n\t\tusWidthClass := uint16be(b[6:8]) - 1\n\t\txx this handles the case where the original usWidthClass == 0\n\t\tif usWidthClass > 8 {\n\t\t\tpanose := b[0x23] - 2\n\t\t\tif panose > 6 {\n\t\t\t\txx TODO\n\t\t\t} else {\n\t\t\t\tswitch panose {\n\t\t\t\tcase 0, 1, 2:\n\t\t\t\t\twidth = float32as(0.000000, 0x0)\n\t\t\t\tcase 3:\n\t\t\t\t\twidth = float32as(0.200000, 0x3e4ccccd)\n\t\t\t\tcase 4:\n\t\t\t\t\twidth = float32as(-0.200000, 0xbe4ccccd)\n\t\t\t\tcase 5:\n\t\t\t\t\twidth = float32as(0.400000, 0x3ecccccd)\n\t\t\t\tcase 6:\n\t\t\t\t\twidth = float32as(-0.400000, 0xbecccccd)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tswitch usWidthClass {\n\t\tcase 0:\n\t\t\twidth = float32as(-0.700000, 0xbf333333)\n\t\tcase 1:\n\t\t\twidth = float32as(-0.500000, 0xbf000000)\n\t\tcase 2:\n\t\t\twidth = float32as(-0.200000, 0xbe4ccccd)\n\t\tcase 3:\n\t\t\twidth = float32as(-0.100000, 0xbdcccccd)\n\t\tcase 4:\n\t\t\twidth = float32as(0.000000, 0x0)\n\t\tcase 5:\n\t\t\twidth = float32as(0.100000, 0x3dcccccd)\n\t\tcase 6:\n\t\t\twidth = float32as(0.400000, 0x3ecccccd)\n\t\tcase 7:\n\t\t\twidth = float32as(0.600000, 0x3f19999a)\n\t\tcase 8:\n\t\t\twidth = float32as(0.800000, 0x3f4ccccd)\n\t\t}\n\t}\n\n\theadtable := f.CopyTable('head')\n\tif headtable != nil {\n\t\tif headtable.Len() >= 54 {\n\t\t\tx := b[0x2d]\n\t\t\tif (x & 0x20) != 0 {\n\t\t\t\twidth = float32as(-0.200000, 0xbe4ccccd)\n\t\t\t} else if (x & 0x40) != 0 {\n\t\t\t\twidth = float32as(0.200000, 0x3e4ccccd)\n\t\t\t}\n\t\t}\n\t}\n\n\txx and if all else fails\n\treturn float32as(0.000000, 0x0)\n}\n\nfunc (f *CTFont) Width() float64 {\n\tif f.IsRegistered() {\n\t\treturn f.RegistryWidth()\n\t}\n\n\twidth := 0.0\n\thasWidth := false\n\n\tif there is an OS2 table {\n\t\tvar usWidthClass uint16\n\n\t\tvalid := false\n\t\tif it's 78 bytes or more {\n\t\t\tusWidthClass = uint16be(table[6:8])\n\t\t\tif usWeightClass <= 10 {\n\t\t\t\tvalid = true\n\t\t\t} else {\n\t\t\t\tvalid = false\n\t\t\t}\n\t\t} else {\n\t\t\tusWidthClass = 0\n\t\t\tvalid = true\n\t\t}\n\t\tif valid {\n\t\t\tten := float64as(10.000000, 0x4024000000000000)\n\t\t\tnegPointFive := float64as(-0.500000, 0xbfe0000000000000)\n\t\t\twidth = (float64(usWidthClass) div ten) + negPointFive\n\t\t\thasWidth = true\n\t\t}\n\t}\n\n\tthen there's the style glossary strings comparison:\n\t{ \"semi condensed\", float32as(-0.100000, 0xbdcccccd) },\n\t{ \"extra expanded\", float32as(0.400000, 0x3ecccccd) },\n\t{ \"semicondensed\", float32as(-0.100000, 0xbdcccccd) },\n\t{ \"extraexpanded\", float32as(0.400000, 0x3ecccccd) },\n\t{ \"semi expanded\", float32as(0.100000, 0x3dcccccd) },\n\t{ \"semiexpanded\", float32as(0.100000, 0x3dcccccd) },\n\t{ \"extra narrow\", float32as(-0.500000, 0xbf000000) },\n\t{ \"extranarrow\", float32as(-0.500000, 0xbf000000) },\n\t{ \"extra wide\", float32as(0.800000, 0x3f4ccccd) },\n\t{ \"condensed\", float32as(-0.200000, 0xbe4ccccd) },\n\t{ \"extrawide\", float32as(0.800000, 0x3f4ccccd) },\n\t{ \"extended\", float32as(0.200000, 0x3e4ccccd) },\n\t{ \"expanded\", float32as(0.200000, 0x3e4ccccd) },\n\t{ \"narrow\", float32as(-0.400000, 0xbecccccd) },\n\t{ \"wide\", float32as(0.600000, 0x3f19999a) },\n\t{ \"thin\", float32as(-0.700000, 0xbf333333) },\n\n\totherwise just return float64as(0.000000, 0x0)\n}\n"
  },
  {
    "path": "_doc/export/ctwidthscombined",
    "content": "-0.100000\n\tstyle string contains \"Semi Cond\"\n\tstyle string contains \"Semi Condensed\"; unregistered fonts only (see below)\n\tstyle string contains \"SemiCondensed\"\n\tOS2 width 4\n\n-0.200000\n\t('head' table byte 0x2d) & 0x20 != 0\n\tATSD style \"cond\"\n\tpanose 6\n\tstyle string contains \"Cond\"\n\tstyle string contains \"Condensed\"\n\tOS2 width 3\n\n-0.400000\n\tpanose 8\n\tstyle string contains \"Compact\"\n\tstyle string contains \"Narrow\"\n\n-0.500000\n\tstyle string contains \"Compressed\"\n\tstyle string contains \"Ext Cond\"\n\tstyle string contains \"Ext Condensed\"\n\tstyle string contains \"Extra Cond\"\n\tstyle string contains \"Extra Condensed\"\n\tstyle string contains \"Extra Narrow\"\n\tstyle string contains \"ExtraNarrow\"\n\tOS2 width 2\n\n-0.700000\n\tstyle string contains \"Ext Compressed\"\n\tstyle string contains \"Extra Compressed\"\n\tstyle 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\n\tstyle string contains \"Thin\"\n\tstyle string contains \"Ultra Compressed\"\n\tstyle string contains \"Ultra Cond\"\n\tstyle string contains \"Ultra Condensed\"\n\tOS2 width 1\n\n0.000000\n\tdefault\n\tATSD style \"med\"\n\tpanose 2, 3, 4\n\tOS2 width 5\n\n0.100000\n\tstyle string contains \"Semi Expanded\"\n\tstyle string contains \"Semi Extended\"\n\tstyle string contains \"SemiExpanded\"\n\tOS2 width 6\n\n0.200000\n\t('head' table byte 0x2d) & 0x40 != 0\n\tATSD style \"ext\"\n\tpanose 5\n\tstyle string contains \"Expanded\"\n\tstyle string contains \"Extended\"\n\n0.400000\n\tpanose 7\n\tstyle string contains \"Ext Expanded\"\n\tstyle string contains \"Ext Extended\"\n\tstyle string contains \"Extra Expanded\"\n\tstyle string contains \"Extra Extended\"\n\tstyle string contains \"ExtraExpanded\"\n\tOS2 width 7\n\n0.600000\n\tstyle string contains \"Wide\"\n\tOS2 width 8\n\n0.800000\n\tstyle string contains \"Extra Wide\"\n\tstyle string contains \"ExtraWide\"\n\tstyle string contains \"Ultra Expanded\"\n\tstyle string contains \"Ultra Extended\"\n\tOS2 width 9\n"
  },
  {
    "path": "_doc/export/ctwidthsprocessed",
    "content": "-0.100000  0xbdcccccd  registered \"Semi Cond\"\n-0.100000  0xbdcccccd  registered \"Semi Condensed\"\n-0.100000  0xbdcccccd  registered \"SemiCondensed\"\n-0.100000  0xbdcccccd  registered OS2 4\n-0.100000  0xbdcccccd  unregistered \"semi condensed\"\n-0.100000  0xbdcccccd  unregistered \"semicondensed\"\n-0.200000  0xbe4ccccd  head[0x2d] & 0x20\n-0.200000  0xbe4ccccd  metadata \"cond\"\n-0.200000  0xbe4ccccd  panose 6\n-0.200000  0xbe4ccccd  registered \"Cond\"\n-0.200000  0xbe4ccccd  registered \"Condensed\"\n-0.200000  0xbe4ccccd  registered OS2 3\n-0.200000  0xbe4ccccd  unregistered \"condensed\"\n-0.400000  0xbecccccd  panose 8\n-0.400000  0xbecccccd  registered \"Compact\"\n-0.400000  0xbecccccd  registered \"Narrow\"\n-0.400000  0xbecccccd  unregistered \"narrow\"\n-0.500000  0xbf000000  registered \"Compressed\"\n-0.500000  0xbf000000  registered \"Ext Cond\"\n-0.500000  0xbf000000  registered \"Ext Condensed\"\n-0.500000  0xbf000000  registered \"Extra Cond\"\n-0.500000  0xbf000000  registered \"Extra Condensed\"\n-0.500000  0xbf000000  registered \"Extra Narrow\"\n-0.500000  0xbf000000  registered \"ExtraNarrow\"\n-0.500000  0xbf000000  registered OS2 2\n-0.500000  0xbf000000  unregistered \"extra narrow\"\n-0.500000  0xbf000000  unregistered \"extranarrow\"\n-0.700000  0xbf333333  registered \"Ext Compressed\"\n-0.700000  0xbf333333  registered \"Extra Compressed\"\n-0.700000  0xbf333333  registered \"Semi Condensed\"\n-0.700000  0xbf333333  registered \"Thin\"\n-0.700000  0xbf333333  registered \"Ultra Compressed\"\n-0.700000  0xbf333333  registered \"Ultra Cond\"\n-0.700000  0xbf333333  registered \"Ultra Condensed\"\n-0.700000  0xbf333333  registered OS2 1\n-0.700000  0xbf333333  unregistered \"thin\"\n0.000000   0x0         default\n0.000000   0x0         metadata \"med\"\n0.000000   0x0         panose 2, 3, 4\n0.000000   0x0         registered OS2 5\n0.000000   0x0         registered default\n0.100000   0x3dcccccd  registered \"Semi Expanded\"\n0.100000   0x3dcccccd  registered \"Semi Extended\"\n0.100000   0x3dcccccd  registered \"SemiExpanded\"\n0.100000   0x3dcccccd  registered OS2 6\n0.100000   0x3dcccccd  unregistered \"semi expanded\"\n0.100000   0x3dcccccd  unregistered \"semiexpanded\"\n0.200000   0x3e4ccccd  head[0x2d] & 0x40\n0.200000   0x3e4ccccd  metadata \"ext\"\n0.200000   0x3e4ccccd  panose 5\n0.200000   0x3e4ccccd  registered \"Expanded\"\n0.200000   0x3e4ccccd  registered \"Extended\"\n0.200000   0x3e4ccccd  unregistered \"expanded\"\n0.200000   0x3e4ccccd  unregistered \"extended\"\n0.400000   0x3ecccccd  panose 7\n0.400000   0x3ecccccd  registered \"Ext Expanded\"\n0.400000   0x3ecccccd  registered \"Ext Extended\"\n0.400000   0x3ecccccd  registered \"Extra Expanded\"\n0.400000   0x3ecccccd  registered \"Extra Extended\"\n0.400000   0x3ecccccd  registered \"ExtraExpanded\"\n0.400000   0x3ecccccd  registered OS2 7\n0.400000   0x3ecccccd  unregistered \"extra expanded\"\n0.400000   0x3ecccccd  unregistered \"extraexpanded\"\n0.600000   0x3f19999a  registered \"Wide\"\n0.600000   0x3f19999a  registered OS2 8\n0.600000   0x3f19999a  unregistered \"wide\"\n0.800000   0x3f4ccccd  registered \"Extra Wide\"\n0.800000   0x3f4ccccd  registered \"ExtraWide\"\n0.800000   0x3f4ccccd  registered \"Ultra Expanded\"\n0.800000   0x3f4ccccd  registered \"Ultra Extended\"\n0.800000   0x3f4ccccd  registered OS2 9\n0.800000   0x3f4ccccd  unregistered \"extra wide\"\n0.800000   0x3f4ccccd  unregistered \"extrawide\"\n"
  },
  {
    "path": "_doc/export/ctwidthvalues",
    "content": "metadata \"med\":\tfloat32as(0.000000, 0x0)\nmetadata \"cond\":\tfloat32as(-0.200000, 0xbe4ccccd)\nmetadata \"ext\":\t\tfloat32as(0.200000, 0x3e4ccccd)\n\nregistered \"Extra Compressed\":\t\tfloat32as(-0.700000, 0xbf333333)\nregistered \"Ultra Compressed\":\t\tfloat32as(-0.700000, 0xbf333333)\nregistered \"Ultra Condensed\":\t\tfloat32as(-0.700000, 0xbf333333)\nregistered \"Extra Condensed\":\t\tfloat32as(-0.500000, 0xbf000000)\nregistered \"Extra Extended\":\t\tfloat32as(0.400000, 0x3ecccccd)\nregistered \"Ext Compressed\":\t\tfloat32as(-0.700000, 0xbf333333)\nregistered \"Ultra Expanded\":\t\tfloat32as(0.800000, 0x3f4ccccd)\nregistered \"Ultra Extended\":\t\tfloat32as(0.800000, 0x3f4ccccd)\nregistered \"Extra Expanded\":\t\tfloat32as(0.400000, 0x3ecccccd)\nregistered \"Semi Condensed\":\t\tfloat32as(-0.700000, 0xbf333333)\nregistered \"Semi Condensed\":\t\tfloat32as(-0.100000, 0xbdcccccd)\nregistered \"Ext Condensed\":\t\tfloat32as(-0.500000, 0xbf000000)\nregistered \"SemiCondensed\":\t\tfloat32as(-0.100000, 0xbdcccccd)\nregistered \"ExtraExpanded\":\t\tfloat32as(0.400000, 0x3ecccccd)\nregistered \"Semi Expanded\":\t\tfloat32as(0.100000, 0x3dcccccd)\nregistered \"Semi Extended\":\t\tfloat32as(0.100000, 0x3dcccccd)\nregistered \"Ext Expanded\":\t\tfloat32as(0.400000, 0x3ecccccd)\nregistered \"Ext Extended\":\t\tfloat32as(0.400000, 0x3ecccccd)\nregistered \"SemiExpanded\":\t\tfloat32as(0.100000, 0x3dcccccd)\nregistered \"Extra Narrow\":\t\tfloat32as(-0.500000, 0xbf000000)\nregistered \"ExtraNarrow\":\t\tfloat32as(-0.500000, 0xbf000000)\nregistered \"Extra Wide\":\t\tfloat32as(0.800000, 0x3f4ccccd)\nregistered \"Ultra Cond\":\t\tfloat32as(-0.700000, 0xbf333333)\nregistered \"Compressed\":\t\tfloat32as(-0.500000, 0xbf000000)\nregistered \"Extra Cond\":\t\tfloat32as(-0.500000, 0xbf000000)\nregistered \"Semi Cond\":\t\tfloat32as(-0.100000, 0xbdcccccd)\nregistered \"Condensed\":\t\tfloat32as(-0.200000, 0xbe4ccccd)\nregistered \"ExtraWide\":\t\tfloat32as(0.800000, 0x3f4ccccd)\nregistered \"Extended\":\t\tfloat32as(0.200000, 0x3e4ccccd)\nregistered \"Expanded\":\t\tfloat32as(0.200000, 0x3e4ccccd)\nregistered \"Ext Cond\":\t\tfloat32as(-0.500000, 0xbf000000)\nregistered \"Narrow\":\t\t\tfloat32as(-0.400000 , 0xbecccccd)\nregistered \"Compact\":\t\tfloat32as(-0.400000, 0xbecccccd)\nregistered \"Cond\":\t\t\tfloat32as(-0.200000, 0xbe4ccccd)\nregistered \"Wide\":\t\t\tfloat32as(0.600000, 0x3f19999a)\nregistered \"Thin\":\t\t\tfloat32as(-0.700000, 0xbf333333)\n\npanose 2, 3, 4:\t\tfloat32as(0.000000, 0x0)\npanose 5:\t\t\tfloat32as(0.200000, 0x3e4ccccd)\npanose 6:\t\t\tfloat32as(-0.200000, 0xbe4ccccd)\npanose 7:\t\t\tfloat32as(0.400000, 0x3ecccccd)\npanose 8:\t\t\tfloat32as(-0.400000, 0xbecccccd)\n\nregistered OS2 1:\tfloat32as(-0.700000, 0xbf333333)\nregistered OS2 2:\tfloat32as(-0.500000, 0xbf000000)\nregistered OS2 3:\tfloat32as(-0.200000, 0xbe4ccccd)\nregistered OS2 4:\tfloat32as(-0.100000, 0xbdcccccd)\nregistered OS2 5:\tfloat32as(0.000000, 0x0)\nregistered OS2 6:\tfloat32as(0.100000, 0x3dcccccd)\nregistered OS2 7:\tfloat32as(0.400000, 0x3ecccccd)\nregistered OS2 8:\tfloat32as(0.600000, 0x3f19999a)\nregistered OS2 9:\tfloat32as(0.800000, 0x3f4ccccd)\n\nhead[0x2d] & 0x20:\t\tfloat32as(-0.200000, 0xbe4ccccd)\nhead[0x2d] & 0x40:\t\tfloat32as(0.200000, 0x3e4ccccd)\n\nregistered default:\t\tfloat32as(0.000000, 0x0)\n\nfunc (f *CTFont) Width() float64 {\n\tif f.IsRegistered() {\n\t\treturn f.RegistryWidth()\n\t}\n\n\twidth := 0.0\n\thasWidth := false\n\n\tif there is an OS2 table {\n\t\tvar usWidthClass uint16\n\n\t\tvalid := false\n\t\tif it's 78 bytes or more {\n\t\t\tusWidthClass = uint16be(table[6:8])\n\t\t\tif usWeightClass <= 10 {\n\t\t\t\tvalid = true\n\t\t\t} else {\n\t\t\t\tvalid = false\n\t\t\t}\n\t\t} else {\n\t\t\tusWidthClass = 0\n\t\t\tvalid = true\n\t\t}\n\t\tif valid {\n\t\t\tten := float64as(10.000000, 0x4024000000000000)\n\t\t\tnegPointFive := float64as(-0.500000, 0xbfe0000000000000)\n\t\t\twidth = (float64(usWidthClass) div ten) + negPointFive\n\t\t\thasWidth = true\n\t\t}\n\t}\n\n\tthen there's the style glossary strings comparison:\nunregistered \"semi condensed\":\tfloat32as(-0.100000, 0xbdcccccd)\nunregistered \"extra expanded\":\tfloat32as(0.400000, 0x3ecccccd)\nunregistered \"semicondensed\":\tfloat32as(-0.100000, 0xbdcccccd)\nunregistered \"extraexpanded\":\t\tfloat32as(0.400000, 0x3ecccccd)\nunregistered \"semi expanded\":\t\tfloat32as(0.100000, 0x3dcccccd)\nunregistered \"semiexpanded\":\t\tfloat32as(0.100000, 0x3dcccccd)\nunregistered \"extra narrow\":\t\tfloat32as(-0.500000, 0xbf000000)\nunregistered \"extranarrow\":\tfloat32as(-0.500000, 0xbf000000)\nunregistered \"extra wide\":\tfloat32as(0.800000, 0x3f4ccccd)\nunregistered \"condensed\":\tfloat32as(-0.200000, 0xbe4ccccd)\nunregistered \"extrawide\":\t\tfloat32as(0.800000, 0x3f4ccccd)\nunregistered \"extended\":\t\tfloat32as(0.200000, 0x3e4ccccd)\nunregistered \"expanded\":\t\tfloat32as(0.200000, 0x3e4ccccd)\nunregistered \"narrow\":\t\tfloat32as(-0.400000, 0xbecccccd)\nunregistered \"wide\":\t\t\tfloat32as(0.600000, 0x3f19999a)\nunregistered \"thin\":\t\t\tfloat32as(-0.700000, 0xbf333333)\n\ndefault:\tfloat64as(0.000000, 0x0)\n"
  },
  {
    "path": "_doc/export/fvar.swift",
    "content": "// 2 november 2017\nimport Cocoa\nimport CoreText\n\nfunc fourccString(_ k: FourCharCode) -> String {\n\tvar c: [UInt8] = [0, 0, 0, 0]\n\tc[0] = UInt8((k >> 24) & 0xFF)\n\tc[1] = UInt8((k >> 16) & 0xFF)\n\tc[2] = UInt8((k >> 8) & 0xFF)\n\tc[3] = UInt8(k & 0xFF)\n\treturn String(bytes: c, encoding: .utf8)!\n}\n\nvar weightMin: Double = 0\nvar weightMax: Double = 0\nvar weightDef: Double = 0\nvar weightVals: [String: Double] = [:]\n\nlet attrs: [String: String] = [\n\tkCTFontFamilyNameAttribute as String:\t\t\"Skia\",\n]\nlet bd = CTFontDescriptorCreateWithAttributes(attrs as CFDictionary)\nlet matches = CTFontDescriptorCreateMatchingFontDescriptors(bd, nil) as! [CTFontDescriptor]\nlet mfont = CTFontCreateWithFontDescriptor(matches[0], 0.0, nil)\nlet master = CTFontCopyVariationAxes(mfont) as! [NSDictionary]\nmaster.forEach { d in\n\tprint(\"axis {\")\n\td.forEach { k, v in\n\t\tif (k as! String) == (kCTFontVariationAxisIdentifierKey as String) {\n\t\t\tlet c = v as! FourCharCode\n\t\t\tprint(\"\\t\\(k) \\(fourccString(c))\")\n\t\t} else {\n\t\t\tprint(\"\\t\\(k) \\(v)\")\n\t\t}\n\t}\n\tprint(\"}\")\n\tif (d[kCTFontVariationAxisNameKey] as! String) == \"Weight\" {\n\t\tweightMin = d[kCTFontVariationAxisMinimumValueKey] as! Double\n\t\tweightMax = d[kCTFontVariationAxisMaximumValueKey] as! Double\n\t\tweightDef = d[kCTFontVariationAxisDefaultValueKey] as! Double\n\t}\n}\nprint(\"\")\nmatches.forEach { d in\n\tlet f = CTFontDescriptorCopyAttribute(d, kCTFontVariationAttribute) as! [FourCharCode: Double]\n\tlet n = CTFontDescriptorCopyAttribute(d, kCTFontStyleNameAttribute) as! String\n\tprint(\"\\(n) {\")\n\tf.forEach { k, v in\n\t\tprint(\"\\t\\(fourccString(k)) \\(v)\")\n\t}\n\tprint(\"}\")\n\tweightVals[n] = weightDef\n\tif let v = f[2003265652] {\n\t\tweightVals[n] = v\n\t}\n}\nprint(\"\")\nweightVals.forEach { k, v in\n\tlet basicScaled = (v - weightMin) / (weightMax - weightMin)\n\tprint(\"\\(k) basic scaled = \\(basicScaled) (OS2 \\(UInt16(basicScaled * 1000)))\")\n\t// https://www.microsoft.com/typography/otspec/otvaroverview.htm#CSN\n\tvar opentypeScaled: Double = 0\n\tif v < weightDef {\n\t\topentypeScaled = -((weightDef - v) / (weightDef - weightMin))\n\t} else if v > weightDef {\n\t\topentypeScaled = (v - weightDef) / (weightMax - weightDef)\n\t}\n\tprint(\"\\(k) opentype scaled = \\(opentypeScaled)\")\n}\nprint(\"\")\nprint(\"\\(String(describing: CTFontDescriptorCreateMatchingFontDescriptors(CTFontDescriptorCreateCopyWithVariation(matches[0], FourCharCode(2003265652) as CFNumber, CGFloat(weightMax)), Set([kCTFontVariationAttribute as String]) as CFSet)))\")\nprint(\"\")\nprint(\"\\(CTFontCopyTable(mfont, CTFontTableTag(kCTFontTableAvar), []) != nil)\")\n"
  },
  {
    "path": "_doc/export/ttfixedtest.go",
    "content": "// 2 november 2017\npackage main\n\nimport (\n\t\"fmt\"\n)\n\ntype fixed1616 uint32\ntype fixed214 uint16\n\nfunc fixed1616To214(f fixed1616) fixed214 {\n\tf += 0x00000002\n\tg := int32(f) >> 2\n\treturn fixed214(uint32(g) & 0xFFFF)\n}\n\nfunc (f fixed1616) In214Range() bool {\n\tbase := int16((f >> 16) & 0xFFFF)\n\treturn base >= -2 && base < 2\n}\n\nfunc (f fixed1616) String() string {\n\tbase := int16((f >> 16) & 0xFFFF)\n\tfrac := float64(f & 0xFFFF) / 65536\n\tres := float64(base) + frac\n\treturn fmt.Sprintf(\"%f 0x%08X\", res, uint32(f))\n}\n\nfunc (f fixed214) String() string {\n\tbase := []int16{\n\t\t0,\n\t\t1,\n\t\t-2,\n\t\t-1,\n\t}[(f & 0xC000) >> 14]\n\tfrac := float64(f & 0x3FFF) / 16384\n\tres := float64(base) + frac\n\treturn fmt.Sprintf(\"%f 0x%04X\", res, uint16(f))\n}\n\nfunc main() {\n\tfmt.Println(fixed214(0x7fff))\n\tfmt.Println(fixed214(0x8000))\n\tfmt.Println(fixed214(0x4000))\n\tfmt.Println(fixed214(0xc000))\n\tfmt.Println(fixed214(0x7000))\n\tfmt.Println(fixed214(0x0000))\n\tfmt.Println(fixed214(0x0001))\n\tfmt.Println(fixed214(0xffff))\n\n\tfmt.Println()\n\n\tfor i := uint64(0x00000000); i <= 0xFFFFFFFF; i++ {\n\t\tj := fixed1616(i)\n\t\tif !j.In214Range() { continue }\n\t\tfmt.Println(j, \"->\", fixed1616To214(j))\n\t}\n}\n"
  },
  {
    "path": "_doc/export/weightlist1.sh",
    "content": "# 21 october 2017\ngawk '\nBEGIN { FS = \"\\t+\" }\n!/float..as/ { next }\n{ i = 0; if ($1 == \"\") i++ }\n(NF-i) != 2 { next }\n{ print }\n' \"$@\"\n"
  },
  {
    "path": "_doc/export/weightlist2.sh",
    "content": "# 21 october 2017\ngawk '\n{\n\tgsub(/float..as\\(/, \"\")\n\tgsub(/,/, \"\", $(NF - 1))\n\tgsub(/\\)$/, \"\")\n\tsplit($0, parts, /:/)\n\tprint $(NF - 1) \"\\t\" $NF \"\\t\" parts[1]\n}\n' \"$@\"\n"
  },
  {
    "path": "_doc/export/weightlist3.sh",
    "content": "# 21 october 2017\nsort -t$'\\t' -k1,1 -k2,2 \"$@\" |\n\tcolumn -t -s$'\\t'\n"
  },
  {
    "path": "_doc/export/writewidths.c",
    "content": "// 22 october 2017\nextern int realMain(void);\nint main(void) { return realMain(); }\n"
  },
  {
    "path": "_doc/export/writewidths.out",
    "content": "unregistered OS2 0:\tfloat64as(-0.5, 0xbfe0000000000000)\nunregistered OS2 1:\tfloat64as(-0.4, 0xbfd999999999999a)\nunregistered OS2 2:\tfloat64as(-0.3, 0xbfd3333333333333)\nunregistered OS2 3:\tfloat64as(-0.2, 0xbfc999999999999a)\nunregistered OS2 4:\tfloat64as(-0.1, 0xbfb9999999999998)\nunregistered OS2 5:\tfloat64as(0, 0x0000000000000000)\nunregistered OS2 6:\tfloat64as(0.1, 0x3fb9999999999998)\nunregistered OS2 7:\tfloat64as(0.2, 0x3fc9999999999998)\nunregistered OS2 8:\tfloat64as(0.3, 0x3fd3333333333334)\nunregistered OS2 9:\tfloat64as(0.4, 0x3fd999999999999a)\nunregistered OS2 10:\tfloat64as(0.5, 0x3fe0000000000000)\n"
  },
  {
    "path": "_doc/export/writewidths.processed",
    "content": "-0.1  0xbfb9999999999998  unregistered OS2 4\n-0.2  0xbfc999999999999a  unregistered OS2 3\n-0.3  0xbfd3333333333333  unregistered OS2 2\n-0.4  0xbfd999999999999a  unregistered OS2 1\n-0.5  0xbfe0000000000000  unregistered OS2 0\n0     0x0000000000000000  unregistered OS2 5\n0.1   0x3fb9999999999998  unregistered OS2 6\n0.2   0x3fc9999999999998  unregistered OS2 7\n0.3   0x3fd3333333333334  unregistered OS2 8\n0.4   0x3fd999999999999a  unregistered OS2 9\n0.5   0x3fe0000000000000  unregistered OS2 10\n"
  },
  {
    "path": "_doc/export/writewidths.s",
    "content": "# 22 october 2017\n# clang -o writewidths writewidths.c writewidths.s -g -Wall -Wextra -pedantic -g\n# thanks to:\n# - http://www.idryman.org/blog/2014/12/02/writing-64-bit-assembly-on-mac-os-x/\n# - 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\n# - https://stackoverflow.com/questions/46309041/trivial-macos-assembly-64-bit-program-has-incorrect-stack-alignment\n# - 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\n# - https://stackoverflow.com/questions/2529185/what-are-cfi-directives-in-gnu-assembler-gas-used-for\n\n.section __DATA,__data\n\ndouble10:\n\t.quad\t0x4024000000000000\ndoubleNeg05:\n\t.quad\t0xbfe0000000000000\n\nfmt:\n\t.asciz\t\"unregistered OS2 %ld:\\tfloat64as(%g, 0x%016lx)\\n\"\n\n.section __TEXT,__text\n.globl _realMain\n_realMain:\n\tpushq\t\t%rbp\n\tmovq\t\t%rsp, %rbp\n\taddq\t\t\t$8, %rsp\n\n\txorq\t\t\t%rcx, %rcx\nloop:\n\tpushq\t\t%rcx\n\t# the code from core text\n\tmovzwl\t\t%cx, %ecx\n\txorps\t\t%xmm0, %xmm0\n\tcvtsi2sdl\t\t%ecx, %xmm0\n\tdivsd\t\tdouble10(%rip), %xmm0\n\taddsd\t\tdoubleNeg05(%rip), %xmm0\n\t# end core text code\n\tpopq\t\t%rcx\n\tpushq\t\t%rcx\n\tmovd\t\t%xmm0, %rdx\n\tmovzwq\t\t%cx, %rsi\n\tleaq\t\t\tfmt(%rip), %rdi\n\tcallq\t\t\t_printf\n\tpopq\t\t%rcx\n\tincw\t\t\t%cx\n\tcmpw\t\t$10, %cx\n\tjbe\t\t\tloop\n\n\txorq\t\t\t%rax, %rax\n\tsubq\t\t\t$8, %rsp\n\tpopq\t\t%rbp\n\tret\n"
  },
  {
    "path": "_doc/form",
    "content": "hiding a control also hides its label\n"
  },
  {
    "path": "_doc/main",
    "content": "after uiQuit or if uiShouldQuit returns nonzero, uiQueueMain's effect is undefined\n"
  },
  {
    "path": "_doc/mainsteps",
    "content": "the function passed to mainsteps must not return until uiQuit itself has been called; otherwise the results are undefined\n"
  },
  {
    "path": "_doc/misctests/gtkprogresstable.c",
    "content": "// 25 june 2018\n#include <gtk/gtk.h>\n\nGtkWidget *mainwin;\nGtkWidget *vbox;\nGtkWidget *hbox;\nGtkWidget *startProgress;\nGtkWidget *startTable;\nGtkWidget *progressbar;\nGtkWidget *scrolledWindow;\nGtkListStore *model;\nGtkWidget *treeview;\nGtkWidget *hbox2;\n\nstatic gboolean pulseProgress(gpointer data)\n{\n\tgtk_progress_bar_pulse(GTK_PROGRESS_BAR(progressbar));\n\treturn TRUE;\n}\n\nstatic void onStartProgressClicked(GtkButton *button, gpointer data)\n{\n\tgtk_widget_set_sensitive(startProgress, FALSE);\n\tg_timeout_add(100, pulseProgress, NULL);\n}\n\ngboolean pbarStarted = FALSE;\ngint pbarValue;\n\nstatic void pbarDataFunc(GtkTreeViewColumn *col, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data)\n{\n\tif (!pbarStarted) {\n\t\tg_object_set(r,\n\t\t\t\"pulse\", -1,\n\t\t\t\"value\", 0,\n\t\t\tNULL);\n\t\treturn;\n\t}\n\tpbarValue++;\n\tif (pbarValue == G_MAXINT)\n\t\tpbarValue = 1;\n\tg_object_set(r, \"pulse\", pbarValue, NULL);\n}\n\nstatic gboolean pulseTable(gpointer data)\n{\n\tGtkTreePath *path;\n\tGtkTreeIter iter;\n\n\tpath = gtk_tree_path_new_from_indices(0, -1);\n\tgtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path);\n\tgtk_tree_model_row_changed(GTK_TREE_MODEL(model), path, &iter);\n\tgtk_tree_path_free(path);\n\treturn TRUE;\n}\n\nstatic void onStartTableClicked(GtkButton *button, gpointer data)\n{\n\tpbarStarted = TRUE;\n\tpbarValue = 0;\n\n\tgtk_widget_set_sensitive(startTable, FALSE);\n\tg_timeout_add(100, pulseTable, NULL);\n}\n\nstatic gboolean onClosing(GtkWidget *win, GdkEvent *e, gpointer data)\n{\n\tgtk_main_quit();\n\treturn FALSE;\n}\n\nint main(void)\n{\n\tGtkTreeIter iter;\n\tGtkTreeViewColumn *col;\n\tGtkCellRenderer *r;\n\n\tgtk_init(NULL, NULL);\n\n\tmainwin = gtk_window_new(GTK_WINDOW_TOPLEVEL);\n\tg_signal_connect(mainwin, \"delete-event\", G_CALLBACK(onClosing), NULL);\n\n\tvbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);\n\tgtk_container_set_border_width(GTK_CONTAINER(vbox), 12);\n\tgtk_container_add(GTK_CONTAINER(mainwin), vbox);\n\n\thbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);\n\tgtk_widget_set_halign(hbox, GTK_ALIGN_CENTER);\n\tgtk_container_add(GTK_CONTAINER(vbox), hbox);\n\n\tstartProgress = gtk_button_new_with_label(\"Start Progress Bar\");\n\tg_signal_connect(startProgress, \"clicked\", G_CALLBACK(onStartProgressClicked), NULL);\n\tgtk_container_add(GTK_CONTAINER(hbox), startProgress);\n\n\tstartTable = gtk_button_new_with_label(\"Start Table Cell Renderer\");\n\tg_signal_connect(startTable, \"clicked\", G_CALLBACK(onStartTableClicked), NULL);\n\tgtk_container_add(GTK_CONTAINER(hbox), startTable);\n\n\tprogressbar = gtk_progress_bar_new();\n\tgtk_container_add(GTK_CONTAINER(vbox), progressbar);\n\n\tscrolledWindow = gtk_scrolled_window_new(NULL, NULL);\n\tgtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledWindow), GTK_SHADOW_IN);\n\tgtk_widget_set_vexpand(scrolledWindow, TRUE);\n\tgtk_container_add(GTK_CONTAINER(vbox), scrolledWindow);\n\n\tmodel = gtk_list_store_new(1, G_TYPE_INT);\n\tgtk_list_store_append(model, &iter);\n\tgtk_list_store_set(model, &iter,\n\t\t0, 0,\n\t\t-1);\n\n\ttreeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));\n\tgtk_container_add(GTK_CONTAINER(scrolledWindow), treeview);\n\n\tcol = gtk_tree_view_column_new();\n\tgtk_tree_view_column_set_resizable(col, TRUE);\n\tgtk_tree_view_column_set_title(col, \"Column\");\n\tgtk_tree_view_append_column(GTK_TREE_VIEW(treeview), col);\n\n\tr = gtk_cell_renderer_progress_new();\n\tgtk_tree_view_column_pack_start(col, r, TRUE);\n\tgtk_tree_view_column_set_cell_data_func(col, r, pbarDataFunc, NULL, NULL);\n\n\thbox2 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);\n\tgtk_widget_set_halign(hbox2, GTK_ALIGN_CENTER);\n\tgtk_container_add(GTK_CONTAINER(vbox), hbox2);\n\n\tgtk_container_add(GTK_CONTAINER(hbox2), gtk_button_new_with_label(\"These buttons\"));\n\tgtk_container_add(GTK_CONTAINER(hbox2), gtk_button_new_with_label(\"do nothing\"));\n\tgtk_container_add(GTK_CONTAINER(hbox2), gtk_button_new_with_label(\"when clicked\"));\n\n\tgtk_widget_show_all(mainwin);\n\tgtk_main();\n\treturn 0;\n}\n"
  },
  {
    "path": "_doc/misctests/winbuttonexplorertheme.cpp",
    "content": "// 9 october 2018\n#define UNICODE\n#define _UNICODE\n#define STRICT\n#define STRICT_TYPED_ITEMIDS\n#define WINVER\t\t\t0x0600\t/* from Microsoft's winnls.h */\n#define _WIN32_WINNT\t\t0x0600\n#define _WIN32_WINDOWS\t0x0600\t/* from Microsoft's pdh.h */\n#define _WIN32_IE\t\t\t0x0700\n#define NTDDI_VERSION\t\t0x06000000\n#include <windows.h>\n#include <commctrl.h>\n#include <uxtheme.h>\n#include <vsstyle.h>\n#include <vssym32.h>\n#include <windowsx.h>\n#include <stdio.h>\n#include <stdlib.h>\n\n// cl winbuttonexplorertheme.cpp -MT -link user32.lib kernel32.lib gdi32.lib comctl32.lib uxtheme.lib msimg32.lib windows.res\n\nvoid diele(const char *func)\n{\n\tDWORD le;\n\n\tle = GetLastError();\n\tfprintf(stderr, \"%s: %I32u\\n\", func, le);\n\texit(EXIT_FAILURE);\n}\n\nvoid diehr(const char *func, HRESULT hr)\n{\n\tfprintf(stderr, \"%s: 0x%08I32X\\n\", func, hr);\n\texit(EXIT_FAILURE);\n}\n\n// TODO if we merge this into libui proper, this will need to be deduplicated\nstatic inline HRESULT lastErrorToHRESULT(DWORD lastError)\n{\n\tif (lastError == 0)\n\t\treturn E_FAIL;\n\treturn HRESULT_FROM_WIN32(lastError);\n}\n\nHINSTANCE hInstance;\nHWND leftButtons[5];\nHWND rightButtons[3];\n\nclass commandModuleStyleParams {\npublic:\n\tvirtual int partID_CMOD_MODULEBACKGROUND(void) const = 0;\n\tvirtual int partID_CMOD_TASKBUTTON(void) const = 0;\n\tvirtual int partID_CMOD_SPLITBUTTONLEFT(void) const = 0;\n\tvirtual int partID_CMOD_SPLITBUTTONRIGHT(void) const = 0;\n\tvirtual int partID_CMOD_MENUGLYPH(void) const = 0;\n\tvirtual int partID_CMOD_OVERFLOWGLYPH(void) const = 0;\n\n\tvirtual int stateID_CMODS_NORMAL(void) const = 0;\n\tvirtual int stateID_CMODS_HOT(void) const = 0;\n\tvirtual int stateID_CMODS_PRESSED(void) const = 0;\n\tvirtual int stateID_CMODS_KEYFOCUSED(void) const = 0;\n\tvirtual int stateID_CMODS_NEARHOT(void) const = 0;\n\n\tvirtual HRESULT backgroundGradientColors(HTHEME theme, COLORREF *colors) const = 0;\n\tvirtual HRESULT buttonTextColor(HTHEME theme, UINT uItemState, COLORREF *color) const = 0;\n\tvirtual BOOL buttonTextShadowed(UINT uItemState) const = 0;\n\n\tvirtual int folderBarMarginsLeftDIP(void) const = 0;\n\tvirtual int folderBarMarginsTopDIP(void) const = 0;\n\tvirtual int folderBarMarginsRightDIP(void) const = 0;\n\tvirtual int folderBarMarginsBottomDIP(void) const = 0;\n\n\tvirtual int buttonMarginsXDIP(void) const = 0;\n\tvirtual int buttonMarginsYDIP(void) const = 0;\n\tvirtual int buttonTextArrowSeparationXDIP(void) const = 0;\n};\n\nclass commandModuleStyleParamsVista : public commandModuleStyleParams {\npublic:\n\tvirtual int partID_CMOD_MODULEBACKGROUND(void) const { return 1; }\n\tvirtual int partID_CMOD_TASKBUTTON(void) const { return 2; }\n\tvirtual int partID_CMOD_SPLITBUTTONLEFT(void) const { return 3; }\n\tvirtual int partID_CMOD_SPLITBUTTONRIGHT(void) const { return 4; }\n\tvirtual int partID_CMOD_MENUGLYPH(void) const { return 5; }\n\tvirtual int partID_CMOD_OVERFLOWGLYPH(void) const { return 6; }\n\n\tvirtual int stateID_CMODS_NORMAL(void) const { return 1; }\n\tvirtual int stateID_CMODS_HOT(void) const { return 2; }\n\tvirtual int stateID_CMODS_PRESSED(void) const { return 3; }\n\tvirtual int stateID_CMODS_KEYFOCUSED(void) const { return 4; }\n\tvirtual int stateID_CMODS_NEARHOT(void) const { return 5; }\n\n\tvirtual HRESULT backgroundGradientColors(HTHEME theme, COLORREF *colors) const\n\t{\n\t\tif (colors == NULL)\n\t\t\treturn E_POINTER;\n#define argb(a, r, g, b) ((((COLORREF) ((BYTE) (a))) << 24) | RGB(r, g, b))\n\t\tcolors[0] = argb(255, 4, 80, 130);\n\t\tcolors[1] = argb(255, 17, 101, 132);\n\t\tcolors[2] = argb(255, 29, 121, 134);\n\t\treturn S_OK;\n\t}\n\n\tvirtual HRESULT buttonTextColor(HTHEME theme, UINT uItemState, COLORREF *color) const\n\t{\n\t\tif (color == NULL)\n\t\t\treturn E_POINTER;\n\t\t*color = GetSysColor(COLOR_WINDOW);\n\t\tif ((uItemState & CDIS_DISABLED) != 0)\n\t\t\t*color = argb(255, 161, 204, 210);\n#undef argb\n\t\treturn S_OK;\n\t}\n\n\tvirtual BOOL buttonTextShadowed(UINT uItemState) const\n\t{\n\t\treturn (uItemState & CDIS_DISABLED) == 0;\n\t}\n\n\tvirtual int folderBarMarginsLeftDIP(void) const { return 3; }\n\tvirtual int folderBarMarginsTopDIP(void) const { return 2; }\n\tvirtual int folderBarMarginsRightDIP(void) const { return 3; }\n\tvirtual int folderBarMarginsBottomDIP(void) const { return 3; }\n\n\tvirtual int buttonMarginsXDIP(void) const { return 6; }\n\tvirtual int buttonMarginsYDIP(void) const { return 5; }\n\tvirtual int buttonTextArrowSeparationXDIP(void) const { return 3; }\n};\n\nclass commandModuleStyleParams7 : public commandModuleStyleParams {\n\tvirtual int partID_CMOD_MODULEBACKGROUND(void) const { return 1; }\n\tvirtual int partID_CMOD_TASKBUTTON(void) const { return 3; }\n\tvirtual int partID_CMOD_SPLITBUTTONLEFT(void) const { return 4; }\n\tvirtual int partID_CMOD_SPLITBUTTONRIGHT(void) const { return 5; }\n\tvirtual int partID_CMOD_MENUGLYPH(void) const { return 6; }\n\tvirtual int partID_CMOD_OVERFLOWGLYPH(void) const { return 7; }\n\n\tvirtual int stateID_CMODS_NORMAL(void) const { return 1; }\n\tvirtual int stateID_CMODS_HOT(void) const { return 2; }\n\tvirtual int stateID_CMODS_PRESSED(void) const { return 3; }\n\tvirtual int stateID_CMODS_KEYFOCUSED(void) const { return 4; }\n\t/*TODO verify this*/virtual int stateID_CMODS_NEARHOT(void) const { return 5; }\n\n\tvirtual HRESULT backgroundGradientColors(HTHEME theme, COLORREF *colors) const\n\t{\n\t\tHRESULT hr;\n\n\t\tif (colors == NULL)\n\t\t\treturn E_POINTER;\n\t\thr = GetThemeColor(theme,\n\t\t\t2, 0,\n\t\t\tTMT_GRADIENTCOLOR1, colors + 0);\n\t\tif (hr != S_OK)\n\t\t\treturn hr;\n\t\thr = GetThemeColor(theme,\n\t\t\t2, 0,\n\t\t\tTMT_GRADIENTCOLOR2, colors + 1);\n\t\tif (hr != S_OK)\n\t\t\treturn hr;\n\t\treturn GetThemeColor(theme,\n\t\t\t2, 0,\n\t\t\tTMT_GRADIENTCOLOR3, colors + 2);\n\t}\n\n\tvirtual HRESULT buttonTextColor(HTHEME theme, UINT uItemState, COLORREF *color) const\n\t{\n\t\tint state;\n\n\t\tif (color == NULL)\n\t\t\treturn E_POINTER;\n\t\tstate = 1;\n\t\tif ((uItemState & CDIS_DISABLED) != 0)\n\t\t\tstate = 6;\n\t\t// TODO check if 3 is the correct part ID for all button types\n\t\treturn GetThemeColor(theme,\n\t\t\t3, state,\n\t\t\tTMT_TEXTCOLOR, color);\n\t}\n\n\tvirtual BOOL buttonTextShadowed(UINT uItemState) const\n\t{\n\t\treturn FALSE;\n\t}\n\n\tvirtual int folderBarMarginsLeftDIP(void) const { return 3; }\n\tvirtual int folderBarMarginsTopDIP(void) const { return 2; }\n\tvirtual int folderBarMarginsRightDIP(void) const { return 9; }\n\tvirtual int folderBarMarginsBottomDIP(void) const { return 3; }\n\n\tvirtual int buttonMarginsXDIP(void) const { return 13; }\n\tvirtual int buttonMarginsYDIP(void) const { return 5; }\n\tvirtual int buttonTextArrowSeparationXDIP(void) const { return 1; }\n};\n\n// all coordinates are in client space\nstruct buttonMetrics {\n\tSIZE fittingSize;\n\tint baseX;\n\tint baseY;\n\tint dpiX;\n\tint dpiY;\n\tBOOL hasText;\n\tSIZE textSize;\n\tBOOL hasArrow;\n\tSIZE arrowSize;\n};\n\nstruct buttonRects {\n\tRECT clientRect;\n\tRECT textRect;\n\tRECT arrowRect;\n};\n\nclass commandModuleStyle {\npublic:\n\tvirtual HRESULT drawFolderBar(commandModuleStyleParams *p, HDC dc, RECT *rcWindow, RECT *rcPaint) const = 0;\n\tvirtual HRESULT buttonMetrics(commandModuleStyleParams *p, HWND button, HDC dc, struct buttonMetrics *m) const = 0;\n\tvirtual HRESULT buttonRects(commandModuleStyleParams *p, HWND button, struct buttonMetrics *m, struct buttonRects *r) const = 0;\n\tvirtual HRESULT drawButton(commandModuleStyleParams *p, HWND button, HDC dc, UINT uItemState, RECT *rcPaint) const = 0;\n};\n\nclass commandModuleStyleThemed : public commandModuleStyle {\n\tHTHEME theme;\n\tHTHEME textstyleTheme;\npublic:\n\tcommandModuleStyleThemed(HTHEME theme, HTHEME textstyleTheme)\n\t{\n\t\tthis->theme = theme;\n\t\tthis->textstyleTheme = textstyleTheme;\n\t}\n\n\tvirtual HRESULT drawFolderBar(commandModuleStyleParams *p, HDC dc, RECT *rcWindow, RECT *rcPaint) const\n\t{\n\t\tCOLORREF colors[3];\n\t\tTRIVERTEX vertices[4];\n\t\tstatic GRADIENT_RECT gr[2] = {\n\t\t\t{ 0, 1 },\n\t\t\t{ 2, 3 },\n\t\t};\n\t\tHRESULT hr;\n\n\t\thr = p->backgroundGradientColors(this->theme, colors);\n\t\tif (hr != S_OK)\n\t\t\treturn hr;\n\n\t\tvertices[0].x = rcWindow->left;\n\t\tvertices[0].y = rcWindow->top;\n\t\tvertices[0].Red = ((COLOR16) GetRValue(colors[0])) << 8;\n\t\tvertices[0].Green = ((COLOR16) GetGValue(colors[0])) << 8;\n\t\tvertices[0].Blue = ((COLOR16) GetBValue(colors[0])) << 8;\n\t\tvertices[0].Alpha = ((COLOR16) LOBYTE(colors[0] >> 24)) << 8;\n\n\t\tvertices[1].x = (rcWindow->right - rcWindow->left) / 2;\n\t\tvertices[1].y = rcWindow->bottom;\n\t\tvertices[1].Red = ((COLOR16) GetRValue(colors[1])) << 8;\n\t\tvertices[1].Green = ((COLOR16) GetGValue(colors[1])) << 8;\n\t\tvertices[1].Blue = ((COLOR16) GetBValue(colors[1])) << 8;\n\t\tvertices[1].Alpha = ((COLOR16) LOBYTE(colors[1] >> 24)) << 8;\n\n\t\tvertices[2] = vertices[1];\n\t\tvertices[2].y = rcWindow->top;\n\n\t\tvertices[3].x = rcWindow->right;\n\t\tvertices[3].y = rcWindow->bottom;\n\t\tvertices[3].Red = ((COLOR16) GetRValue(colors[2])) << 8;\n\t\tvertices[3].Green = ((COLOR16) GetGValue(colors[2])) << 8;\n\t\tvertices[3].Blue = ((COLOR16) GetBValue(colors[2])) << 8;\n\t\tvertices[3].Alpha = ((COLOR16) LOBYTE(colors[2] >> 24)) << 8;\n\n\t\tif (GradientFill(dc, vertices, 4, (PVOID) gr, 2, GRADIENT_FILL_RECT_H) == FALSE)\n\t\t\treturn lastErrorToHRESULT(GetLastError());\n\t\treturn DrawThemeBackground(this->theme, dc,\n\t\t\tp->partID_CMOD_MODULEBACKGROUND(), 0,\n\t\t\trcWindow, rcPaint);\n\t}\n\n#define hasNonsplitArrow(button) ((button) == leftButtons[0] || (button) == leftButtons[1] || (button) == leftButtons[2])\n\n#define dlgUnitsToX(dlg, baseX) MulDiv((dlg), (baseX), 4)\n#define dlgUnitsToY(dlg, baseY) MulDiv((dlg), (baseY), 8)\n// TODO verify the parameter order\n#define dipsToX(dip, dpiX) MulDiv((dip), (dpiX), 96)\n#define dipsToY(dip, dpiY) MulDiv((dip), (dpiY), 96)\n\n\t// TODO for win7: the sizes are correct (according to UI Automation) but they don't visually match?\n\tvirtual HRESULT buttonMetrics(commandModuleStyleParams *p, HWND button, HDC dc, struct buttonMetrics *m) const\n\t{\n\t\tBOOL releaseDC;\n\t\tTEXTMETRICW tm;\n\t\tRECT r;\n\t\tint minStdButtonHeight;\n\t\tHRESULT hr;\n\n\t\tif (m == NULL)\n\t\t\treturn E_POINTER;\n\n\t\treleaseDC = FALSE;\n\t\tif (dc == NULL) {\n\t\t\tdc = GetDC(button);\n\t\t\tif (dc == NULL)\n\t\t\t\treturn lastErrorToHRESULT(GetLastError());\n\t\t\treleaseDC = TRUE;\n\t\t}\n\n\t\tZeroMemory(&tm, sizeof (TEXTMETRICW));\n\t\thr = GetThemeTextMetrics(this->textstyleTheme, dc,\n\t\t\tTEXT_BODYTEXT, 0,\n\t\t\t&tm);\n\t\tif (hr != S_OK)\n\t\t\tgoto fail;\n\t\thr = GetThemeTextExtent(this->textstyleTheme, dc,\n\t\t\tTEXT_BODYTEXT, 0,\n\t\t\tL\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\", 52, 0,\n\t\t\tNULL, &r);\n\t\tif (hr != S_OK)\n\t\t\tgoto fail;\n\t\tm->baseX = (int) (((r.right - r.left) / 26 + 1) / 2);\n\t\tm->baseY = (int) tm.tmHeight;\n\t\tm->dpiX = GetDeviceCaps(dc, LOGPIXELSX);\n\t\tm->dpiY = GetDeviceCaps(dc, LOGPIXELSY);\n\n\t\tm->fittingSize.cx = 0;\n\t\tm->fittingSize.cy = 0;\n\n\t\tm->hasText = TRUE;\n\t\tif (m->hasText) {\n\t\t\tLRESULT n;\n\t\t\tWCHAR *buf;\n\t\t\tLOGFONTW lf;\n\n\t\t\tn = SendMessageW(button, WM_GETTEXTLENGTH, 0, 0);\n\t\t\tbuf = new WCHAR[n + 1];\n\t\t\tGetWindowTextW(button, buf, n + 1);\n\t\t\thr = GetThemeTextExtent(this->textstyleTheme, dc,\n\t\t\t\tTEXT_BODYTEXT, 0,\n\t\t\t\tbuf, n, DT_CENTER,\n\t\t\t\tNULL, &r);\n\t\t\tdelete[] buf;\n\t\t\tif (hr != S_OK)\n\t\t\t\tgoto fail;\n\t\t\tm->textSize.cx = r.right - r.left;\n\t\t\tm->textSize.cy = r.bottom - r.top;\n\t\t\tm->fittingSize.cx += m->textSize.cx;\n\t\t\tm->fittingSize.cy += m->textSize.cy;\n\n\t\t\t// 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...\n\t\t\t// and yes, it seems to be using the raw lfHeight value here :/\n\t\t\t// TODO find the right TMT constant\n\t\t\thr = GetThemeFont(this->textstyleTheme, dc,\n\t\t\t\t4, 0,\n\t\t\t\tTMT_FONT, &lf);\n\t\t\tif (hr != S_OK)\n\t\t\t\tgoto fail;\n\t\t\tm->fittingSize.cx += 2 * (abs(lf.lfHeight) / 6);\n\t\t}\n\n\t\tm->hasArrow = hasNonsplitArrow(button);\n\t\tif (m->hasArrow) {\n\t\t\t// TS_MIN returns 1x1 and TS_DRAW returns 0x0, so...\n\t\t\thr = GetThemePartSize(theme, dc,\n\t\t\t\tp->partID_CMOD_MENUGLYPH(), 0,\n\t\t\t\tNULL, TS_TRUE, &(m->arrowSize));\n\t\t\tif (hr != S_OK)\n\t\t\t\tgoto fail;\n\t\t\tm->fittingSize.cx += m->arrowSize.cx;\n\t\t\t// TODO I don't think dui70.dll takes this into consideration...\n\t\t\tif (m->fittingSize.cy < m->arrowSize.cy)\n\t\t\t\tm->fittingSize.cy = m->arrowSize.cy;\n\t\t\tm->fittingSize.cx += dipsToX(p->buttonTextArrowSeparationXDIP(), m->dpiX);\n\t\t}\n\n\t\tm->fittingSize.cx += dipsToX(p->buttonMarginsXDIP(), m->dpiX) * 2;\n\t\tm->fittingSize.cy += dipsToY(p->buttonMarginsYDIP(), m->dpiY) * 2;\n\n\t\t// and dui70.dll seems to do a variant of this but for text buttons only...\n\t\tminStdButtonHeight = dlgUnitsToY(14, m->baseY);\n\t\tif (m->fittingSize.cy < minStdButtonHeight)\n\t\t\tm->fittingSize.cy = minStdButtonHeight;\n\n\t\thr = S_OK;\n\tfail:\n\t\tif (releaseDC)\n\t\t\t// TODO when migrating this to libui, this will need to be renamed to indicate we are intentionally ignoring errors\n\t\t\tReleaseDC(button, dc);\n\t\treturn hr;\n\t};\n\n\t// TODO check errors\n\tvirtual HRESULT buttonRects(commandModuleStyleParams *p, HWND button, struct buttonMetrics *m, struct buttonRects *r) const\n\t{\n\t\tif (r == NULL)\n\t\t\treturn E_POINTER;\n\n\t\tGetClientRect(button, &(r->clientRect));\n\n\t\tif (m->hasText)\n\t\t\tr->textRect = r->clientRect;\n\n\t\tif (m->hasArrow) {\n\t\t\tr->arrowRect = r->clientRect;\n\t\t\tr->arrowRect.left = r->arrowRect.right;\n\t\t\tr->arrowRect.left -= dipsToX(p->buttonMarginsXDIP(), m->dpiX);\n\t\t\tr->arrowRect.right = r->arrowRect.left;\n\t\t\tr->arrowRect.left -= m->arrowSize.cx;\n\t\t\tr->arrowRect.top += ((r->arrowRect.bottom - r->arrowRect.top) - m->arrowSize.cy) / 2;\n\t\t\tr->arrowRect.bottom = r->arrowRect.top + m->arrowSize.cy;\n\n\t\t\tif (m->hasText) {\n\t\t\t\tr->textRect.right = r->arrowRect.left - dipsToX(p->buttonTextArrowSeparationXDIP(), m->dpiX);\n\t\t\t\tr->textRect.right += dipsToX(p->buttonMarginsXDIP(), m->dpiX);\n\t\t\t}\n\t\t}\n\n\t\treturn S_OK;\n\t}\n\n\t// TODO check errors fully\n\tvirtual HRESULT drawButton(commandModuleStyleParams *p, HWND button, HDC dc, UINT uItemState, RECT *rcPaint) const\n\t{\n\t\tstruct buttonMetrics m;\n\t\tstruct buttonRects r;\n\t\tint part, state;\n\t\tHRESULT hr;\n\n\t\thr = this->buttonMetrics(p, button, dc, &m);\n\t\tif (hr != S_OK)\n\t\t\treturn hr;\n\t\thr = this->buttonRects(p, button, &m, &r);\n\t\tif (hr != S_OK)\n\t\t\treturn hr;\n\n\t\tpart = p->partID_CMOD_TASKBUTTON();\n\t\tstate = p->stateID_CMODS_NORMAL();\n\t\tif ((uItemState & CDIS_FOCUS) != 0)\n\t\t\tstate = p->stateID_CMODS_KEYFOCUSED();\n\t\tif ((uItemState & CDIS_HOT) != 0)\n\t\t\tstate = p->stateID_CMODS_HOT();\n\t\tif ((uItemState & CDIS_SELECTED) != 0)\n\t\t\tstate = p->stateID_CMODS_PRESSED();\n\t\thr = DrawThemeParentBackground(button, dc, rcPaint);\n\t\tif (hr != S_OK)\n\t\t\treturn hr;\n\t\thr = DrawThemeBackground(this->theme, dc,\n\t\t\tpart, state,\n\t\t\t&(r.clientRect), rcPaint);\n\t\tif (hr != S_OK)\n\t\t\treturn hr;\n\n\t\tif (m.hasText) {\n\t\t\tint textState;\n\t\t\tCOLORREF textColor;\n\t\t\tLRESULT n;\n\t\t\tWCHAR *buf;\n\t\t\tDTTOPTS dttopts;\n\n\t\t\thr = p->buttonTextColor(this->theme, uItemState, &textColor);\n\t\t\tif (hr != S_OK)\n\t\t\t\treturn hr;\n\t\t\tn = SendMessageW(button, WM_GETTEXTLENGTH, 0, 0);\n\t\t\tbuf = new WCHAR[n + 1];\n\t\t\tGetWindowTextW(button, buf, n + 1);\n\t\t\tZeroMemory(&dttopts, sizeof (DTTOPTS));\n\t\t\tdttopts.dwSize = sizeof (DTTOPTS);\n\t\t\tdttopts.dwFlags = DTT_TEXTCOLOR;\n\t\t\tdttopts.crText = textColor;\n\t\t\thr = DrawThemeTextEx(this->textstyleTheme, dc,\n\t\t\t\tTEXT_BODYTEXT, 0,\n\t\t\t\tbuf, n, DT_CENTER | DT_VCENTER | DT_SINGLELINE,\n\t\t\t\t&(r.textRect), &dttopts);\n\t\t\tdelete[] buf;\n\t\t\tif (hr != S_OK)\n\t\t\t\treturn hr;\n\t\t}\n\n\t\tif (m.hasArrow) {\n\t\t\t// TODO verify the state ID on all platforms\n\t\t\thr = DrawThemeBackground(this->theme, dc,\n\t\t\t\tp->partID_CMOD_MENUGLYPH(), 0,\n\t\t\t\t&(r.arrowRect), rcPaint);\n\t\t\tif (hr != S_OK)\n\t\t\t\treturn hr;\n\t\t}\n\n\t\treturn S_OK;\n\t}\n};\n\n#if 0\n// TODO check errors\nvoid drawExplorerChevron(HTHEME theme, HDC dc, HWND rebar, WPARAM band, RECT *rcPaint)\n{\n\tREBARBANDINFOW rbi;\n\tRECT r;\n\tint state;\n\n\tZeroMemory(&rbi, sizeof (REBARBANDINFOW));\n\trbi.cbSize = sizeof (REBARBANDINFOW);\n\trbi.fMask = RBBIM_CHILD | RBBIM_CHEVRONLOCATION | RBBIM_CHEVRONSTATE;\n\tSendMessageW(rebar, RB_GETBANDINFOW, band, (LPARAM) (&rbi));\n\tif ((rbi.uChevronState & STATE_SYSTEM_INVISIBLE) != 0)\n\t\treturn;\n\tstate = 1;\n\t// TODO check if this is correct\n\tif ((rbi.uChevronState & STATE_SYSTEM_FOCUSED) != 0)\n\t\tstate = 4;\n\tif ((rbi.uChevronState & STATE_SYSTEM_HOTTRACKED) != 0)\n\t\tstate = 2;\n\tif ((rbi.uChevronState & STATE_SYSTEM_PRESSED) != 0)\n\t\tstate = 3;\n\tr = rbi.rcChevronLocation;\n\t// TODO commctrl.h says this should be correct for the chevron rect, but it's not?\n//\tMapWindowRect(rbi.hwndChild, rebar, &r);\n\tDrawThemeBackground(theme, dc,\n\t\t3, state,\n\t\t&r, rcPaint);\n\tDrawThemeBackground(theme, dc,\n\t\t7, 1,\n\t\t&r, rcPaint);\n}\n#endif\n\nHICON shieldIcon;\nHICON applicationIcon;\nHICON helpIcon;\nHIMAGELIST rightList;\n\nHTHEME theme = NULL;\nHTHEME textstyleTheme = NULL;\nconst char *which = \"7\";\ncommandModuleStyle *cms = NULL;\ncommandModuleStyleParams *cmsp = NULL;\nHIMAGELIST dropdownArrowList = NULL;\n\nstatic struct {\n\tconst WCHAR *text;\n\tBOOL dropdown;\n} leftbarButtons[] = {\n\t{ L\"Organize\", TRUE },\n\t{ L\"Include in library\", TRUE },\n\t{ L\"Share with\", TRUE },\n\t{ L\"Burn\", FALSE },\n\t{ L\"New folder\", FALSE },\n};\n\n// TODO check errors\nLRESULT drawExplorerButton(NMCUSTOMDRAW *nm)\n{\n\tHRESULT hr;\n\n\tif (nm->dwDrawStage != CDDS_PREPAINT)\n\t\treturn CDRF_DODEFAULT;\n\thr = cms->drawButton(cmsp, nm->hdr.hwndFrom,\n\t\tnm->hdc, nm->uItemState, &(nm->rc));\n\tif (hr != S_OK)\n\t\treturn CDRF_DODEFAULT;\n\treturn CDRF_SKIPDEFAULT;\n}\n\nvoid onWM_CREATE(HWND hwnd)\n{\n\tint buttonx, buttony;\n\tint i;\n\n\tbuttonx = 5;\n\tbuttony = 5;\n\tfor (i = 0; i < 5; i++) {\n\t\t// TODO split buttons don't support arrow navigation?\n\t\tleftButtons[i] = CreateWindowExW(0,\n\t\t\tL\"BUTTON\", leftbarButtons[i].text,\n\t\t\tWS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,\n\t\t\tbuttonx, buttony,\n\t\t\t150, 30,\n\t\t\thwnd, (HMENU) (100 + i), hInstance, NULL);\n\t\tif (leftButtons[i] == NULL)\n\t\t\tdiele(\"CreateWindowExW(L\\\"BUTTON\\\")\");\n\t\tbuttonx += 150;\n\t}\n}\n\n// TODO check errors\nvoid updateTheme(HWND hwnd)\n{\n\tif (cmsp != NULL) {\n\t\tdelete cmsp;\n\t\tcmsp = NULL;\n\t}\n\tif (cms != NULL) {\n\t\tdelete cms;\n\t\tcms = NULL;\n\t}\n\tif (textstyleTheme != NULL) {\n\t\tCloseThemeData(textstyleTheme);\n\t\ttextstyleTheme = NULL;\n\t}\n\tif (theme != NULL) {\n\t\tCloseThemeData(theme);\n\t\ttheme = NULL;\n\t}\n\n\ttheme = OpenThemeData(hwnd, L\"CommandModule\");\n\ttextstyleTheme = OpenThemeData(hwnd, L\"TEXTSTYLE\");\n\tcms = new commandModuleStyleThemed(theme, textstyleTheme);\n\tif (strcmp(which, \"vista\") == 0)\n\t\tcmsp = new commandModuleStyleParamsVista;\n\telse\n\t\tcmsp = new commandModuleStyleParams7;\n}\n\nvoid repositionButtons(HWND hwnd)\n{\n\tHDWP dwp;\n\tint buttonx, buttony;\n\tHDC dc;\n\tint dpiX;\n\tint dpiY;\n\tstruct buttonMetrics m;\n\tint i;\n\n\tdc = GetDC(hwnd);\n\tif (dc == NULL)\n\t\tdiele(\"GetDC()\");\n\tdpiX = GetDeviceCaps(dc, LOGPIXELSX);\n\tdpiY = GetDeviceCaps(dc, LOGPIXELSY);\n\t// TODO check error\n\tReleaseDC(hwnd, dc);\n\n\tdwp = BeginDeferWindowPos(5);\n\tif (dwp == NULL)\n\t\tdiele(\"BeginDeferWindowPos()\");\n\tbuttonx = dipsToX(cmsp->folderBarMarginsLeftDIP(), dpiX);\n\tbuttony = dipsToY(cmsp->folderBarMarginsTopDIP(), dpiY);\n\tfor (i = 0; i < 5; i++) {\n\t\tcms->buttonMetrics(cmsp, leftButtons[i], NULL, &m);\n\t\tdwp = DeferWindowPos(dwp, leftButtons[i], NULL,\n\t\t\tbuttonx, buttony, m.fittingSize.cx, m.fittingSize.cy,\n\t\t\tSWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER);\n\t\tif (dwp == NULL)\n\t\t\tdiele(\"DeferWindowPos()\");\n\t\tbuttonx += m.fittingSize.cx;\n\t}\n\tif (EndDeferWindowPos(dwp) == 0)\n\t\tdiele(\"EndDeferWindowPos()\");\n}\n\n// TODO check errors\nvoid folderBarRect(HWND hwnd, HDC dc, RECT *r)\n{\n\tint dpiX;\n\tint dpiY;\n\tRECT child;\n\tint i;\n\n\tdpiX = GetDeviceCaps(dc, LOGPIXELSX);\n\tdpiY = GetDeviceCaps(dc, LOGPIXELSY);\n\n\tGetClientRect(hwnd, r);\n\tr->right -= r->left;\n\tr->left = 0;\n\tr->top = 0;\n\tr->bottom = 0;\n\n\tfor (i = 0; i < 5; i++) {\n\t\tGetWindowRect(leftButtons[i], &child);\n\t\tif (r->bottom < (child.bottom - child.top))\n\t\t\tr->bottom = (child.bottom - child.top);\n\t}\n\n\tr->bottom += dipsToY(cmsp->folderBarMarginsTopDIP(), dpiY);\n\tr->bottom += dipsToY(cmsp->folderBarMarginsBottomDIP(), dpiY);\n}\n\n#if 0\n// TODO check errors\nvoid handleEvents(HWND hwnd, WPARAM wParam)\n{\n\tLRESULT n;\n\tconst WCHAR *selRebar = NULL, *selToolbar = NULL;\n\tWCHAR *bufRebar = NULL, *bufToolbar = NULL;\n\tBOOL changeRebar = FALSE, changeToolbar = FALSE;\n\tBOOL invalidate = FALSE;\n\tWPARAM check;\n\n\tswitch (wParam) {\n\tcase MAKEWPARAM(300, CBN_SELCHANGE):\n\t\tn = SendMessageW(rebarCombo, CB_GETCURSEL, 0, 0);\n\t\tselRebar = rebarThemes[n];\n\t\tchangeRebar = TRUE;\n\t\tbreak;\n\tcase MAKEWPARAM(301, CBN_SELCHANGE):\n\t\tn = SendMessageW(toolbarCombo, CB_GETCURSEL, 0, 0);\n\t\tselToolbar = toolbarThemes[n];\n\t\tchangeToolbar = TRUE;\n\t\tbreak;\n\tcase MAKEWPARAM(200, BN_CLICKED):\n\t\tdrawmode = 0;\n\t\tn = SendMessageW(rebarCombo, WM_GETTEXTLENGTH, 0, 0);\n\t\tbufRebar = new WCHAR[n + 1];\n\t\tGetWindowTextW(rebarCombo, bufRebar, n + 1);\n\t\tn = SendMessageW(toolbarCombo, WM_GETTEXTLENGTH, 0, 0);\n\t\tbufToolbar = new WCHAR[n + 1];\n\t\tGetWindowTextW(toolbarCombo, bufToolbar, n + 1);\n\t\tselRebar = bufRebar;\n\t\tselToolbar = bufToolbar;\n\t\tchangeRebar = TRUE;\n\t\tchangeToolbar = TRUE;\n\t\tbreak;\n\tcase MAKEWPARAM(201, BN_CLICKED):\n\t\tdrawmode = 1;\n\t\tinvalidate = TRUE;\n\t\tbreak;\n\tcase MAKEWPARAM(302, BN_CLICKED):\n\t\tShowWindow(leftbar, SW_HIDE);\n\t\tcheck = BST_CHECKED;\n\t\tif (SendMessage(toolbarTransparentCheckbox, BM_GETCHECK, 0, 0) == BST_CHECKED)\n\t\t\tcheck = BST_UNCHECKED;\n\t\tSendMessage(toolbarTransparentCheckbox, BM_SETCHECK, check, 0);\n\t\tif (check == BST_CHECKED)\n\t\t\tSendMessageW(leftbar, TB_SETSTYLE, 0, toolbarStyles | TBSTYLE_LIST | TBSTYLE_TRANSPARENT);\n\t\telse\n\t\t\tSendMessageW(leftbar, TB_SETSTYLE, 0, toolbarStyles | TBSTYLE_LIST);\n\t\tShowWindow(leftbar, SW_SHOW);\n\t\tbreak;\n\tcase MAKEWPARAM(202, BN_CLICKED):\n\t\tSetFocus(leftbar);\n\t\tbreak;\n\t}\n\tif (changeRebar) {\n\t\tif (selRebar != NULL && wcscmp(selRebar, L\"NULL\") == 0)\n\t\t\tselRebar = NULL;\n\t\tif (selRebar != NULL && *selRebar != L'\\0')\n\t\t\tSendMessageW(rebar, RB_SETWINDOWTHEME, 0, (LPARAM) selRebar);\n\t\telse\n\t\t\tSetWindowTheme(rebar, selRebar, selRebar);\n\t\tinvalidate = TRUE;\n\t}\n\tif (changeToolbar) {\n\t\tif (selToolbar != NULL && wcscmp(selToolbar, L\"NULL\") == 0)\n\t\t\tselToolbar = NULL;\n\t\tif (selToolbar != NULL && *selToolbar != L'\\0') {\n\t\t\tSendMessageW(leftbar, TB_SETWINDOWTHEME, 0, (LPARAM) selToolbar);\n\t\t\tSendMessageW(rightbar, TB_SETWINDOWTHEME, 0, (LPARAM) selToolbar);\n\t\t} else {\n\t\t\tSetWindowTheme(leftbar, selToolbar, selToolbar);\n\t\t\tSetWindowTheme(rightbar, selToolbar, selToolbar);\n\t\t}\n\t\tinvalidate = TRUE;\n\t}\n\tif (invalidate)\n\t\tInvalidateRect(hwnd, NULL, TRUE);\n\tif (bufRebar != NULL)\n\t\tdelete[] bufRebar;\n\tif (bufToolbar != NULL)\n\t\tdelete[] bufToolbar;\n}\n#endif\n\nLRESULT CALLBACK wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)\n{\n\tHDC dc;\n\tPAINTSTRUCT ps;\n\tNMHDR *nm = (NMHDR *) lParam;\n\tint i;\n\n\tswitch (uMsg) {\n\tcase WM_CREATE:\n\t\tonWM_CREATE(hwnd);\n\t\tupdateTheme(hwnd);\n\t\trepositionButtons(hwnd);\n\t\tbreak;\n\tcase WM_CLOSE:\n\t\tPostQuitMessage(0);\n\t\tbreak;\n\tcase WM_SIZE:\n\t\trepositionButtons(hwnd);\n\t\t// TODO check errors\n\t\tInvalidateRect(hwnd, NULL, TRUE);\n\t\tbreak;\n\tcase WM_THEMECHANGED:\n\t\tupdateTheme(hwnd);\n\t\trepositionButtons(hwnd);\n\t\tbreak;\n\tcase WM_PAINT:\n\t\t// TODO check errors\n\t\tdc = BeginPaint(hwnd, &ps);\n\t\t{RECT w;\n\t\tfolderBarRect(hwnd, dc, &w);\n\t\tcms->drawFolderBar(cmsp, dc, &w, &(ps.rcPaint));}\n\t\tEndPaint(hwnd, &ps);\n\t\treturn 0;\n\tcase WM_PRINTCLIENT:\n\t\t{RECT w, paint;\n\t\tfolderBarRect(hwnd, (HDC) wParam, &w);\n\t\tGetClientRect(hwnd,&paint);\n\t\tcms->drawFolderBar(cmsp, (HDC) wParam, &w, &w);}\n\t\treturn 0;\n#if 0\n\tcase WM_COMMAND:\n\t\thandleEvents(hwnd, wParam);\n\t\tbreak;\n#endif\n\tcase WM_NOTIFY:\n\t\tswitch (nm->code) {\n\t\tcase NM_CUSTOMDRAW:\n\t\t\tfor (i = 0; i < 5; i++)\n\t\t\t\tif (nm->hwndFrom == leftButtons[i])\n\t\t\t\t\treturn drawExplorerButton((NMCUSTOMDRAW *) nm);\n\t\t\tbreak;\n\t\t}\n\t\tbreak;\n\t}\n\treturn DefWindowProcW(hwnd, uMsg, wParam, lParam);\n}\n\nEXTERN_C IMAGE_DOS_HEADER __ImageBase;\n\nint main(int argc, char *argv[])\n{\n\tSTARTUPINFOW si;\n\tint nCmdShow;\n\tINITCOMMONCONTROLSEX icc;\n\tHICON hDefaultIcon;\n\tHCURSOR hDefaultCursor;\n\tWNDCLASSW wc;\n\tHWND mainwin;\n\tMSG msg;\n\tHRESULT hr;\n\n\tif (argc > 1)\n\t\twhich = argv[1];\n\n\thInstance = (HINSTANCE) (&__ImageBase);\n\tnCmdShow = SW_SHOWDEFAULT;\n\tGetStartupInfoW(&si);\n\tif ((si.dwFlags & STARTF_USESHOWWINDOW) != 0)\n\t\tnCmdShow = si.wShowWindow;\n\n\tZeroMemory(&icc, sizeof (INITCOMMONCONTROLSEX));\n\ticc.dwSize = sizeof (INITCOMMONCONTROLSEX);\n\ticc.dwICC = ICC_STANDARD_CLASSES | ICC_BAR_CLASSES | ICC_COOL_CLASSES;\n\tif (InitCommonControlsEx(&icc) == 0)\n\t\tdiele(\"InitCommonControlsEx()\");\n\n\thDefaultIcon = LoadIconW(NULL, IDI_APPLICATION);\n\tif (hDefaultIcon == NULL)\n\t\tdiele(\"LoadIconW(IDI_APPLICATION)\");\n\thDefaultCursor = LoadCursorW(NULL, IDC_ARROW);\n\tif (hDefaultCursor == NULL)\n\t\tdiele(\"LoadCursorW(IDC_ARROW)\");\n\n\thr = LoadIconMetric(NULL, IDI_SHIELD, LIM_SMALL, &shieldIcon);\n\tif (hr != S_OK)\n\t\tdiehr(\"LoadIconMetric(IDI_SHIELD)\", hr);\n\thr = LoadIconMetric(NULL, IDI_APPLICATION, LIM_SMALL, &applicationIcon);\n\tif (hr != S_OK)\n\t\tdiehr(\"LoadIconMetric(IDI_APPLICATION)\", hr);\n\thr = LoadIconMetric(NULL, IDI_QUESTION, LIM_SMALL, &helpIcon);\n\tif (hr != S_OK)\n\t\tdiehr(\"LoadIconMetric(IDI_QUESTION)\", hr);\n\trightList = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),\n\t\tILC_COLOR32, 0, 3);\n\tif (rightList == NULL)\n\t\tdiele(\"ImageList_Create()\");\n\tif (ImageList_ReplaceIcon(rightList, -1, shieldIcon) == -1)\n\t\tdiele(\"ImageList_ReplaceIcon(IDI_SHIELD)\");\n\tif (ImageList_ReplaceIcon(rightList, -1, applicationIcon) == -1)\n\t\tdiele(\"ImageList_ReplaceIcon(IDI_APPLICATION)\");\n\tif (ImageList_ReplaceIcon(rightList, -1, helpIcon) == -1)\n\t\tdiele(\"ImageList_ReplaceIcon(IDI_QUESTION)\");\n\n\tZeroMemory(&wc, sizeof (WNDCLASSW));\n\twc.lpszClassName = L\"mainwin\";\n\twc.lpfnWndProc = wndproc;\n\twc.hInstance = hInstance;\n\twc.hIcon = hDefaultIcon;\n\twc.hCursor = hDefaultCursor;\n\twc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);\n\tif (RegisterClassW(&wc) == 0)\n\t\tdiele(\"RegisterClassW()\");\n\n\tmainwin = CreateWindowExW(0,\n\t\tL\"mainwin\", L\"Main Window\",\n\t\tWS_OVERLAPPEDWINDOW,\n\t\tCW_USEDEFAULT, CW_USEDEFAULT,\n\t\tCW_USEDEFAULT, CW_USEDEFAULT,\n\t\tNULL, NULL, hInstance, NULL);\n\tif (mainwin == NULL)\n\t\tdiele(\"CreateWindowExW(L\\\"mainwin\\\")\");\n\n\tShowWindow(mainwin, nCmdShow);\n\tif (UpdateWindow(mainwin) == 0)\n\t\tdiele(\"UpdateWindow()\");\n\n\tfor (;;) {\n\t\tint res;\n\n\t\tres = GetMessageW(&msg, NULL, 0, 0);\n\t\tif (res < 0)\n\t\t\tdiele(\"GetMessageW()\");\n\t\tif (res == 0)\n\t\t\tbreak;\n\t\tif (IsDialogMessageW(mainwin, &msg) == 0) {\n\t\t\tTranslateMessage(&msg);\n\t\t\tDispatchMessageW(&msg);\n\t\t}\n\t}\n\treturn 0;\n}\n"
  },
  {
    "path": "_doc/misctests/winrebarexplorertheme.cpp",
    "content": "// 9 october 2018\n#define UNICODE\n#define _UNICODE\n#define STRICT\n#define STRICT_TYPED_ITEMIDS\n#define WINVER\t\t\t0x0600\t/* from Microsoft's winnls.h */\n#define _WIN32_WINNT\t\t0x0600\n#define _WIN32_WINDOWS\t0x0600\t/* from Microsoft's pdh.h */\n#define _WIN32_IE\t\t\t0x0700\n#define NTDDI_VERSION\t\t0x06000000\n#include <windows.h>\n#include <commctrl.h>\n#include <uxtheme.h>\n#include <vsstyle.h>\n#include <vssym32.h>\n#include <windowsx.h>\n#include <stdio.h>\n#include <stdlib.h>\n\n// cl winrebarexplorertheme.cpp -MT -link user32.lib kernel32.lib gdi32.lib comctl32.lib uxtheme.lib msimg32.lib windows.res\n\nvoid diele(const char *func)\n{\n\tDWORD le;\n\n\tle = GetLastError();\n\tfprintf(stderr, \"%s: %I32u\\n\", func, le);\n\texit(EXIT_FAILURE);\n}\n\nvoid diehr(const char *func, HRESULT hr)\n{\n\tfprintf(stderr, \"%s: 0x%08I32X\\n\", func, hr);\n\texit(EXIT_FAILURE);\n}\n\nHINSTANCE hInstance;\nHWND rebar;\nHWND leftbar;\nHWND rightbar;\nHWND rebarCombo;\nHWND toolbarCombo;\nHWND toolbarTransparentCheckbox;\n\nHICON shieldIcon;\nHICON applicationIcon;\nHICON helpIcon;\nHIMAGELIST rightList;\n\n#define toolbarStyles (WS_CHILD | CCS_NODIVIDER | CCS_NOPARENTALIGN | CCS_NORESIZE | TBSTYLE_FLAT)\n\nstatic struct {\n\tconst WCHAR *text;\n\tBOOL dropdown;\n} leftbarButtons[] = {\n\t{ L\"Organize\", TRUE },\n\t{ L\"Include in library\", TRUE },\n\t{ L\"Share with\", TRUE },\n\t{ L\"Burn\", FALSE },\n\t{ L\"New folder\", FALSE },\n};\n\n// TODO check errors\n// TODO extract colors from the theme\nvoid drawExplorerBackground(HTHEME theme, HDC dc, RECT *rcWindow, RECT *rcPaint)\n{\n\tstatic TRIVERTEX vertices[] = {\n\t\t{ 0, 0, 4 << 8, 80 << 8, 130 << 8, 255 << 8 },\n\t\t{ 0, 0, 17 << 8, 101 << 8, 132 << 8, 255 << 8 },\n\t\t{ 0, 0, 17 << 8, 101 << 8, 132 << 8, 255 << 8 },\n\t\t{ 0, 0, 29 << 8, 121 << 8, 134 << 8, 255 << 8 },\n\t};\n\tstatic GRADIENT_RECT gr[2] = {\n\t\t{ 0, 1 },\n\t\t{ 2, 3 },\n\t};\n\n\tvertices[0].x = rcPaint->left;\n\tvertices[0].y = 0;\n\tvertices[1].x = rcPaint->right;\n\tvertices[1].y = (rcWindow->bottom - rcWindow->top) / 2;\n\tvertices[2].x = rcPaint->left;\n\tvertices[2].y = (rcWindow->bottom - rcWindow->top) / 2;\n\tvertices[3].x = rcPaint->right;\n\tvertices[3].y = rcWindow->bottom - rcWindow->top;\n\tGradientFill(dc, vertices, 4, (PVOID) gr, 2, GRADIENT_FILL_RECT_V);\n\tDrawThemeBackground(theme, dc,\n\t\t1, 0,\n\t\trcWindow, rcPaint);\n}\n\n// TODO check errors\nvoid drawExplorerChevron(HTHEME theme, HDC dc, HWND rebar, WPARAM band, RECT *rcPaint)\n{\n\tREBARBANDINFOW rbi;\n\tRECT r;\n\tint state;\n\n\tZeroMemory(&rbi, sizeof (REBARBANDINFOW));\n\trbi.cbSize = sizeof (REBARBANDINFOW);\n\trbi.fMask = RBBIM_CHILD | RBBIM_CHEVRONLOCATION | RBBIM_CHEVRONSTATE;\n\tSendMessageW(rebar, RB_GETBANDINFOW, band, (LPARAM) (&rbi));\n\tif ((rbi.uChevronState & STATE_SYSTEM_INVISIBLE) != 0)\n\t\treturn;\n\tstate = 1;\n\t// TODO check if this is correct\n\tif ((rbi.uChevronState & STATE_SYSTEM_FOCUSED) != 0)\n\t\tstate = 4;\n\tif ((rbi.uChevronState & STATE_SYSTEM_HOTTRACKED) != 0)\n\t\tstate = 2;\n\tif ((rbi.uChevronState & STATE_SYSTEM_PRESSED) != 0)\n\t\tstate = 3;\n\tr = rbi.rcChevronLocation;\n\t// TODO commctrl.h says this should be correct for the chevron rect, but it's not?\n//\tMapWindowRect(rbi.hwndChild, rebar, &r);\n\tDrawThemeBackground(theme, dc,\n\t\t3, state,\n\t\t&r, rcPaint);\n\tDrawThemeBackground(theme, dc,\n\t\t7, 1,\n\t\t&r, rcPaint);\n}\n\n// TODO check errors\nLRESULT customDrawExplorerRebar(NMCUSTOMDRAW *nm)\n{\n\tHTHEME theme;\n\tRECT r;\n\n\tif (nm->dwDrawStage != CDDS_PREPAINT)\n\t\treturn CDRF_DODEFAULT;\n\ttheme = OpenThemeData(nm->hdr.hwndFrom, L\"CommandModule\");\n\tGetClientRect(nm->hdr.hwndFrom, &r);\n\tdrawExplorerBackground(theme, nm->hdc, &r, &(nm->rc));\n\t// TODO dwItemSpec is often invalid?!\n\tdrawExplorerChevron(theme, nm->hdc, nm->hdr.hwndFrom, 0, &(nm->rc));\n\tCloseThemeData(theme);\n\treturn CDRF_SKIPDEFAULT;\n}\n\n// TODO check errors\nLRESULT customDrawExplorerToolbar(NMTBCUSTOMDRAW *nm)\n{\n\tHWND toolbar, rebar;\n\tWPARAM itemIndex;\n\tTBBUTTON tbb;\n\tHTHEME theme;\n\tRECT r;\n\tint part, state;\n\n\ttoolbar = nm->nmcd.hdr.hwndFrom;\n\tswitch (nm->nmcd.dwDrawStage) {\n\tcase CDDS_PREPAINT:\n\t\ttheme = OpenThemeData(toolbar, L\"CommandModule\");\n\t\trebar = GetParent(toolbar);\n\t\tGetWindowRect(rebar, &r);\n\t\tMapWindowRect(NULL, toolbar, &r);\n\t\tdrawExplorerBackground(theme, nm->nmcd.hdc, &r, &(nm->nmcd.rc));\n\t\tCloseThemeData(theme);\n\t\treturn CDRF_NOTIFYITEMDRAW;\n\tcase CDDS_ITEMPREPAINT:\n\t\titemIndex = (WPARAM) SendMessageW(toolbar, TB_COMMANDTOINDEX, nm->nmcd.dwItemSpec, 0);\n\t\tZeroMemory(&tbb, sizeof (TBBUTTON));\n\t\tSendMessageW(toolbar, TB_GETBUTTON, itemIndex, (LPARAM) (&tbb));\n\t\ttheme = OpenThemeData(toolbar, L\"CommandModule\");\n\t\tpart = 3;\n\t\tif ((tbb.fsStyle & BTNS_DROPDOWN) != 0)\n\t\t\tpart = 4;\n\t\tstate = 1;\n\t\t// TODO this doesn't work; both keyboard and mouse are listed as HOT\n\t\tif ((nm->nmcd.uItemState & CDIS_FOCUS) != 0)\n\t\t\tstate = 4;\n\t\tif ((nm->nmcd.uItemState & CDIS_HOT) != 0)\n\t\t\tstate = 2;\n\t\tif ((nm->nmcd.uItemState & CDIS_SELECTED) != 0)\n\t\t\tstate = 3;\n\t\tSendMessageW(toolbar, TB_GETITEMRECT, itemIndex, (LPARAM) (&r));\n\t\tDrawThemeBackground(theme, nm->nmcd.hdc,\n\t\t\t3, state,\n\t\t\t&r, &(nm->nmcd.rc));\n\t\tCloseThemeData(theme);\n\t\treturn TBCDRF_NOBACKGROUND;\n\t}\n\treturn CDRF_DODEFAULT;\n}\n\nstatic struct {\n\tconst WCHAR *text;\n\tLRESULT (*handleRebar)(NMCUSTOMDRAW *nm);\n\tLRESULT (*handleToolbar)(NMTBCUSTOMDRAW *nm);\n} drawmodes[] = {\n\t{ L\"SetWindowTheme()\", NULL, NULL },\n\t{ L\"Custom Draw Explorer\", customDrawExplorerRebar, customDrawExplorerToolbar },\n\t{ NULL, NULL },\n};\n\nint drawmode = 0;\n\nstatic const WCHAR *rebarThemes[] = {\n\tL\"NULL\",\n\tL\"\",\n\tL\"AlternateRebar\",\n\tL\"BrowserTabBar\",\n\tL\"Communications\",\n\tL\"Default\",\n\tL\"ExplorerBar\",\n\tL\"Help\",\n\tL\"InactiveNavbar\",\n\tL\"InactiveNavbarComposited\",\n\tL\"ITBarBase\",\n\tL\"MaxInactiveNavbar\",\n\tL\"MaxInactiveNavbarComposited\",\n\tL\"MaxNavbar\",\n\tL\"MaxNavbarComposited\",\n\tL\"Media\",\n\tL\"Navbar\",\n\tL\"NavbarBase\",\n\tL\"NavbarComposited\",\n\tL\"NavbarNonTopmost\",\n\tL\"Rebar\",\n\tL\"RebarStyle\",\n\tL\"TaskBar\",\n\tL\"TaskBarComposited\",\n\tNULL,\n};\n\nstatic WCHAR *toolbarThemes[] = {\n\tL\"NULL\",\n\tL\"\",\n\tL\"Alternate\",\n\tL\"BB\",\n\tL\"BBComposited\",\n\tL\"Communications\",\n\tL\"ExplorerMenu\",\n\tL\"Go\",\n\tL\"GoComposited\",\n\tL\"InactiveBB\",\n\tL\"InactiveBBComposited\",\n\tL\"InactiveGo\",\n\tL\"InactiveGoComposited\",\n\tL\"InfoPaneToolbar\",\n\tL\"LVPopup\",\n\tL\"LVPopupBottom\",\n\tL\"MaxBB\",\n\tL\"MaxBBComposited\",\n\tL\"MaxGo\",\n\tL\"MaxGoComposited\",\n\tL\"MaxInactiveBB\",\n\tL\"MaxInactiveBBComposited\",\n\tL\"MaxInactiveGo\",\n\tL\"MaxInactiveGoComposited\",\n\tL\"Media\",\n\tL\"Placesbar\",\n\tL\"SearchButton\",\n\tL\"SearchButtonComposited\",\n\tL\"StartMenu\",\n\tL\"TaskBar\",\n\tL\"TaskBarComposited\",\n\tL\"TaskBarVert\",\n\tL\"TaskBarVertComposited\",\n\tL\"Toolbar\",\n\tL\"ToolbarStyle\",\n\tL\"TrayNotify\",\n\tL\"TrayNotifyComposited\",\n\tNULL,\n};\n\n// TODO toolbarThemes\n\nvoid onWM_CREATE(HWND hwnd)\n{\n\tTBBUTTON tbb[5];\n\tRECT btnrect;\n\tDWORD tbbtnsize;\n\tLONG tbsizex, tbsizey;\n\tREBARBANDINFOW rbi;\n\tHWND button;\n\tLONG buttonx, buttony;\n\tLONG combox, comboy;\n\tint i;\n\n\trebar = CreateWindowExW(0,\n\t\tREBARCLASSNAMEW, NULL,\n\t\tWS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | CCS_NODIVIDER | CCS_TOP | RBS_FIXEDORDER,\n\t\t0, 0, 0, 0,\n\t\thwnd, (HMENU) 100, hInstance, NULL);\n\tif (rebar == NULL)\n\t\tdiele(\"CreateWindowExW(REBARCLASSNAMEW)\");\n\n\tleftbar = CreateWindowExW(0,\n\t\tTOOLBARCLASSNAMEW, NULL,\n\t\ttoolbarStyles | TBSTYLE_LIST | TBSTYLE_TRANSPARENT,\n\t\t0, 0, 0, 0,\n\t\thwnd, (HMENU) 101, hInstance, NULL);\n\tif (leftbar == NULL)\n\t\tdiele(\"CreateWindowExW(TOOLBARCLASSNAMEW) leftbar\");\n\tSendMessageW(leftbar, TB_BUTTONSTRUCTSIZE, sizeof (TBBUTTON), 0);\n\t// I_IMAGENONE causes the button text to be left-aligned; don't use it\n//\tif (SendMessageW(leftbar, TB_SETBITMAPSIZE, 0, 0) == FALSE)\n//\t\tdiele(\"TB_SETBITMAPSIZE\");\n\tSendMessageW(leftbar, TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_DRAWDDARROWS | TBSTYLE_EX_HIDECLIPPEDBUTTONS | TBSTYLE_EX_MIXEDBUTTONS);\n\t// TODO this *should* be DIPs...\n\t// TODO figure out where the *2 is documented\n//\tSendMessageW(leftbar, TB_SETPADDING, 0, MAKELPARAM(6 * 2, 5 * 2));\n\tZeroMemory(tbb, 5 * sizeof (TBBUTTON));\n\tfor (i = 0; i < 5; i++) {\n\t\ttbb[i].iBitmap = 0;\n\t\ttbb[i].idCommand = i;\n\t\ttbb[i].fsState = TBSTATE_ENABLED;\n\t\ttbb[i].fsStyle = BTNS_AUTOSIZE | BTNS_BUTTON | BTNS_NOPREFIX | BTNS_SHOWTEXT;\n\t\tif (leftbarButtons[i].dropdown)\n\t\t\ttbb[i].fsStyle |= BTNS_DROPDOWN | BTNS_WHOLEDROPDOWN;\n\t\ttbb[i].iString = (INT_PTR) (leftbarButtons[i].text);\n\t}\n\tif (SendMessageW(leftbar, TB_ADDBUTTONSW, 5, (LPARAM) tbb) == FALSE)\n\t\tdiele(\"TB_ADDBUTTONSW\");\n\n\ttbsizex = 0;\n\tfor (i = 0; i < 5; i++) {\n\t\tif (SendMessageW(leftbar, TB_GETITEMRECT, (WPARAM) i, (LPARAM) (&btnrect)) == FALSE)\n\t\t\tdiele(\"TB_GETITEMRECT\");\n\t\ttbsizex += btnrect.right - btnrect.left;\n\t}\n\ttbbtnsize = (DWORD) SendMessageW(leftbar, TB_GETBUTTONSIZE, 0, 0);\n\ttbsizey = HIWORD(tbbtnsize);\n\n\tZeroMemory(&rbi, sizeof (REBARBANDINFOW));\n\trbi.cbSize = sizeof (REBARBANDINFOW);\n\trbi.fMask = RBBIM_CHILD | RBBIM_STYLE | RBBIM_SIZE | RBBIM_CHILDSIZE | RBBIM_IDEALSIZE | RBBIM_ID;\n\trbi.fStyle = RBBS_NOGRIPPER | RBBS_CHILDEDGE | RBBS_USECHEVRON | RBBS_HIDETITLE;\n\trbi.hwndChild = leftbar;\n\trbi.cx = tbsizex;\n\trbi.cyChild = tbsizey;\n\trbi.cxMinChild = 0;\n\trbi.cyMinChild = tbsizey;\n\trbi.cxIdeal = tbsizex;\n\trbi.wID = 0;\n\tif (SendMessageW(rebar, RB_INSERTBANDW, (WPARAM) (-1), (LPARAM) (&rbi)) == 0)\n\t\tdiele(\"RB_INSERTBANDW leftbar\");\n\n\trightbar = CreateWindowExW(0,\n\t\tTOOLBARCLASSNAMEW, NULL,\n\t\ttoolbarStyles | TBSTYLE_TRANSPARENT,\n\t\t0, 0, 0, 0,\n\t\thwnd, (HMENU) 102, hInstance, NULL);\n\tif (rightbar == NULL)\n\t\tdiele(\"CreateWindowExW(TOOLBARCLASSNAMEW) rightbar\");\n\tSendMessageW(rightbar, TB_BUTTONSTRUCTSIZE, sizeof (TBBUTTON), 0);\n\tSendMessageW(rightbar, TB_SETIMAGELIST, 0, (LPARAM) rightList);\n\tSendMessageW(rightbar, TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_DRAWDDARROWS);\n\t// TODO this *should* be DIPs...\n\t// TODO figure out where the *2 is documented\n//\tSendMessageW(rightbar, TB_SETPADDING, 0, MAKELPARAM(6 * 2, 5 * 2));\n\tZeroMemory(tbb, 5 * sizeof (TBBUTTON));\n\ttbb[0].iBitmap = 0;\n\ttbb[0].idCommand = 0;\n\ttbb[0].fsState = TBSTATE_ENABLED;\n\ttbb[0].fsStyle = BTNS_AUTOSIZE | BTNS_BUTTON | BTNS_DROPDOWN;\n\ttbb[1].iBitmap = 1;\n\ttbb[1].idCommand = 1;\n\ttbb[1].fsState = TBSTATE_ENABLED;\n\ttbb[1].fsStyle = BTNS_AUTOSIZE | BTNS_BUTTON;\n\ttbb[2].iBitmap = 2;\n\ttbb[2].idCommand = 2;\n\ttbb[2].fsState = TBSTATE_ENABLED;\n\ttbb[2].fsStyle = BTNS_AUTOSIZE | BTNS_BUTTON;\n\tif (SendMessageW(rightbar, TB_ADDBUTTONSW, 3, (LPARAM) tbb) == FALSE)\n\t\tdiele(\"TB_ADDBUTTONSW\");\n\t// TODO check error\n\t// TODO figure out why this works here but not elsewhere\n//\tSendMessageW(rightbar, TB_SETBUTTONSIZE, 0, 0);\n\n\ttbsizex = 0;\n\tfor (i = 0; i < 3; i++) {\n\t\tif (SendMessageW(rightbar, TB_GETITEMRECT, (WPARAM) i, (LPARAM) (&btnrect)) == FALSE)\n\t\t\tdiele(\"TB_GETITEMRECT\");\n\t\ttbsizex += btnrect.right - btnrect.left;\n\t}\n\ttbbtnsize = (DWORD) SendMessageW(rightbar, TB_GETBUTTONSIZE, 0, 0);\n\ttbsizey = HIWORD(tbbtnsize);\n\n\tZeroMemory(&rbi, sizeof (REBARBANDINFOW));\n\trbi.cbSize = sizeof (REBARBANDINFOW);\n\trbi.fMask = RBBIM_CHILD | RBBIM_STYLE | RBBIM_SIZE | RBBIM_CHILDSIZE | RBBIM_ID;\n\trbi.fStyle = RBBS_NOGRIPPER | RBBS_HIDETITLE;\n\trbi.hwndChild = rightbar;\n\trbi.cx = tbsizex;\n\trbi.cyChild = tbsizey;\n\trbi.cxMinChild = tbsizex;\n\trbi.cyMinChild = tbsizey;\n\trbi.wID = 1;\n\tif (SendMessageW(rebar, RB_INSERTBANDW, (WPARAM) (-1), (LPARAM) (&rbi)) == 0)\n\t\tdiele(\"RB_INSERTBANDW rightbar\");\n\n\tbuttonx = 10;\n\tbuttony = 40;\n#define buttonwid 200\n#define buttonht 25\n\tfor (i = 0; drawmodes[i].text != NULL; i++) {\n\t\tbutton = CreateWindowExW(0,\n\t\t\tL\"BUTTON\", drawmodes[i].text,\n\t\t\tWS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,\n\t\t\tbuttonx, buttony,\n\t\t\tbuttonwid, buttonht,\n\t\t\thwnd, (HMENU) (200 + i), hInstance, NULL);\n\t\tif (button == NULL)\n\t\t\tdiele(\"CreateWIndowExW(L\\\"BUTTON\\\")\");\n\t\tif (i == 0) {\n\t\t\tcombox = buttonx + buttonwid + 5;\n\t\t\tcomboy = buttony;\n\t\t}\n\t\tbuttony += buttonht + 5;\n\t}\n\tbutton = CreateWindowExW(0,\n\t\tL\"BUTTON\", L\"Give Toolbar Focus\",\n\t\tWS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,\n\t\tbuttonx, buttony,\n\t\tbuttonwid, buttonht,\n\t\thwnd, (HMENU) (200 + i), hInstance, NULL);\n\tif (button == NULL)\n\t\tdiele(\"CreateWIndowExW(L\\\"BUTTON\\\")\");\n\trebarCombo = CreateWindowExW(WS_EX_CLIENTEDGE,\n\t\tL\"COMBOBOX\", L\"\",\n\t\tWS_CHILD | WS_VISIBLE | CBS_DROPDOWN,\n\t\tcombox, comboy,\n\t\tbuttonwid, buttonht,\n\t\thwnd, (HMENU) 300, hInstance, NULL);\n\tif (rebarCombo == NULL)\n\t\tdiele(\"CreateWindowExW(L\\\"COMBOBOX\\\")\");\n\tfor (i = 0; rebarThemes[i] != NULL; i++)\n\t\t// TODO check error\n\t\tSendMessageW(rebarCombo, CB_ADDSTRING, 0, (LPARAM) (rebarThemes[i]));\n\tcomboy += buttonht + 5;\n\ttoolbarCombo = CreateWindowExW(WS_EX_CLIENTEDGE,\n\t\tL\"COMBOBOX\", L\"\",\n\t\tWS_CHILD | WS_VISIBLE | CBS_DROPDOWN,\n\t\tcombox, comboy,\n\t\tbuttonwid, buttonht,\n\t\thwnd, (HMENU) 301, hInstance, NULL);\n\tif (toolbarCombo == NULL)\n\t\tdiele(\"CreateWindowExW(L\\\"COMBOBOX\\\")\");\n\tfor (i = 0; toolbarThemes[i] != NULL; i++)\n\t\t// TODO check error\n\t\tSendMessageW(toolbarCombo, CB_ADDSTRING, 0, (LPARAM) (toolbarThemes[i]));\n\tcomboy += buttonht + 5;\n\ttoolbarTransparentCheckbox = CreateWindowExW(0,\n\t\tL\"BUTTON\", L\"Transparent toolbar\",\n\t\tWS_CHILD | WS_VISIBLE | BS_CHECKBOX,\n\t\tcombox, comboy,\n\t\tbuttonwid, buttonht,\n\t\thwnd, (HMENU) 302, hInstance, NULL);\n\tif (toolbarTransparentCheckbox == NULL)\n\t\tdiele(\"CreateWindowExW(L\\\"BUTTON\\\")\");\n\tSendMessage(toolbarTransparentCheckbox, BM_SETCHECK, BST_CHECKED, 0);\n}\n\n// TODO it seems like I shouldn't have to do this?\nvoid repositionRebar(HWND hwnd)\n{\n\tRECT win, rb;\n\n\tif (GetClientRect(hwnd, &win) == 0)\n\t\tdiele(\"GetClientRect()\");\n\tif (GetWindowRect(rebar, &rb) == 0)\n\t\tdiele(\"GetWindowRect()\");\n\tif (SetWindowPos(rebar, NULL,\n\t\t0, 0, win.right - win.left, rb.bottom - rb.top,\n\t\tSWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER) == 0)\n\t\tdiele(\"SetWindowPos()\");\n}\n\n// TODO check errors\nvoid handleEvents(HWND hwnd, WPARAM wParam)\n{\n\tLRESULT n;\n\tconst WCHAR *selRebar = NULL, *selToolbar = NULL;\n\tWCHAR *bufRebar = NULL, *bufToolbar = NULL;\n\tBOOL changeRebar = FALSE, changeToolbar = FALSE;\n\tBOOL invalidate = FALSE;\n\tWPARAM check;\n\n\tswitch (wParam) {\n\tcase MAKEWPARAM(300, CBN_SELCHANGE):\n\t\tn = SendMessageW(rebarCombo, CB_GETCURSEL, 0, 0);\n\t\tselRebar = rebarThemes[n];\n\t\tchangeRebar = TRUE;\n\t\tbreak;\n\tcase MAKEWPARAM(301, CBN_SELCHANGE):\n\t\tn = SendMessageW(toolbarCombo, CB_GETCURSEL, 0, 0);\n\t\tselToolbar = toolbarThemes[n];\n\t\tchangeToolbar = TRUE;\n\t\tbreak;\n\tcase MAKEWPARAM(200, BN_CLICKED):\n\t\tdrawmode = 0;\n\t\tn = SendMessageW(rebarCombo, WM_GETTEXTLENGTH, 0, 0);\n\t\tbufRebar = new WCHAR[n + 1];\n\t\tGetWindowTextW(rebarCombo, bufRebar, n + 1);\n\t\tn = SendMessageW(toolbarCombo, WM_GETTEXTLENGTH, 0, 0);\n\t\tbufToolbar = new WCHAR[n + 1];\n\t\tGetWindowTextW(toolbarCombo, bufToolbar, n + 1);\n\t\tselRebar = bufRebar;\n\t\tselToolbar = bufToolbar;\n\t\tchangeRebar = TRUE;\n\t\tchangeToolbar = TRUE;\n\t\tbreak;\n\tcase MAKEWPARAM(201, BN_CLICKED):\n\t\tdrawmode = 1;\n\t\tinvalidate = TRUE;\n\t\tbreak;\n\tcase MAKEWPARAM(302, BN_CLICKED):\n\t\tShowWindow(leftbar, SW_HIDE);\n\t\tcheck = BST_CHECKED;\n\t\tif (SendMessage(toolbarTransparentCheckbox, BM_GETCHECK, 0, 0) == BST_CHECKED)\n\t\t\tcheck = BST_UNCHECKED;\n\t\tSendMessage(toolbarTransparentCheckbox, BM_SETCHECK, check, 0);\n\t\tif (check == BST_CHECKED)\n\t\t\tSendMessageW(leftbar, TB_SETSTYLE, 0, toolbarStyles | TBSTYLE_LIST | TBSTYLE_TRANSPARENT);\n\t\telse\n\t\t\tSendMessageW(leftbar, TB_SETSTYLE, 0, toolbarStyles | TBSTYLE_LIST);\n\t\tShowWindow(leftbar, SW_SHOW);\n\t\tbreak;\n\tcase MAKEWPARAM(202, BN_CLICKED):\n\t\tSetFocus(leftbar);\n\t\tbreak;\n\t}\n\tif (changeRebar) {\n\t\tif (selRebar != NULL && wcscmp(selRebar, L\"NULL\") == 0)\n\t\t\tselRebar = NULL;\n\t\tif (selRebar != NULL && *selRebar != L'\\0')\n\t\t\tSendMessageW(rebar, RB_SETWINDOWTHEME, 0, (LPARAM) selRebar);\n\t\telse\n\t\t\tSetWindowTheme(rebar, selRebar, selRebar);\n\t\tinvalidate = TRUE;\n\t}\n\tif (changeToolbar) {\n\t\tif (selToolbar != NULL && wcscmp(selToolbar, L\"NULL\") == 0)\n\t\t\tselToolbar = NULL;\n\t\tif (selToolbar != NULL && *selToolbar != L'\\0') {\n\t\t\tSendMessageW(leftbar, TB_SETWINDOWTHEME, 0, (LPARAM) selToolbar);\n\t\t\tSendMessageW(rightbar, TB_SETWINDOWTHEME, 0, (LPARAM) selToolbar);\n\t\t} else {\n\t\t\tSetWindowTheme(leftbar, selToolbar, selToolbar);\n\t\t\tSetWindowTheme(rightbar, selToolbar, selToolbar);\n\t\t}\n\t\tinvalidate = TRUE;\n\t}\n\tif (invalidate)\n\t\tInvalidateRect(hwnd, NULL, TRUE);\n\tif (bufRebar != NULL)\n\t\tdelete[] bufRebar;\n\tif (bufToolbar != NULL)\n\t\tdelete[] bufToolbar;\n}\n\nLRESULT CALLBACK wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)\n{\n\tNMHDR *nm = (NMHDR *) lParam;\n\n\tswitch (uMsg) {\n\tcase WM_CREATE:\n\t\tonWM_CREATE(hwnd);\n\t\tbreak;\n\tcase WM_CLOSE:\n\t\tPostQuitMessage(0);\n\t\tbreak;\n\tcase WM_SIZE:\n\t\trepositionRebar(hwnd);\n\t\tbreak;\n\tcase WM_COMMAND:\n\t\thandleEvents(hwnd, wParam);\n\t\tbreak;\n\tcase WM_NOTIFY:\n\t\tswitch (nm->code) {\n\t\tcase NM_CUSTOMDRAW:\n\t\t\tif (drawmode == 0)\n\t\t\t\tbreak;\n\t\t\tif (nm->hwndFrom == rebar)\n\t\t\t\treturn (*(drawmodes[drawmode].handleRebar))((NMCUSTOMDRAW *) nm);\n\t\t\telse if (nm->hwndFrom == leftbar || nm->hwndFrom == rightbar)\n\t\t\t\treturn (*(drawmodes[drawmode].handleToolbar))((NMTBCUSTOMDRAW *) nm);\n\t\t\tbreak;\n\t\t}\n\t\tbreak;\n\t}\n\treturn DefWindowProcW(hwnd, uMsg, wParam, lParam);\n}\n\nEXTERN_C IMAGE_DOS_HEADER __ImageBase;\n\nint main(void)\n{\n\tSTARTUPINFOW si;\n\tint nCmdShow;\n\tINITCOMMONCONTROLSEX icc;\n\tHICON hDefaultIcon;\n\tHCURSOR hDefaultCursor;\n\tWNDCLASSW wc;\n\tHWND mainwin;\n\tMSG msg;\n\tHRESULT hr;\n\n\thInstance = (HINSTANCE) (&__ImageBase);\n\tnCmdShow = SW_SHOWDEFAULT;\n\tGetStartupInfoW(&si);\n\tif ((si.dwFlags & STARTF_USESHOWWINDOW) != 0)\n\t\tnCmdShow = si.wShowWindow;\n\n\tZeroMemory(&icc, sizeof (INITCOMMONCONTROLSEX));\n\ticc.dwSize = sizeof (INITCOMMONCONTROLSEX);\n\ticc.dwICC = ICC_STANDARD_CLASSES | ICC_BAR_CLASSES | ICC_COOL_CLASSES;\n\tif (InitCommonControlsEx(&icc) == 0)\n\t\tdiele(\"InitCommonControlsEx()\");\n\n\thDefaultIcon = LoadIconW(NULL, IDI_APPLICATION);\n\tif (hDefaultIcon == NULL)\n\t\tdiele(\"LoadIconW(IDI_APPLICATION)\");\n\thDefaultCursor = LoadCursorW(NULL, IDC_ARROW);\n\tif (hDefaultCursor == NULL)\n\t\tdiele(\"LoadCursorW(IDC_ARROW)\");\n\n\thr = LoadIconMetric(NULL, IDI_SHIELD, LIM_SMALL, &shieldIcon);\n\tif (hr != S_OK)\n\t\tdiehr(\"LoadIconMetric(IDI_SHIELD)\", hr);\n\thr = LoadIconMetric(NULL, IDI_APPLICATION, LIM_SMALL, &applicationIcon);\n\tif (hr != S_OK)\n\t\tdiehr(\"LoadIconMetric(IDI_APPLICATION)\", hr);\n\thr = LoadIconMetric(NULL, IDI_QUESTION, LIM_SMALL, &helpIcon);\n\tif (hr != S_OK)\n\t\tdiehr(\"LoadIconMetric(IDI_QUESTION)\", hr);\n\trightList = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),\n\t\tILC_COLOR32, 0, 3);\n\tif (rightList == NULL)\n\t\tdiele(\"ImageList_Create()\");\n\tif (ImageList_ReplaceIcon(rightList, -1, shieldIcon) == -1)\n\t\tdiele(\"ImageList_ReplaceIcon(IDI_SHIELD)\");\n\tif (ImageList_ReplaceIcon(rightList, -1, applicationIcon) == -1)\n\t\tdiele(\"ImageList_ReplaceIcon(IDI_APPLICATION)\");\n\tif (ImageList_ReplaceIcon(rightList, -1, helpIcon) == -1)\n\t\tdiele(\"ImageList_ReplaceIcon(IDI_QUESTION)\");\n\n\tZeroMemory(&wc, sizeof (WNDCLASSW));\n\twc.lpszClassName = L\"mainwin\";\n\twc.lpfnWndProc = wndproc;\n\twc.hInstance = hInstance;\n\twc.hIcon = hDefaultIcon;\n\twc.hCursor = hDefaultCursor;\n\twc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);\n\tif (RegisterClassW(&wc) == 0)\n\t\tdiele(\"RegisterClassW()\");\n\n\tmainwin = CreateWindowExW(0,\n\t\tL\"mainwin\", L\"Main Window\",\n\t\tWS_OVERLAPPEDWINDOW,\n\t\tCW_USEDEFAULT, CW_USEDEFAULT,\n\t\tCW_USEDEFAULT, CW_USEDEFAULT,\n\t\tNULL, NULL, hInstance, NULL);\n\tif (mainwin == NULL)\n\t\tdiele(\"CreateWindowExW(L\\\"mainwin\\\")\");\n\n\tShowWindow(mainwin, nCmdShow);\n\tif (UpdateWindow(mainwin) == 0)\n\t\tdiele(\"UpdateWindow()\");\n\n\tfor (;;) {\n\t\tint res;\n\n\t\tres = GetMessageW(&msg, NULL, 0, 0);\n\t\tif (res < 0)\n\t\t\tdiele(\"GetMessageW()\");\n\t\tif (res == 0)\n\t\t\tbreak;\n\t\tTranslateMessage(&msg);\n\t\tDispatchMessageW(&msg);\n\t}\n\treturn 0;\n}\n"
  },
  {
    "path": "_doc/names.md",
    "content": "TODO clean this up\n\nTODO note that you -fvisibility=hidden means nothing in static libraries, hence this (confirmed on OS X)\n\nIn 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.\n\n# Reserved names; for users\n\nAll reserved names in libui are defined by a prefix followed by any uppercase letter in ASCII. The bullet lists before list those prefixes.\n\nGlobal-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:\n\n- `ui`\n- `uipriv`\n\nGObject 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):\n\n- `uipriv`\n\nObjective-C method names:\n\n- `initWithUipriv`\n- `initWithFrame:uipriv` (TODO probably worth removing)\n- `uipriv`\n- `isUipriv` (for compatibility with KVO and `@property` statements)\n- `setUipriv` (for compatibility with KVO and `@property` statements)\n\nObjective-C ivar names:\n\n- `uipriv`\n- `_uipriv` (for compatibility with KVO and `@property` statements)\n\nObjective-C property names:\n\n- `uipriv`\n\nTODO GObject macros (in libui's source code), properties, and signals\n\n# Developer notes\n\nTODO\n"
  },
  {
    "path": "_doc/slider",
    "content": "if min >= max then they are swapped\n"
  },
  {
    "path": "_doc/spinbox",
    "content": "if min >= max then they are swapped\n"
  },
  {
    "path": "_doc/static",
    "content": "comctl6\nlibui.res\n"
  },
  {
    "path": "_doc/windowmovesize",
    "content": "you should never need to use these functions\nthey are provided only for the cases when ABSOLUTELY NECESSARY\nthe 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\n"
  },
  {
    "path": "_doc/winstatic",
    "content": "libui uses resources starting at 29000\n"
  },
  {
    "path": "_future/otherattributes/ui.h",
    "content": "_UI_ENUM(uiAttribute) {\n\tuiAttributeFamily,\n\tuiAttributeSize,\t\t\t\t// use Double\n\tuiAttributeWeight,\n\tuiAttributeItalic,\n\tuiAttributeStretch,\n\tuiAttributeColor,\t\t\t// use R, G, B, A\n\tuiAttributeBackground,\t\t// use R, G, B, A\n\n\t// TODO kerning amount\n\t// OS X: kCTKernAttributeName\n\t// \t> 0: farther (TODO from advance or standard kerning?)\n\t// \t== 0: no kerning\n\t// \t< 0: closer (TODO same)\n\t// \tundefined: standard kerning\n\t// Pango: pango_attr_letter_spacing_new()\n\t// \tparameter meaning unspecified\n\t// Windows: requires Platform Update, SetLetterSpacing()\n\t// \tparameter meaning unspecified\n\n\tuiAttributeUnderline,\t\t// enum uiDrawUnderlineStyle\n\t// TODO what is the color in the case we don't specify it, black or the text color?\n\tuiAttributeUnderlineColor,\t// enum uiDrawUnderlineColor\n\n\t// TODO kCTSuperscriptAttributeName vs below\n\t// all it does is set the below attribute so\n\n\t// TODO kCTBaselineClassAttributeName, kCTBaselineInfoAttributeName, kCTBaselineReferenceInfoAttributeName\n\n\t// TODO strikethroughs? (pango yes, directwrite yes, os x no)\n\t// TODO baseline offsets? (pango yes)\n\t// TODO size scales? (pango yes)\n\t// TODO fallbacks (pango: enable or disable)\n\n\t// TODO document that this will also enable language-specific font features (TODO on DirectWrite too?)\n\t// TODO document that this should be strict BCP 47 form (A-Z, a-z, 0-9, and -) for maximum compatibility\n\tuiAttributeLanguage,\t\t// BCP 47 string\n\n\t// These attributes represent typographic features. Each feature\n\t// is a separate attribute, to make composition easier. The\n\t// availability of for each attribute are defined by the font; the\n\t// default values are defined by the font and/or by the OS.\n\t// \n\t// A note about features whose parameter is an enumeration:\n\t// OS X defines typographic features using the AAT specification\n\t// and converts to OpenType internally when needed, whereas\n\t// other platforms use OpenType directly. OpenType is less\n\t// precise about what each enumeration value means than AAT\n\t// is, so enumeration values do not necessarily represent what\n\t// OS X expects with all fonts. In cases where they do, libui\n\t// provides an enumeration type to use. Otherwise, the AAT\n\t// enumeration values are provided in comments for\n\t// documentation purposes.\n\n\t// TODO kAllTypographicFeaturesType\n\n\t// AAT calls these \"common ligatures\"\n\tuiAttributeStandardLigatures,\t\t// 0 = off, 1 = on\n\tuiAttributeRequiredLigatures,\t\t// 0 = off, 1 = on\n\t// AAT calls these \"rare ligatures\"\n\tuiAttributeDiscretionaryLigatures,\t// 0 = off, 1 = on\n\tuiAttributeContextualLigatures,\t// 0 = off, 1 = on\n\tuiAttributeHistoricalLigatures,\t\t// 0 = off, 1 = on\n\n\t// TODO uiAttributeCursiveConnection,\t\t// 0 = none, 1 = some, 2 = all\n\n\tuiAttributeUnicase,\t\t\t\t// 0 = off, 1 = on\n\n\t// TODO uiAttributeLinguisticRearrangement,\t// 0 = off, 1 = on\n\n\t// TODO rename this\n\tuiAttributeNumberSpacings,\t\t// enum uiAttributeNumberSpacing\n\n\t// TODO kSmartSwashType, falt and jalt\n\n\t// TODO kDiacriticsType\n\n\tuiAttributeSuperscripts,\t\t\t// enum uiAttributeSuperscript\n\n\tuiAttributeFractionForms,\t\t\t// enum uiAttributeFractionForm\n\n\tuiAttributeSlashedZero,\t\t\t// 0 = off, 1 = on\n\n\tuiAttributeMathematicalGreek,\t\t// 0 = off, 1 = on\n\n\t// AAT defines the following values:\n\t// 0 = none\n\t// 1 = dingbats\n\t// 2 = pi characters\n\t// 3 = fleurons\n\t// 4 = decorative borders\n\t// 5 = international symbols\n\t// 6 = mathematical symbols\n\t// OpenType says alphanumeric characters must(? TODO) have one form each and the bullet character U+2022 (•) can have many\n\tuiAttributeOrnamentalForms,\t\t// an integer from 0 to a font-specified upper bound\n\t// TODO provide a function to get the upper bound?\n\n\t// AAT calls this \"character alternatives\" and defines the\n\t// following values:\n\t// 0 = none\n\t// OpenType calls this \"access all alternates\".\n\t// TODO doesn't OpenType do the same about 0?\n\tuiAttributeSpecificCharacterForm,\t// an integer from 0 to a font-specified upper bound\n\t// TODO provide a function to get the upper bound?\n\n\tuiAttributeTitlingCapitalForms,\t\t// 0 = off, 1 = on\n\n\t// AAT calls these \"character shapes\"\n\tuiAttributeHanCharacterForms,\t// enum uiAttributeHanCharacterForm\n\n\t// OpenType calls these \"old-style\"\n\tuiAttributeLowercaseNumbers,\t// 0 = off, 1 = on\n\n\t// TODO kTextSpacingType\n\t// see kKanaSpacingType below\n\n\tuiAttributeHanjaToHangul,\t\t// 0 = off, 1 = on\n\n\t// AAT defines the following values:\n\t// 0 = none\n\t// 1 = box\n\t// 2 = rounded box\n\t// 3 = circle\n\t// 4 = inverted circle\n\t// 5 = parentheses\n\t// 6 = period\n\t// 7 = roman numeral\n\t// 8 = diamond\n\t// 9 = inverted box\n\t// 10 = inverted rounded box\n\t// TODO rename to AnnotatedForms?\n\tuiAttributeAnnotatedGlyphForms,\t\t// an integer from 0 to a font-specified upper bound\n\t// TODO provide a function to get the upper bound?\n\n\t// TODO kKanaSpacingType\n\t// TODO kIdeographicSpacingType\n\t// can they be provided independently of kTextSpacingType? Core Text doesn't seem to\n\n\t// TODO kUnicodeDecompositionType\n\n\tuiAttributeRubyKanaForms,\t\t// 0 = off, 1 = on\n\n\t// TODO kCJKVerticalRomanPlacementType\n\t// this is 'valt' in OpenType but I don't know if I want to make it selectable or not\n\n\tuiAttributeCJKRomansToItalics,\t// 0 = off, 1 = on\n\n\t// AAT calls this \"case-sensitive layout\"\n\tuiAttributeCaseSensitiveForms,\t// 0 = off, 1 = on\n\t// AAT: this is called \"case-sensitive spacing\"\n\tuiAttributeCapitalSpacing,\t\t// 0 = off, 1 = on\n\n\tuiAttributeAlternateHorizontalKana,\t\t// 0 = off, 1 = on\n\tuiAttributeAlternateVerticalKana,\t// 0 = off, 1 = on\n\n\t// TODO \"Alternate\"? unify all this\n\t// TODO document that these are guaranteed to be consecutive\n\tuiAttributeStylisticAlternate1,\t\t// 0 = off, 1 = on\n\tuiAttributeStylisticAlternate2,\t\t// 0 = off, 1 = on\n\tuiAttributeStylisticAlternate3,\t\t// 0 = off, 1 = on\n\tuiAttributeStylisticAlternate4,\t\t// 0 = off, 1 = on\n\tuiAttributeStylisticAlternate5,\t\t// 0 = off, 1 = on\n\tuiAttributeStylisticAlternate6,\t\t// 0 = off, 1 = on\n\tuiAttributeStylisticAlternate7,\t\t// 0 = off, 1 = on\n\tuiAttributeStylisticAlternate8,\t\t// 0 = off, 1 = on\n\tuiAttributeStylisticAlternate9,\t\t// 0 = off, 1 = on\n\tuiAttributeStylisticAlternate10,\t// 0 = off, 1 = on\n\tuiAttributeStylisticAlternate11,\t// 0 = off, 1 = on\n\tuiAttributeStylisticAlternate12,\t// 0 = off, 1 = on\n\tuiAttributeStylisticAlternate13,\t// 0 = off, 1 = on\n\tuiAttributeStylisticAlternate14,\t// 0 = off, 1 = on\n\tuiAttributeStylisticAlternate15,\t// 0 = off, 1 = on\n\tuiAttributeStylisticAlternate16,\t// 0 = off, 1 = on\n\tuiAttributeStylisticAlternate17,\t// 0 = off, 1 = on\n\tuiAttributeStylisticAlternate18,\t// 0 = off, 1 = on\n\tuiAttributeStylisticAlternate19,\t// 0 = off, 1 = on\n\tuiAttributeStylisticAlternate20,\t// 0 = off, 1 = on\n\n\tuiAttributeContextualAlternates,\t// 0 = off, 1 = on\n\tuiAttributeSwashes,\t\t\t\t// 0 = off, 1 = on\n\tuiAttributeContextualSwashes,\t\t// 0 = off, 1 = on\n\n\tuiAttributeLowercaseCapForms,\t// enum uiAttributeCapForm\n\tuiAttributeUppercaseCapForms,\t// enum uiAttributeCapForm\n\n\t// TODO kCJKRomanSpacingType\n\n\t// TODO uiAttributeSystem, (this might not be doable with DirectWrite)\n\t// TODO uiAttributeCustom,\n};\n"
  },
  {
    "path": "_future/textlanguageattr/README",
    "content": "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.)\n"
  },
  {
    "path": "_future/textlanguageattr/attrstr_darwin.m",
    "content": "struct fontParams {\n\tuiDrawFontDescriptor desc;\n\tuint16_t featureTypes[maxFeatures];\n\tuint16_t featureSelectors[maxFeatures];\n\tsize_t nFeatures;\n\tconst char *language;\n};\n\n\n\t// locale identifiers are specified as BCP 47: https://developer.apple.com/reference/corefoundation/cflocale?language=objc\n\tcase uiAttributeLanguage:\n\t\t// LONGTERM FUTURE when we move to 10.9, switch to using kCTLanguageAttributeName\n\t\tensureFontInRange(p, start, end);\n\t\tadjustFontInRange(p, start, end, ^(struct fontParams *fp) {\n\t\t\tfp->language = (const char *) (spec->Value);\n\t\t});\n\t\tbreak;\n\n\tdesc = fontdescAppendFeatures(desc, fp->featureTypes, fp->featureSelectors, fp->nFeatures, fp->language);\n"
  },
  {
    "path": "_future/textlanguageattr/attrstr_unix.c",
    "content": "\tPangoLanguage *lang;\n\n\t// 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\n\tcase uiAttributeLanguage:\n\t\tlang = pango_language_from_string((const char *) (spec->Value));\n\t\taddattr(p, start, end,\n\t\t\tpango_attr_language_new(lang));\n\t\t// lang *cannot* be freed\n\t\tbreak;\n"
  },
  {
    "path": "_future/textlanguageattr/attrstr_windows.cpp",
    "content": "\tWCHAR *localeName;\n\n\t// 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\n\tcase uiAttributeLanguage:\n\t\tlocaleName = toUTF16((char *) (spec->Value));\n\t\thr = p->layout->SetLocaleName(localeName, range);\n\t\tif (hr != S_OK)\n\t\t\tlogHRESULT(L\"error applying locale name attribute\", hr);\n\t\tuiFree(localeName);\n\t\tbreak;"
  },
  {
    "path": "_future/textlanguageattr/common_attrlist.c",
    "content": "\tcase uiAttributeLanguage:\n\t\treturn asciiStringsEqualCaseFold((char *) (attr->spec.Value), (char *) (spec->Value));\n"
  },
  {
    "path": "_future/textlanguageattr/drawtext_example.c",
    "content": "before \"or any combination of the above\"\n\n\t// thanks to https://twitter.com/codeman38/status/831924064012886017\n\tnext = \"\\xD0\\xB1\\xD0\\xB3\\xD0\\xB4\\xD0\\xBF\\xD1\\x82\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"multiple languages (compare \");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeItalic;\n\tspec.Value = uiDrawTextItalicItalic;\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tspec.Type = uiAttributeLanguage;\n\tspec.Value = (uintptr_t) \"ru\";\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \" to \");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeItalic;\n\tspec.Value = uiDrawTextItalicItalic;\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tspec.Type = uiAttributeLanguage;\n\tspec.Value = (uintptr_t) \"sr\";\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \" \\xE2\\x80\\x94 may require changing the font)\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n"
  },
  {
    "path": "_future/textlanguageattr/fontmatch_darwin.m",
    "content": "// note: this doesn't work for languages; we have to parse the ltag table\n\n// fortunately features that aren't supported are simply ignored, so we can copy them all in\n// LONGTERM FUTURE when we switch to 10.9, the language parameter won't be needed anymore\n// LONGTERM FUTURE and on 10.10 we can use OpenType tags directly!\nCTFontDescriptorRef fontdescAppendFeatures(CTFontDescriptorRef desc, const uint16_t *types, const uint16_t *selectors, size_t n, const char *language)\n{\n\tCTFontDescriptorRef new;\n\tCFMutableArrayRef outerArray;\n\tCFDictionaryRef innerDict;\n\tCFNumberRef numType, numSelector;\n\tconst void *keys[2], *values[2];\n\tsize_t i;\n\tCFArrayRef languages;\n\tCFIndex il, nl;\n\tCFStringRef curlang;\n\tchar d[2];\n\n\touterArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);\n\tif (outerArray == NULL) {\n\t\t// TODO\n\t}\n\tkeys[0] = kCTFontFeatureTypeIdentifierKey;\n\tkeys[1] = kCTFontFeatureSelectorIdentifierKey;\n\tfor (i = 0; i < n; i++) {\n\t\tnumType = CFNumberCreate(NULL, kCFNumberSInt16Type,\n\t\t\t(const SInt16 *) (types + i));\n\t\tnumSelector = CFNumberCreate(NULL, kCFNumberSInt16Type,\n\t\t\t(const SInt16 *) (selectors + i));\n\t\tvalues[0] = numType;\n\t\tvalues[1] = numSelector;\n\t\tinnerDict = CFDictionaryCreate(NULL,\n\t\t\tkeys, values, 2,\n\t\t\t// TODO are these correct?\n\t\t\t&kCFCopyStringDictionaryKeyCallBacks,\n\t\t\t&kCFTypeDictionaryValueCallBacks);\n\t\tif (innerDict == NULL) {\n\t\t\t// TODO\n\t\t}\n\t\tCFArrayAppendValue(outerArray, innerDict);\n\t\tCFRelease(innerDict);\n\t\tCFRelease(numSelector);\n\t\tCFRelease(numType);\n\t}\n\n\t// now we have to take care of the language\n\tif (language != NULL) {\n\t\tlanguages = CTFontDescriptorCopyAttribute(desc, kCTFontLanguagesAttribute);\n\t\tif (languages != NULL) {\n\t\t\tnl = CFArrayGetCount(languages);\n\t\t\td[0] = language[0];\n\t\t\tif (d[0] >= 'A' && d[0] <= 'Z')\n\t\t\t\td[0] += 'a' - 'A';\n\t\t\td[1] = language[1];\n\t\t\tif (d[1] >= 'A' && d[1] <= 'Z')\n\t\t\t\td[1] += 'a' - 'A';\n\t\t\tfor (il = 0; il < nl; il++) {\n\t\t\t\tchar c[2];\n\t\t\n\t\t\t\tcurlang = (CFStringRef) CFArrayGetValueAtIndex(languages, il);\n\t\t\t\t// TODO check for failure\n\t\t\t\tCFStringGetBytes(curlang, CFRangeMake(0, 2),\n\t\t\t\t\tkCFStringEncodingUTF8, 0, false,\n\t\t\t\t\t(UInt8 *) c, 2, NULL);\n\t\t\t\tif (c[0] >= 'A' && c[0] <= 'Z')\n\t\t\t\t\tc[0] += 'a' - 'A';\n\t\t\t\tif (c[1] >= 'A' && c[1] <= 'Z')\n\t\t\t\t\tc[1] += 'a' - 'A';\n\t\t\t\tif (c[0] == d[0] && c[1] == d[1])\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (il != nl) {\n\t\t\t\tuint16_t typ;\n\t\t\n\t\t\t\ttyp = kLanguageTagType;\n\t\t\t\til++;\n\t\t\t\tnumType = CFNumberCreate(NULL, kCFNumberSInt16Type,\n\t\t\t\t\t(const SInt16 *) (&typ));\n\t\t\t\tnumSelector = CFNumberCreate(NULL, kCFNumberCFIndexType,\n\t\t\t\t\t&il);\n\t\t\t\tvalues[0] = numType;\n\t\t\t\tvalues[1] = numSelector;\n\t\t\t\tinnerDict = CFDictionaryCreate(NULL,\n\t\t\t\t\tkeys, values, 2,\n\t\t\t\t\t// TODO are these correct?\n\t\t\t\t\t&kCFCopyStringDictionaryKeyCallBacks,\n\t\t\t\t\t&kCFTypeDictionaryValueCallBacks);\n\t\t\t\tif (innerDict == NULL) {\n\t\t\t\t\t// TODO\n\t\t\t\t}\n\t\t\t\tCFArrayAppendValue(outerArray, innerDict);\n\t\t\t\tCFRelease(innerDict);\n\t\t\t\tCFRelease(numSelector);\n\t\t\t\tCFRelease(numType);\n\t\t\t}\n\t\t\tCFRelease(languages);\n\t\t}\n\t}\n\n\tkeys[0] = kCTFontFeatureSettingsAttribute;\n\tvalues[0] = outerArray;\n\tinnerDict = CFDictionaryCreate(NULL,\n\t\tkeys, values, 1,\n\t\t// TODO are these correct?\n\t\t&kCFCopyStringDictionaryKeyCallBacks,\n\t\t&kCFTypeDictionaryValueCallBacks);\n\tCFRelease(outerArray);\n\tnew = CTFontDescriptorCreateCopyWithAttributes(desc, innerDict);\n\tCFRelease(desc);\n\tCFRelease(innerDict);\n\treturn new;\n}\n"
  },
  {
    "path": "_future/textlanguageattr/ui.h",
    "content": "after UnderlineColor, before feature tags\n\n\t// TODO document that this will also enable language-specific font features (TODO on DirectWrite too?)\n\t// TODO document that this should be strict BCP 47 form (A-Z, a-z, 0-9, and -) for maximum compatibility\n\tuiAttributeLanguage,\t\t// BCP 47 string\n"
  },
  {
    "path": "_future/unittest/checklist_attrstr",
    "content": "= attributed strings\nattribute lengths are rounded to complete unicode codepoints\nzero-length attributes are elided\nconsecutive attributes of the same type and value are merged\noverlapping attributes of different types do not split each other\noverlapping attributes of the same type but different values do split\nempty string is allowed\nempty string cannot have attributes\nfont family names are case-insensitive both in attributes and in descriptors\nattributes are unique throughout a Unicode codepoint, not just to UTF-8 bytes\ndefine what \"it is an error\" means in the case of uiFreeAttribute() and all uiAttributeValue() functions and constructors\ndoes uiAttributeFamily() return a normalized string\nshould uiNewAttributeBackground() be renamed to uiNewAttributeBackgroundColor() and likewise for the type constant\nshould underline colors just ignore non-custom component arguments\nshould any color getter function accept a NULL pointer\nwhat should uiAttributeUnderlineColor() do if the color type isn't Custom but the other pointers are non-NULL\nshould uiOpenTypeFeaturesGet() accept a NULL value pointer\nwhat happens if uiOpenTypeFeaturesForEach() is given a NULl function pointer\nshould FeaturesAttribute be changed to OpenTypeFeaturesAttribute and likewise for the type enum\nshould uiNewFeaturesAttribute() accept NULL\nshould uiNewFamilyAttribute() accept NULL\nit is an error in ForEach too\ninvalid values for uiDrawTextAlign\nempty text layouts have one line\nTODO figure out what to do if any field (particularly the font family name) in uiFontDescriptor is unset\n"
  },
  {
    "path": "_future/unittest/opentype_test.c",
    "content": "// 27 february 2018\n#ifndef TODO_TEST\n#error TODO this is where libui itself goes\n#endif\n#include <inttypes.h>\n#include \"testing.h\"\n\n#include <stddef.h>\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\ntypedef struct uiOpenTypeFeatures uiOpenTypeFeatures;\ntypedef int uiForEach;\nenum { uiForEachContinue, uiForEachStop };\ntypedef uiForEach (*uiOpenTypeFeaturesForEachFunc)(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value, void *data);\n#define uiprivNew(x) ((x *) malloc(sizeof (x)))\n#define uiprivAlloc(x,y) malloc(x)\n#define uiprivRealloc(x,y,z) realloc(x,y)\n#define uiprivFree free\n#include \"opentype.c\"\n\nstatic void freeOpenType(void *otf)\n{\n\tuiFreeOpenTypeFeatures((uiOpenTypeFeatures *) otf);\n}\n\ntestingTest(OpenTypeFeaturesAddGet)\n{\n\tuiOpenTypeFeatures *otf;\n\tint got;\n\tuint32_t value;\n\n\totf = uiNewOpenTypeFeatures();\n\ttestingTDefer(t, freeOpenType, otf);\n\tuiOpenTypeFeaturesAdd(otf, 'a', 'b', 'c', 'd', 12345);\n\tgot = uiOpenTypeFeaturesGet(otf, 'a', 'b', 'c', 'd', &value);\n\n\tif (!got)\n\t\ttestingTErrorf(t, \"uiOpenTypeFeaturesGet() failed to get feature we added\");\n\telse if (value != 12345)\n\t\ttestingTErrorf(t, \"feature abcd: got %\" PRIu32 \", want 12345\", value);\n}\n\ntestingTest(OpenTypeFeaturesRemove)\n{\n\tuiOpenTypeFeatures *otf;\n\tuint32_t value;\n\n\totf = uiNewOpenTypeFeatures();\n\ttestingTDefer(t, freeOpenType, otf);\n\tuiOpenTypeFeaturesAdd(otf, 'a', 'b', 'c', 'd', 12345);\n\tuiOpenTypeFeaturesRemove(otf, 'a', 'b', 'c', 'd');\n\n\tif (uiOpenTypeFeaturesGet(otf, 'a', 'b', 'c', 'd', &value))\n\t\ttestingTErrorf(t, \"uiOpenTypeFeaturesGet() succeeded in getting deleted feature; value %\" PRIu32, value);\n}\n\ntestingTest(OpenTypeFeaturesCloneAdd)\n{\n\tuiOpenTypeFeatures *otf, *otf2;\n\tuint32_t value;\n\n\totf = uiNewOpenTypeFeatures();\n\ttestingTDefer(t, freeOpenType, otf);\n\tuiOpenTypeFeaturesAdd(otf, 'a', 'b', 'c', 'd', 12345);\n\totf2 = uiOpenTypeFeaturesClone(otf);\n\ttestingTDefer(t, freeOpenType, otf2);\n\tuiOpenTypeFeaturesAdd(otf2, 'q', 'w', 'e', 'r', 56789);\n\n\tif (uiOpenTypeFeaturesGet(otf, 'q', 'w', 'e', 'r', &value))\n\t\ttestingTErrorf(t, \"uiOpenTypeFeaturesGet() on original succeeded in getting feature added to clone; value %\" PRIu32, value);\n}\n\ntestingTest(OpenTypeFeaturesCloneModify)\n{\n\tuiOpenTypeFeatures *otf, *otf2;\n\tuint32_t value;\n\n\totf = uiNewOpenTypeFeatures();\n\ttestingTDefer(t, freeOpenType, otf);\n\tuiOpenTypeFeaturesAdd(otf, 'a', 'b', 'c', 'd', 12345);\n\totf2 = uiOpenTypeFeaturesClone(otf);\n\ttestingTDefer(t, freeOpenType, otf2);\n\tuiOpenTypeFeaturesAdd(otf2, 'a', 'b', 'c', 'd', 56789);\n\n\tuiOpenTypeFeaturesGet(otf, 'a', 'b', 'c', 'd', &value);\n\tif (value != 12345)\n\t\ttestingTErrorf(t, \"uiOpenTypeFeaturesGet() on original: got %\" PRIu32 \", want 12345\", value);\n\tuiOpenTypeFeaturesGet(otf2, 'a', 'b', 'c', 'd', &value);\n\tif (value != 56789)\n\t\ttestingTErrorf(t, \"uiOpenTypeFeaturesGet() on clone: got %\" PRIu32 \", want 56789\", value);\n}\n\ntestingTest(OpenTypeFeaturesCloneRemove)\n{\n\tuiOpenTypeFeatures *otf, *otf2;\n\tuint32_t value;\n\n\totf = uiNewOpenTypeFeatures();\n\ttestingTDefer(t, freeOpenType, otf);\n\tuiOpenTypeFeaturesAdd(otf, 'a', 'b', 'c', 'd', 12345);\n\totf2 = uiOpenTypeFeaturesClone(otf);\n\ttestingTDefer(t, freeOpenType, otf2);\n\tuiOpenTypeFeaturesRemove(otf2, 'a', 'b', 'c', 'd');\n\n\tif (uiOpenTypeFeaturesGet(otf2, 'a', 'b', 'c', 'd', &value))\n\t\ttestingTErrorf(t, \"uiOpenTypeFeaturesGet() on clone succeeded in getting feature removed from clone; value %\" PRIu32, value);\n\tif (!uiOpenTypeFeaturesGet(otf, 'a', 'b', 'c', 'd', &value))\n\t\ttestingTErrorf(t, \"uiOpenTypeFeaturesGet() on original failed to get feature removed from clone\");\n}\n\nint main(void)\n{\n\treturn testingMain();\n}\n"
  },
  {
    "path": "_future/unittest/testing.h",
    "content": "// 27 february 2018\n\n// TODO\n// - 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\n// - also in the above: note the unspecified order of data in the sub-segments...\n\n#ifndef testingprivIncludeGuard_testing_h\n#define testingprivIncludeGuard_testing_h\n\n#include <stdarg.h>\n\n#undef testingprivBadLanguageVersion\n#ifdef __cplusplus\n// 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...\n// TODO what if __cplusplus is blank (maybe only in that case, since IIRC C++98 requires __cplusplus to have a value)?\n#if __cplusplus < 201103L\n#define testingprivBadLanguageVersion\n#endif\n#elif !defined(__STDC_VERSION__)\n#define testingprivBadLanguageVersion\n#elif __STDC_VERSION__ < 199901L\n#define testingprivBadLanguageVersion\n#endif\n#ifdef testingprivBadLanguageVersion\n#error sorry, TODO requires either C99 or C++11; cannot continue\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#ifdef __cplusplus\n#define testingprivMkScaffold(name) \\\n\tstatic inline void testingprivScaffold ## name(testingT *t) \\\n\t{ \\\n\t\tbool failedNow = false, skippedNow = false; \\\n\t\ttry { name(t); } \\\n\t\tcatch (testingprivFailNowException e) { failedNow = true; } \\\n\t\tcatch (testingprivSkipNowException e) { skippedNow = true; } \\\n\t\t/* TODO see if we should catch other exceptions too */ \\\n\t\t/* don't call these in the catch blocks as they call longjmp() */ \\\n\t\tif (failedNow) testingprivTDoFailNow(t); \\\n\t\tif (skippedNow) testingprivTDoSkipNow(t); \\\n\t}\n#else\n#define testingprivMkScaffold(name) \\\n\tstatic inline void testingprivScaffold ## name(testingT *t) { name(t); }\n#endif\n\n// references:\n// - https://gitlab.gnome.org/GNOME/glib/blob/master/glib/gconstructor.h\n// - https://gitlab.gnome.org/GNOME/glib/blob/master/gio/glib-compile-resources.c\n// - https://msdn.microsoft.com/en-us/library/bb918180.aspx\n#if defined(__cplusplus)\n#define testingprivMkCtor(name, reg) \\\n\tstatic reg ## Class testingprivCtor ## name(#name, testingprivScaffold ## name);\n#elif defined(__GNUC__)\n#define testingprivMkCtor(name, reg) \\\n\t__attribute__((constructor)) static void testingprivCtor ## name(void) { reg(#name, testingprivScaffold ## name); }\n#elif defined(_MSC_VER)\n#define testingprivMkCtorPrototype(name, reg) \\\n\tstatic int name(void) testingprivCtor ## name(void) { reg(#name, testingprivScaffold ## name); return 0; } \\\n\t__pragma(section(\".CRT$XCU\",read)) \\\n\t__declspec(allocate(\".CRT$XCU\")) static int (*testingprivCtorPtr ## name)(void) = testingprivCtor ## name;\n#elif defined(__SUNPRO_C)\n#define testingprivMkCtor(name, reg) \\\n\t_Pragma(\"init(testingprivCtor\" #name \")\") static void testingprivCtor ## name(void) { reg(#name, testingprivScaffold ## name); }\n#else\n#error unknown compiler for making constructors in C; cannot continue\n#endif\n\n#define testingTest(Name) \\\n\tvoid Test ## Name(testingT *t); \\\n\ttestingprivMkScaffold(Test ## Name) \\\n\ttestingprivMkCtor(Test ## Name, testingprivRegisterTest) \\\n\tvoid Test ## Name(testingT *t)\n\nextern int testingMain(void);\n\ntypedef struct testingT testingT;\n#define testingTLogf(t, ...) \\\n\ttestingprivExpand(testingprivTLogfThen((void), t, __VA_ARGS__))\n#define testingTLogvf(t, format, ap) \\\n\ttestingprivTLogvfThen((void), t, format, ap)\n#define testingTErrorf(t, ...) \\\n\ttestingprivExpand(testingprivTLogfThen(testingTFail, t, __VA_ARGS__))\n#define testingTErrorvf(t, format, ap) \\\n\ttestingprivTLogvfThen(testingTFail, t, format, ap)\n#define testingTFatalf(t, ...) \\\n\ttestingprivExpand(testingprivTLogfThen(testingTFailNow, t, __VA_ARGS__))\n#define testingTFatalvf(t, format, ap) \\\n\ttestingprivTLogvfThen(testingTFailNow, t, format, ap)\n#define testingTSkipf(t, ...) \\\n\ttestingprivExpand(testingprivTLogfThen(testingTSkipNow, t, __VA_ARGS__))\n#define testingTSkipvf(t, format, ap) \\\n\ttestingprivTLogvfThen(testingTSkipNow, t, format, ap)\nextern void testingTFail(testingT *t);\n#ifdef __cplusplus\n#define testingTFailNow(t) (throw testingprivFailNowException())\n#define testingTSkipNow(t) (throw testingprivSkipNowException())\n#else\n#define testingTFailNow(t) (testingprivTDoFailNow(t))\n#define testingTSkipNow(t) (testingprivTDoSkipNow(t))\n#endif\n// TODO should the defered function also have t passed to it?\nextern void testingTDefer(testingT *t, void (*f)(void *data), void *data);\n\n// TODO IEEE 754 helpers\n// references:\n// - https://www.sourceware.org/ml/libc-alpha/2009-04/msg00005.html\n// - https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html\n// - https://stackoverflow.com/questions/5085533/is-a-c-preprocessor-identical-to-a-c-preprocessor\n\n// TODO should __LINE__ arguments use intmax_t or uintmax_t instead of int?\nextern void testingprivRegisterTest(const char *, void (*)(testingT *));\n// see https://stackoverflow.com/questions/32399191/va-args-expansion-using-msvc\n#define testingprivExpand(x) x\n#define testingprivTLogfThen(then, t, ...) ((testingprivTLogfFull(t, __FILE__, __LINE__, __VA_ARGS__)), (then(t)))\n#define testingprivTLogvfThen(then, t, format, ap) ((testingprivTLogvfFull(t, __FILE__, __LINE__, format, ap)), (then(t)))\nextern void testingprivTLogfFull(testingT *, const char *, int, const char *, ...);\nextern void testingprivTLogvfFull(testingT *, const char *, int, const char *, va_list);\nextern void testingprivTDoFailNow(testingT *);\nextern void testingprivTDoSkipNow(testingT *);\n\n#ifdef __cplusplus\n}\nnamespace {\n\tclass testingprivFailNowException {};\n\tclass testingprivSkipNowException {};\n\tclass testingprivRegisterTestClass {\n\tpublic:\n\t\ttestingprivRegisterTestClass(const char *name, void (*f)(testingT *)) { testingprivRegisterTest(name, f); }\n\t};\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "_future/unittest/testing_testing.c",
    "content": "// 27 february 2018\n#include <stdio.h>\n#include <stdlib.h>\n#include <setjmp.h>\n#include \"testing.h\"\n\n#define testingprivNew(T) ((T *) malloc(sizeof (T)))\n\nstruct defer {\n\tvoid (*f)(void *);\n\tvoid *data;\n\tstruct defer *next;\n};\n\nstruct testingT {\n\tconst char *name;\n\tvoid (*f)(testingT *);\n\tint failed;\n\tint skipped;\n\tjmp_buf returnNowBuf;\n\tstruct defer *defers;\n\tint defersRun;\n\ttestingT *next;\n};\n\nstatic testingT *tests = NULL;\n\nvoid testingprivRegisterTest(const char *name, void (*f)(testingT *))\n{\n\ttestingT *t;\n\n\tt = testingprivNew(testingT);\n\tt->name = name;\n\tt->f = f;\n\tt->failed = 0;\n\tt->skipped = 0;\n\tt->defers = NULL;\n\tt->defersRun = 0;\n\t// TODO add in the order called\n\tt->next = tests;\n\ttests = t;\n}\n\nstatic void runDefers(testingT *t)\n{\n\tstruct defer *d;\n\n\tif (t->defersRun)\n\t\treturn;\n\tt->defersRun = 1;\n\tfor (d = t->defers; d != NULL; d = d->next)\n\t\t(*(d->f))(d->data);\n}\n\nint testingMain(void)\n{\n\ttestingT *t;\n\tint anyFailed;\n\tconst char *status;\n\n\t// TODO see if this should run if all tests are skipped\n\tif (tests == NULL) {\n\t\tfprintf(stderr, \"warning: no tests to run\\n\");\n\t\t// imitate Go here (TODO confirm this)\n\t\treturn 0;\n\t}\n\n\tanyFailed = 0;\n\tfor (t = tests; t != NULL; t = t->next) {\n\t\tprintf(\"=== RUN   %s\\n\", t->name);\n\t\tif (setjmp(t->returnNowBuf) == 0)\n\t\t\t(*(t->f))(t);\n\t\trunDefers(t);\n\t\tstatus = \"PASS\";\n\t\tif (t->failed) {\n\t\t\tstatus = \"FAIL\";\n\t\t\tanyFailed = 1;\n\t\t} else if (t->skipped)\n\t\t\t// note that failed overrides skipped\n\t\t\tstatus = \"SKIP\";\n\t\tprintf(\"--- %s: %s (%s)\\n\", status, t->name, \"TODO\");\n\t}\n\n\tif (anyFailed) {\n\t\tprintf(\"FAIL\\n\");\n\t\treturn 1;\n\t}\n\tprintf(\"PASS\\n\");\n\treturn 0;\n}\n\nvoid testingprivTLogfFull(testingT *t, const char *file, int line, const char *format, ...)\n{\n\tva_list ap;\n\n\tva_start(ap, format);\n\ttestingprivTLogvfFull(t, file, line, format, ap);\n\tva_end(ap);\n}\n\nvoid testingprivTLogvfFull(testingT *t, const char *file, int line, const char *format, va_list ap)\n{\n\t// TODO extract filename from file\n\tprintf(\"\\t%s:%d: \", file, line);\n\t// TODO split into lines separated by \\n\\t\\t and trimming trailing empty lines\n\tvprintf(format, ap);\n\tprintf(\"\\n\");\n}\n\nvoid testingTFail(testingT *t)\n{\n\tt->failed = 1;\n}\n\nstatic void returnNow(testingT *t)\n{\n\t// run defers before calling longjmp() just to be safe\n\trunDefers(t);\n\tlongjmp(t->returnNowBuf, 1);\n}\n\nvoid testingprivTDoFailNow(testingT *t)\n{\n\ttestingTFail(t);\n\treturnNow(t);\n}\n\nvoid testingprivTDoSkipNow(testingT *t)\n{\n\tt->skipped = 1;\n\treturnNow(t);\n}\n\nvoid testingTDefer(testingT *t, void (*f)(void *data), void *data)\n{\n\tstruct defer *d;\n\n\td = testingprivNew(struct defer);\n\td->f = f;\n\td->data = data;\n\t// add to the head of the list so defers are run in reverse order of how they were added\n\td->next = t->defers;\n\tt->defers = d;\n}\n"
  },
  {
    "path": "_future/verticaltext/README",
    "content": "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).\n\nOn 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:\nhttp://www.unicode.org/notes/tn22/RobustVerticalLayout.pdf\nhttp://www.unicode.org/Public/vertical/revision-17/VerticalOrientation-17.txt\n\nIn 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.\nTODO investigate kCJKVerticalRomanPlacementType\n\nIf 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.\n\nMore links:\nhttps://www.w3.org/TR/2012/NOTE-jlreq-20120403/#line-composition\nhttps://www.w3.org/TR/REC-CSS2/notes.html\n\nTODO indicate where in the attributes.c file that block of code should go (or drop it entirely for the reasons listed above)\nTODO same for ui.h\n\nTODO vertical carets\n"
  },
  {
    "path": "_future/verticaltext/attrstr_darwin.m",
    "content": "\tcase uiAttributeVerticalForms:\n\t\tif (spec->Value != 0) {\n\t\t\tCFAttributedStringSetAttribute(p->mas, range, kCTVerticalFormsAttributeName, kCFBooleanTrue);\n//\t\t\tCFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassRoman);\n//\t\t\tCFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassIdeographicCentered);\n//\t\t\tCFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassIdeographicLow);\n//\t\t\tCFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassIdeographicHigh);\n//\t\t\tCFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassHanging);\n//\t\t\tCFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassMath);\n\t\t} else {\n\t\t\tCFAttributedStringSetAttribute(p->mas, range, kCTVerticalFormsAttributeName, kCFBooleanFalse);\n//\t\t\tCFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassRoman);\n//\t\t\tCFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassIdeographicCentered);\n//\t\t\tCFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassIdeographicLow);\n//\t\t\tCFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassIdeographicHigh);\n//\t\t\tCFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassHanging);\n//\t\t\tCFAttributedStringSetAttribute(p->mas, range, kCTBaselineClassAttributeName, kCTBaselineClassMath);\n\t\t}\n\t\tbreak;\n"
  },
  {
    "path": "_future/verticaltext/attrstr_unix.c",
    "content": "\tPangoGravity gravity;\n\n\tcase uiAttributeVerticalForms:\n\t\tgravity = PANGO_GRAVITY_SOUTH;\n\t\tif (spec->Value != 0)\n\t\t\tgravity = PANGO_GRAVITY_EAST;\n\t\taddattr(p, start, end,\n\t\t\tpango_attr_gravity_new(gravity));\n\t\tbreak;\n"
  },
  {
    "path": "_future/verticaltext/attrstr_windows.cpp",
    "content": "\tuint32_t vertval;\n\n\tcase uiAttributeVerticalForms:\n\t\t// LONGTERM 8 and/or 8.1 add other methods for vertical text\n\t\top.p = p;\n\t\top.start = start;\n\t\top.end = end;\n\t\tvertval = 0;\n\t\tif (spec->Value != 0)\n\t\t\tvertval = 1;\n\t\tdoOpenType(\"vert\", vertval, &op);\n\t\tdoOpenType(\"vrt2\", vertval, &op);\n\t\tdoOpenType(\"vkrn\", vertval, &op);\n\t\tdoOpenType(\"vrtr\", vertval, &op);\n\t\tbreak;\n"
  },
  {
    "path": "_future/verticaltext/common_attrlist.c",
    "content": "\tcase uiAttributeVerticalForms:\n\t\treturn boolsEqual(attr, spec);\n"
  },
  {
    "path": "_future/verticaltext/drawtext_example.c",
    "content": "\tnext = \"vertical glyph forms\";\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeVerticalForms;\n\tspec.Value = 1;\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \" (which you can draw rotated for proper vertical text; for instance, \");\n\tnext = \"\\xE3\\x81\\x82\\xE3\\x81\\x84\\xE3\\x81\\x86\\xE3\\x81\\x88\\xE3\\x81\\x8A\";\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeVerticalForms;\n\tspec.Value = 1;\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n"
  },
  {
    "path": "_future/verticaltext/ui.h",
    "content": "\t// TODO rename to uiAttributeVertical?\n\tuiAttributeVerticalForms,\t\t// 0 = off, 1 = on\n"
  },
  {
    "path": "_notes/OS2",
    "content": "https://twitter.com/OS2World/status/983822011389620224\n\tHi. 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\nhttps://twitter.com/OS2World/status/983822594465034240\n\tThere 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.\nhttp://www.edm2.com/index.php/Main_Page\nhttps://www.arcanoae.com/resources/downloadables/arca-noae-package-manager/\nhttp://www.edm2.com/index.php/IBM_OS/2_Toolkit_Documentation\nhttps://www.os2world.com/forum/index.php?topic=953.0\nhttps://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\nhttp://www.edm2.com/index.php/List_of_Presentation_Manager_Articles\nhttps://www.ecsoft2.org/\nhttp://www.edm2.com/index.php/Cairo\n\thttp://www.edm2.com/index.php/Doodle\nhttp://www.edm2.com/index.php/Workplace_Shell_Toolkit\nhttp://wpstk.netlabs.org/en/site/index.xml\nhttps://en.wikipedia.org/wiki/Workplace_Shell\nhttps://www.google.com/search?q=OS2+alphablending&ie=utf-8&oe=utf-8&client=firefox-b-1\nhttp://www.edm2.com/index.php/List_of_Multimedia_Articles\nalphablending:\n\thttp://www.osnews.com/story/369/Review-eComStation-OS2-1.0/page3/\n\thttp://halfos.ru/documentation/33-os2-api-documentation/67-opengl-os2-developer-reference-guide.html\n\thttp://www.altools.com/ALTools/ALSee/ALSee-Image-Viewer.aspx\n\thttp://www.os2voice.org/vnewsarc/bn2007122.html\n\thttp://www.mozillazine.org/talkback.html?article=194\n\thttps://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\n"
  },
  {
    "path": "_notes/azure-pipelines",
    "content": "multi-platform {\nhttps://docs.microsoft.com/en-us/azure/devops/pipelines/get-started-multiplatform?view=azure-devops\n}\n\nmicrosoft-hosted agents {\nhttps://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops&tabs=yaml#use-a-microsoft-hosted-agent\nhttps://docs.microsoft.com/en-us/azure/devops/pipelines/agents/v2-windows?view=azure-devops\nhttps://github.com/Microsoft/azure-pipelines-image-generation/blob/master/images/win/Vs2017-Server2016-Readme.md\nhttps://github.com/Microsoft/azure-pipelines-image-generation/blob/master/images/win/Vs2015-Server2012R2-Readme.md\n}\n\nmaximum number of processors (for ninja -j) {\nmsbuild supports this with the -m option natively; meanwhile:\nhttps://www.gnu.org/software/coreutils/manual/html_node/nproc-invocation.html\n}\n\npotentially useful tasks {\nhttps://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/utility/publish-pipeline-artifact?view=azure-devops\nhttps://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/build/visual-studio-build?view=azure-devops\nhttps://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/build/xcode?view=azure-devops\nhttps://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/utility/github-release?view=azure-devops\nhttps://docs.microsoft.com/en-us/azure/devops/extend/develop/add-build-task?view=azure-devops\n}\n\nmeson CI reference {\nhttps://github.com/mesonbuild/meson/blob/master/docs/markdown/Continuous-Integration.md\n}\n\njob templates, which will clean up our pipelines {\nhttps://docs.microsoft.com/en-us/azure/devops/pipelines/process/templates?view=azure-devops\n}\n\nmigrating from travis {\nhttps://docs.microsoft.com/en-us/azure/devops/pipelines/migrate/from-travis?view=azure-devops\n}\n\n32-bit {\nhttps://github.com/numpy/numpy/issues/12856\nhttps://github.com/numpy/numpy/pull/12863/files\nhttps://github.com/numpy/numpy/blob/master/azure-pipelines.yml (also explains what the @s mean; thanks guys)\n}\n\nusing visual studio tools {\nhttps://docs.microsoft.com/en-us/dotnet/framework/tools/developer-command-prompt-for-vs\nhttps://docs.microsoft.com/en-us/cpp/build/building-on-the-command-line?view=vs-2019#developer_command_file_locations\n}\n\nOLD using visual studio tools; also some docker {\nhttps://github.com/mesonbuild/meson/blob/master/ci/azure-steps.yml\nhttps://github.com/reactiveui/Akavache/blob/master/azure-pipelines.yml\nhttps://github.com/vector-of-bool/CMakeCM/blob/master/azure-pipelines.yml\nhttps://docs.microsoft.com/en-us/visualstudio/install/advanced-build-tools-container?view=vs-2019\nhttps://docs.microsoft.com/en-us/visualstudio/install/build-tools-container?view=vs-2019\nhttps://devblogs.microsoft.com/cppblog/using-msvc-in-a-docker-container-for-your-c-projects/\nhttps://devblogs.microsoft.com/cppblog/finding-the-visual-c-compiler-tools-in-visual-studio-2017/\n}\n\nreference {\nhttps://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema#task\nhttps://docs.microsoft.com/en-us/azure/devops/pipelines/apps/windows/cpp?view=azure-devops\nhttps://docs.microsoft.com/en-us/azure/devops/pipelines/languages/python?view=azure-devops\nhttps://docs.microsoft.com/en-us/azure/devops/pipelines/process/conditions?view=azure-devops&tabs=yaml\n}\n\nC++ binary compatibility and shared libraries (TODO split into its own notes file) {\nhttps://docs.microsoft.com/en-us/cpp/porting/binary-compat-2015-2017?view=vs-2017\n}\n\nothers I forgot why I had them open {\nhttps://github.com/Microsoft/cpprestsdk/blob/master/azure-pipelines.yml (32-bit?)\nhttps://docs.microsoft.com/en-us/azure/devops/release-notes/2018/sprint-142-update (I forget, maybe also 32-bit?)\n}\n\nothers that might not be relevant {\nhttps://github.com/Microsoft/azure-pipelines-agent/blob/master/docs/start/envubuntu.md has instructions on how to build the agent tool itself\n}\n"
  },
  {
    "path": "_notes/caretWidths",
    "content": "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\n"
  },
  {
    "path": "_notes/cplusplus",
    "content": "https://blogs.msdn.microsoft.com/oldnewthing/20181226-00/?p=100565\n"
  },
  {
    "path": "_notes/darwinAutoLayout",
    "content": "https://developer.apple.com/library/mac/documentation/AppKit/Reference/NSLayoutConstraint_Class/\n\thttps://developer.apple.com/documentation/uikit/nslayoutconstraint?language=objc\nhttps://developer.apple.com/library/mac/documentation/UserExperience/Conceptual/AutolayoutPG/ProgrammaticallyCreatingConstraints.html#//apple_ref/doc/uid/TP40010853-CH16-SW1\n\thttps://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/ProgrammaticallyCreatingConstraints.html#//apple_ref/doc/uid/TP40010853-CH16-SW1 (Listing 13-3)\nhttps://developer.apple.com/library/mac/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithScrollViews.html#//apple_ref/doc/uid/TP40010853-CH24-SW1\n\thttps://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)\n"
  },
  {
    "path": "_notes/darwinNSAlertIcons",
    "content": "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:\nhttp://0xced.blogspot.com/2009/11/clalert-nsalert-done-right.html\nhttps://gist.github.com/0xced/228140\nhttp://editra.org/uploads/code/artmac.html\nhttp://mirror.informatimago.com/next/developer.apple.com/documentation/Carbon/Reference/IconServices/index.html\nhttp://www.cocoabuilder.com/archive/cocoa/15427-iconref-to-nsimage.html\nhttps://github.com/lukakerr/Swift-NSUserNotificationPrivate\nhttps://stackoverflow.com/questions/32943220/the-sidebar-icon-image-name-in-osx\n"
  },
  {
    "path": "_notes/dialogs",
    "content": "https://github.com/kusti8/proton-native/issues/47#issuecomment-373068947\nhttps://blogs.kde.org/2009/03/26/how-crash-almost-every-qtkde-application-and-how-fix-it-0\n"
  },
  {
    "path": "_notes/highDPI",
    "content": "High DPI Displays | Qt 5.5 http://doc.qt.io/qt-5/highdpi.html bottom of page(?)\n"
  },
  {
    "path": "_notes/i18n",
    "content": "https://msdn.microsoft.com/en-us/library/windows/desktop/dd319079(v=vs.85).aspx\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/dd318103(v=vs.85).aspx\nhttps://stackoverflow.com/questions/4663855/is-there-a-repository-for-localized-common-text-in-winforms\nhttps://stackoverflow.com/questions/2502375/find-localized-windows-strings\nhttps://docs.microsoft.com/en-us/windows-hardware/customize/mobile/mcsf/create-a-resource-only-dll-for-localized-strings\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/ee845043(v=vs.85).aspx\nhttps://msdn.microsoft.com/en-us/library/cc194807.aspx\nhttps://www.codeproject.com/Articles/10542/Easily-Load-and-Format-Strings-from-the-String-Tab\nhttps://www.codeproject.com/Tips/431045/The-inner-working-of-FindResource-and-LoadString-W\nhttps://mihai-nita.net/2007/05/03/how-to-localize-an-rc-file/\nhttps://www.microsoft.com/en-us/language\nhttps://www.microsoft.com/en-us/language/Terminology\nhttps://www.microsoft.com/en-us/language/LicenseAgreement\nhttps://www.microsoft.com/en-us/language/Translations\nhttp://www.ttt.org/oscarstandards/tbx/\nhttps://blogs.msdn.microsoft.com/oldnewthing/20181122-00/?p=100295\n"
  },
  {
    "path": "_notes/macosAlternateHiDPI",
    "content": "https://developer.apple.com/documentation/corefoundation/1542764-cfurlcopyresourcepropertyforkey?language=objc\nhttps://developer.apple.com/documentation/foundation/nsargumentdomain?language=objc\nhttps://developer.apple.com/documentation/appkit/1428499-nsapplicationmain\nhttps://webkit.googlesource.com/WebKit/+/9ae3deefb7df48bd85f01edcf8382ee300eafbd4%5E!/\nhttps://stackoverflow.com/questions/11181324/how-to-change-retina-display-system-preferences-in-osx\nhttps://web.archive.org/web/20150828053709/http://garethjenkins.com/2012/07/01/investigating-a-high-resolution-retina-utility-for-macbook-pro-1x-and-2x-modes/\nhttps://github.com/avibrazil/RDM\nhttps://www.google.com/search?client=firefox-b-1-d&q=IOFramebuffer+subclass\nhttps://developer.apple.com/documentation/kernel/ioframebuffer?language=objc\nhttps://stackoverflow.com/questions/51846999/how-to-write-macos-display-driver\nhttps://twitter.com/kenkeiter/status/3631378994298882\nhttp://en.ennowelbers.info/node/33\nhttps://www.insanelymac.com/forum/topic/114528-how-to-compile-a-driver-from-source/\nhttps://github.com/mkernel/EWProxyFramebuffer\nhttps://stackoverflow.com/questions/46904493/ioframebuffer-cant-access-vram-framebuffer-in-macos-10-13\nhttps://www.google.com/search?client=firefox-b-1-d&q=IOFramebuffer+10.13\nhttps://github.com/codykrieger/gfxCardStatus/issues/296\nhttps://plugable.com/2018/03/30/macos-10-13-4-disables-displaylink-duet-display-devices/\nhttps://www.tekrevue.com/tip/hidpi-mode-os-x/\nhttps://medium.com/@ivan.ha/how-to-mimic-a-2k-monitor-as-retina-display-in-macos-sierra-using-hidpi-f53d87630c48\nhttps://www.google.com/search?client=firefox-b-1-d&q=IOFramebuffer+10.13\nhttps://www.quora.com/What-is-the-underlying-reason-if-DisplayLink-is-no-longer-working-properly-on-Mac-OS\nhttps://github.com/Siguza/iokit-utils\n"
  },
  {
    "path": "_notes/misc",
    "content": "windows data types, \"open specifications\" on msdn https://msdn.microsoft.com/en-us/library/cc230321.aspx\n\t(I usually use https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx for windows data types)\n\nwindows platform update for windows 7\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/jj863687(v=vs.85).aspx\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/hh802478(v=vs.85).aspx\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/hh802480(v=vs.85).aspx\n\nDWM, header bars, toolbars\nhttps://stackoverflow.com/questions/41106347/why-is-my-dwmextendframeintoclientaread-window-not-drawing-the-dwm-borders/41125616#41125616\nhttps://developer.gnome.org/hig/stable/header-bars.html.en\nhttps://developer.apple.com/library/content/documentation/UserExperience/Conceptual/OSXHIGuidelines/WindowTitleBarToolbar.html#//apple_ref/doc/uid/20000957-CH39-SW1\nhttps://developer.apple.com/library/content/documentation/UserExperience/Conceptual/OSXHIGuidelines/ControlsAll.html#//apple_ref/doc/uid/20000957-CH46-SW2\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/bb787329(v=vs.85).aspx\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/bb787334(v=vs.85).aspx\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/bb787337(v=vs.85).aspx\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/cc835034(v=vs.85).aspx\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/bb787345(v=vs.85).aspx\n\nrendering\nhttps://www.youtube.com/watch?v=UUfXWzp0-DU\n\ntext input on windows\nhttps://blogs.msdn.microsoft.com/oldnewthing/20121025-00/?p=6253\nhttps://www.google.com/search?q=translatemessage+site%3Ahttp%3A%2F%2Farchives.miloush.net%2Fmichkap%2Farchive%2F&ie=utf-8&oe=utf-8\nhttp://archives.miloush.net/michkap/archive/2008/04/22/8415843.html\nhttp://archives.miloush.net/michkap/archive/2007/03/25/1948887.html\nhttp://archives.miloush.net/michkap/archive/2006/09/10/748775.html\nhttp://archives.miloush.net/michkap/archive/2004/11/27/270931.html\nhttps://stackoverflow.com/questions/41334851/since-translatemessage-returns-nonzero-unconditionally-how-can-i-tell-either\nhttp://stackoverflow.com/questions/41334851/since-translatemessage-returns-nonzero-unconditionally-how-can-i-tell-either?noredirect=1#comment70107257_41334851\n\ntext layouts\nhttps://developer.apple.com/reference/coretext/2110184-ctframe?language=objc\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/dd368203(v=vs.85).aspx\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/dd368205(v=vs.85).aspx\nhttps://developer.gnome.org/pango/1.30/pango-Layout-Objects.html#pango-layout-set-font-description\nhttps://developer.gnome.org/pango/1.30/pango-Fonts.html#PangoWeight-enum\nhttps://git.gnome.org/browse/pango/tree/pango/pangocoretext-fontmap.c (code for small caps)\nhttps://bugzilla.gnome.org/show_bug.cgi?id=766148\nhttps://developer.apple.com/documentation/coretext/ctline?preferredLanguage=occ\nhttps://developer.apple.com/reference/coretext/1508876-ctlinegetstringindexforposition?language=objc\nhttps://developer.apple.com/library/content/documentation/StringsTextFonts/Conceptual/CoreText_Programming/Introduction/Introduction.html#//apple_ref/doc/uid/TP40005533-CH1-SW1\nhttps://developer.apple.com/reference/coretext/2110184-ctframe?language=objc\nhttps://developer.apple.com/reference/coretext/2168885-ctline?language=objc\nhttps://developer.apple.com/library/content/documentation/StringsTextFonts/Conceptual/CoreText_Programming/LayoutOperations/LayoutOperations.html\nhttp://asciiwwdc.com/2013/sessions/205\nhttps://developer.apple.com/reference/coretext/1511332-ctlinegetboundswithoptions?language=objc\nhttps://stackoverflow.com/questions/19709751/ctlinegetboundswithoptions-what-does-the-returned-frame-origin-y-value-mean?rq=1\nhttps://imgur.com/a/lqhzC\nhttps://imgur.com/a/hYeOL\nhttps://www.google.com/search?q=ctline+%22paragraph+spacing%22&ie=utf-8&oe=utf-8\nhttp://stackoverflow.com/questions/8010615/how-do-i-determine-the-of-a-ctline-following-a-ctline\nhttps://imgur.com/a/dlpm2\nhttps://stackoverflow.com/questions/41601845/am-i-missing-something-in-core-text-that-will-let-me-see-which-line-a-certain-po\nhttps://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\nhttps://github.com/macvim-dev/macvim/issues/400#issuecomment-274292877\nhttps://bugs.webkit.org/show_bug.cgi?id=68287\nhttps://trac.webkit.org/changeset/95391\nhttps://trac.webkit.org/changeset/95391/trunk/Source/WebCore/platform/graphics/mac/ComplexTextControllerCoreText.cpp\nhttp://stackoverflow.com/questions/41857213/is-there-any-way-i-can-get-precise-metrics-line-ascent-line-descent-etc-of\nhttp://pawlan.com/monica/articles/texttutorial/int.html\n\nenter in entry fields, possibly other text layout issues, possibly keyboard shortcuts\nhttps://developer.gnome.org/gtk3/3.10/GtkEntry.html \"activate\" signal\nhttps://gitlab.gnome.org/GNOME/gtk/blob/gtk-3-10/gtk/gtkentry.c (not sure where)\nhttps://developer.gnome.org/gtk3/3.10/GtkTextView.html signals section of contents\nhttps://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)\nhttps://developer.gnome.org/gtk3/3.10/gtk3-Bindings.html\nhttps://gitlab.gnome.org/GNOME/gtk/blob/gtk-3-10/gtk/gtkwidget.c (not sure where)\nhttps://gitlab.gnome.org/GNOME/gtk/blob/gtk-3-10/gtk/gtkbindings.c (not sure where)\nhttps://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\nhttps://gitlab.gnome.org/GNOME/gnome-builder/tree/master/libide/sourceview/ide-source-view.c likewise\nhttps://gitlab.gnome.org/GNOME/gnome-builder/tree/master/libide/sourceview/ide-source-view-mode.c#n323 likewise\n\nrgb int->float conversion\nhttps://stackoverflow.com/questions/41348339/how-to-convert-rgb-to-hexadecimal-using-gtk?noredirect=1#comment69903262_41348339\n\nwindows fonts, hi-dpi\nhttps://stackoverflow.com/questions/41448320/dlgtemplateex-and-ds-shellfont-what-about-point-size\n\nwindows default fonts\nhttps://stackoverflow.com/questions/41505151/how-to-draw-text-with-the-default-ui-font-in-directwrite#41505750\n\nuwp stuff\nhttps://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/inking-controls\nhttps://msdn.microsoft.com/en-us/windows/uwp/controls-and-patterns/master-details\n\ncmake stuff\nhttps://public.kitware.com/pipermail/cmake/2010-January/034669.html\nhttps://cmake.org/cmake/help/v3.0/command/string.html\nhttps://cmake.org/pipermail/cmake/2003-April/003599.html\n\nopentype features and locales\nhttps://www.microsoft.com/typography/otspec/featurelist.htm\nhttps://en.wikipedia.org/wiki/List_of_typographic_features\nhttps://www.microsoft.com/typography/otspec160/scripttags.htm\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/dd371503(v=vs.85).aspx\n\ngspell (TODO figure out what I wanted from this; possibly spelling checking)\nhttps://git.gnome.org/browse/gspell/tree/gspell/gspell-inline-checker-text-buffer.c\nhttps://git.gnome.org/browse/gtk+/tree/gtk/gtktextview.c?h=gtk-3-10\n\nother assorted notes on text\nhttps://mail.gnome.org/archives/gtk-devel-list/2016-March/msg00037.html\nhttps://mail.gnome.org/archives/gtk-devel-list/2016-June/msg00007.html\nhttps://blogs.msdn.microsoft.com/tsfaware/\nhttps://stackoverflow.com/questions/40453186/what-is-the-correct-modern-way-to-handle-arbitrary-text-input-in-a-custom-contr\nhttps://www.microsoft.com/typography/fonts/family.aspx\n\nwindows ribbon\nhttps://twitter.com/_Ninji/status/814918189708668929 (C08rw9TWgAMpLkC.jpg:large)\n\nhttps://developer.apple.com/library/content/samplecode/CocoaTipsAndTricks/Introduction/Intro.html#//apple_ref/doc/uid/DTS40010039 BlurryView and StaticObjcMethods in particular\nprinting https://developer.apple.com/library/content/samplecode/PMPrinterPrintWithFile/Introduction/Intro.html#//apple_ref/doc/uid/DTS10003958\nauto layout https://developer.apple.com/library/content/releasenotes/UserExperience/RNAutomaticLayout/index.html#//apple_ref/doc/uid/TP40010631\n\nother stuff, mostly custom draw on windows\nhttps://github.com/pauldotknopf/WindowsSDK7-Samples/blob/master/multimedia/DirectWrite/ChooseFont/ChooseFont.cpp at ChooseFontDialog::OnFontSizeNameEdit() definition\nhttps://developer.gnome.org/gtk3/3.10/GtkColorButton.html\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/bb775951(v=vs.85).aspx\n\thttps://msdn.microsoft.com/en-us/library/windows/desktop/bb775951(v=vs.85).aspx between BS_LEFTTEXT and BS_RADIOBUTTON\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/bb775923(v=vs.85).aspx\n\thttps://msdn.microsoft.com/en-us/library/windows/desktop/bb775923(v=vs.85).aspx\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/bb775802(v=vs.85).aspx\n\thttps://msdn.microsoft.com/en-us/library/windows/desktop/bb775802(v=vs.85).aspx\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/bb761837(v=vs.85).aspx\n\thttps://msdn.microsoft.com/en-us/library/windows/desktop/bb761837(v=vs.85).aspx\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/bb761847(v=vs.85).aspx\n\thttps://msdn.microsoft.com/en-us/library/windows/desktop/bb761847(v=vs.85).aspx description + parameters\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/bb775483(v=vs.85).aspx\n\thttps://msdn.microsoft.com/en-us/library/windows/desktop/bb775483(v=vs.85).aspx dwItemSpec parameter\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/bb775951(v=vs.85).aspx#BS_NOTIFY\n\thttps://msdn.microsoft.com/en-us/library/windows/desktop/bb775951(v=vs.85).aspx#BS_NOTIFY BS_NOTIFY to BS_RIGHT\nhttp://stackoverflow.com/questions/17678261/how-to-change-color-of-a-button-while-keeping-buttons-functions-in-win32\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/ff919569(v=vs.85).aspx\n\thttps://msdn.microsoft.com/en-us/library/windows/desktop/ff919569(v=vs.85).aspx\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/ff919573(v=vs.85).aspx\n\thttps://msdn.microsoft.com/en-us/library/windows/desktop/ff919573(v=vs.85).aspx switch (pnm->hdr.code){\nhttp://stackoverflow.com/questions/36756094/will-the-device-context-of-a-child-window-have-the-same-text-metrics-and-text-ex\nactually 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...\n\nmore miscellaneous\nhttps://blogs.msdn.microsoft.com/oldnewthing/20160328-00/?p=93204 from \"One is virtual memory\" to \"occured to them that their\"\nhttps://blogs.msdn.microsoft.com/oldnewthing/20160331-00/?p=93231\n\thttps://msdn.microsoft.com/en-us/library/windows/desktop/aa373631(v=vs.85).aspx\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/ms648395(v=vs.85).aspx\n\thttps://msdn.microsoft.com/en-us/library/windows/desktop/ms648395(v=vs.85).aspx\nhttps://blogs.msdn.microsoft.com/oldnewthing/20101118-00/?p=12253#comment-875273 \"MTA implies poor service and fare hikes.\"\n\nfrom #gtk+\n[18:06:48]  <zorcon>\tif 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())\n[18:12:47]  <~Company>\tzorcon: yes\n\nwindows stuff: mostly aero tricks, sdk version macro notes, ribbon stuff, scrollbar stuff too\nhttps://tools.stefankueng.com/SendMessage.html\nhttps://sourceforge.net/p/sendmessage/code/HEAD/tree/trunk/src/SendMessage.vcxproj\nhttps://sourceforge.net/p/sendmessage/code/HEAD/tree/trunk/src/stdafx.h\nhttps://sourceforge.net/p/sendmessage/code/HEAD/tree/trunk/src/MainDlg.h\nhttps://sourceforge.net/u/steveking/profile/\nhttps://github.com/stefankueng/BowPad/tree/master/src + sourceforge original\nhttps://github.com/stefankueng/sktoolslib/blob/master/AeroControls.cpp + sourceforge original\nhttps://tools.stefankueng.com/BowPad.html\nhttps://www.codeproject.com/Articles/1084/Custom-Scrollbar-Library-version-1-1#_articleTop\nhttps://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\n\thttps://searchcode.com/codesearch/view/7295148/\n\thttps://github.com/TortoiseGit/tortoisesvn/blob/master/src/Utils/MiscUI/AeroBaseDlg.h\n\thttps://github.com/TortoiseGit/tortoisesvn/blob/master/src/Utils/MiscUI/AeroControls.cpp\n\thttps://github.com/gavioto/stexbar/blob/master/Misc/FileTool/src/CleanVerifyDlg.h\n\nwindows ribbon colors and dwm customization\nhttps://github.com/stefankueng/BowPad/blob/master/src/MainWindow.cpp\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/dd940487(v=vs.85).aspx\nhttps://github.com/yohei-yoshihara/GameOfLife3D/blob/master/GameOfLife3DLib/gameoflife3d/Ribbon.cpp\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/dd940404(v=vs.85).aspx\nhttps://github.com/stefankueng/sktoolslib/blob/master/AeroColors.cpp\nhttps://gist.github.com/emoacht/bfa852ccc16bdb5465bd\nhttps://stackoverflow.com/questions/4258295/aero-how-to-draw-solid-opaque-colors-on-glass\nhttps://github.com/ComponentFactory/Krypton\nhttps://www.codeproject.com/Articles/620045/Custom-Controls-in-Win-API-Visual-Styles\nhttps://blogs.msdn.microsoft.com/wpf/2010/11/30/systemcolors-reference/\nhttps://stackoverflow.com/questions/25639621/check-when-a-user-changes-windows-glass-brush-theme-color\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/aa969513(v=vs.85).aspx\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/aa969527(v=vs.85).aspx\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/dd388198(v=vs.85).aspx\nI hope the MFC ribbon can have customizable colors too, otherwise I'll have to do some serious image processing...\n\nwindows debugging\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/hh780351(v=vs.85).aspx\n\thttps://msdn.microsoft.com/en-us/library/windows/desktop/ff476881(v=vs.85).aspx#Debug\n\thttps://msdn.microsoft.com/en-us/library/windows/desktop/hh780343(v=vs.85).aspx\n\thttps://msdn.microsoft.com/en-us/library/windows/desktop/dn457937(v=vs.85).aspx\n\nmore OS2 stuff\nhttps://www.google.com/search?q=%22ibm+graphics+development+toolkit%22&ie=utf-8&oe=utf-8&client=firefox-b-1\nhttps://www.google.com/search?q=%22os%2F2+graphics+development+toolkit%22&ie=utf-8&oe=utf-8&client=firefox-b-1\nhttp://www.edm2.com/index.php/IBM_OS/2_Developer%27s_Packages\n\n[17:48:52]  <Chloe>\tandlabs: I got splitView and NSWindow size restoration working btw\n[17:49:09]  <Chloe>\tandlabs: see https://github.com/eintw1ck/mail/blob/master/Sources/mail/MainViewController.swift for splitView\n\nold stuff\nfont1.gif (GIF Image, 424 × 475 pixels) http://www.functionx.com/win32/controls/dlgboxes/font1.gif\nInskcape’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\")\n\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/ms632615(v=vs.85).aspx we need to handle this alongisde WM_CAPTURECHANGED\n\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/dn424996%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396\nwhatever this is seems to have no documentation... it appears to be related to  UWP, if not only Store apps...\n\nunreachable code optimizations\nhttps://docs.microsoft.com/en-us/cpp/intrinsics/assume?view=vs-2017\nhttps://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html\n"
  },
  {
    "path": "_notes/rebarstuff",
    "content": "https://docs.microsoft.com/en-us/windows/desktop/uxguide/cmd-toolbars\nhttps://docs.microsoft.com/en-us/windows/desktop/controls/cc-faq-iemenubar\nhttps://docs.microsoft.com/en-us/windows/desktop/controls/cc-faq-ietoolbar\nhttps://docs.microsoft.com/en-us/windows/desktop/controls/create-rebar-controls\nhttps://docs.microsoft.com/en-us/windows/desktop/controls/create-toolbars\nhttps://docs.microsoft.com/en-us/windows/desktop/controls/handle-drop-down-buttons\nhttps://docs.microsoft.com/en-us/windows/desktop/controls/tb-buttonstructsize\n https://www.google.com/search?q=winapi+toolbar+dropdown+position&ie=utf-8&oe=utf-8&client=firefox-b-1\nhttps://www.google.com/search?q=winapi+toolbar+dropdown+arrow+position&ie=utf-8&oe=utf-8&client=firefox-b-1\n"
  },
  {
    "path": "_notes/tableNotes",
    "content": "https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Protocols/NSOutlineViewDataSource_Protocol/index.html#//apple_ref/occ/intfm/NSOutlineViewDataSource/outlineView:child:ofItem:\nhttps://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSOutlineViewDelegate_Protocol/index.html#//apple_ref/occ/intfm/NSOutlineViewDelegate/outlineView:didAddRowView:forRow:\n\thttps://developer.apple.com/documentation/appkit/nsoutlineviewdelegate/1528320-outlineview?language=objc\nhttps://github.com/mity/mctrl/blob/master/mctrl/treelist.c around treelist_set_subitem()\n"
  },
  {
    "path": "_notes/textSelections",
    "content": "http://www.catch22.net/tuts/transparent-text\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/ms724371(v=vs.85).aspx\n\tTODO which color did I want from this list\n"
  },
  {
    "path": "_notes/winARM64",
    "content": "http://www.os2museum.com/wp/windows-10-arm64-on-qemu/\nhttps://docs.microsoft.com/en-us/windows/uwp/porting/apps-on-arm\nhttp://pete.akeo.ie/2017/05/compiling-desktop-arm-applications-with.html\n"
  },
  {
    "path": "_notes/windowsHighDPI",
    "content": "https://msdn.microsoft.com/en-us/library/windows/desktop/dn469266(v=vs.85).aspx\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/mt744321(v=vs.85).aspx\n!!!! http://stackoverflow.com/questions/41917279/do-child-windows-have-the-same-dpi-as-their-parents-in-a-per-monitor-aware-appli\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/mt843498(v=vs.85).aspx\nhttps://msdn.microsoft.com/library/windows/desktop/mt791579(v=vs.85).aspx\nhttps://blogs.windows.com/buildingapps/2017/04/04/high-dpi-scaling-improvements-desktop-applications-windows-10-creators-update/\nhttps://channel9.msdn.com/Events/Windows/Windows-Developer-Day-Creators-Update/High-DPI-Improvements-for-Desktop-Developers\nhttps://social.msdn.microsoft.com/Forums/vstudio/en-US/31d2a89f-3518-403e-b1e4-bbe37ac1b0f0/per-monitor-high-dpi-awareness-wmdpichanged?forum=vcgeneral\nhttps://stackoverflow.com/questions/36864894/scaling-the-non-client-area-title-bar-menu-bar-for-per-monitor-high-dpi-suppo/37624363\nhttps://stackoverflow.com/questions/41448320/dlgtemplateex-and-ds-shellfont-what-about-point-size\nhttp://stackoverflow.com/questions/41917279/do-child-windows-have-the-same-dpi-as-their-parents-in-a-per-monitor-aware-appli\n\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/dn469266(v=vs.85).aspx\n\thttps://msdn.microsoft.com/library/windows/desktop/mt843498(v=vs.85).aspx(d=robot)\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/dn469266(v=vs.85).aspx#appendix_c_common_high_dpi_issues\n\thttps://msdn.microsoft.com/library/windows/desktop/mt843498(v=vs.85).aspx(d=robot)#appendix_c_common_high_dpi_issues\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/dn469266(v=vs.85).aspx#addressing_high_dpi_issues\n\thttps://msdn.microsoft.com/library/windows/desktop/mt843498(v=vs.85).aspx(d=robot)#addressing_high_dpi_issues\n\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/dn302215(v=vs.85).aspx\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/hh802769(v=vs.85).aspx\n"
  },
  {
    "path": "_notes/windowsPrinting",
    "content": "https://msdn.microsoft.com/en-us/library/windows/desktop/ff686805(v=vs.85).aspx\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/dn495653(v=vs.85).aspx\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/hh448422(v=vs.85).aspx\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/dd316975(v=vs.85).aspx\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/dd372919(v=vs.85).aspx\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/ee264335(v=vs.85).aspx\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/dd145198(v=vs.85).aspx\n"
  },
  {
    "path": "_notes/windowsUWPGlass",
    "content": "https://twitter.com/omgubuntu/status/962765197109841922\nhttps://github.com/DominicMaas/SoundByte/blob/master/SoundByte.UWP/Resources/Brushes.xaml\nhttps://docs.microsoft.com/en-us/windows/uwp/design/style/acrylic\nhttps://www.google.com/search?q=uwp+acrylic+winapi&ie=utf-8&oe=utf-8&client=firefox-b-1\nhttps://stackoverflow.com/questions/43931709/acrylic-material-in-win32-app\nhttps://stackoverflow.com/questions/44000217/mimicking-acrylic-in-a-win32-app\nhttps://stackoverflow.com/questions/43699256/how-to-use-acrylic-accent-in-windows-10-creators-update\nhttps://stackoverflow.com/questions/39135834/how-to-call-uwp-api-from-converted-win32-app-desktop-app-converter\nhttps://stackoverflow.com/questions/32724187/how-do-you-set-the-glass-blend-colour-on-windows-10\nhttps://channel9.msdn.com/Events/Build/2017/B8034\nhttps://www.reddit.com/r/Windows10/comments/7lzyzc/since_the_taskbar_is_still_win32_and_it_can_have/\nhttps://thenextweb.com/microsoft/2017/05/15/microsoft-fluent-design-system-breaking-windows-10s-new-look/\nhttps://github.com/bbougot/AcrylicWPF\nhttp://vhanla.codigobit.info/2015/07/enable-windows-10-aero-glass-aka-blur.html\nhttp://undoc.airesoft.co.uk/user32.dll/SetWindowCompositionAttribute.php\nhttp://undoc.airesoft.co.uk/user32.dll/GetWindowCompositionAttribute.php\nhttps://msdn.microsoft.com/en-us/library/windows/desktop/aa969530(v=vs.85).aspx\nhttps://github.com/bbougot/AcrylicWPF/blob/master/MainWindow.xaml.cs\nhttps://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=\nhttps://github.com/sgothel/jogl/blob/master/src/nativewindow/native/win32/WindowsDWM.c\nhttp://www.brandonfa.lk/win8/win8_devrel_head_x86/uxtheme.h\nhttp://www.brandonfa.lk/win8/\nhttps://github.com/gamozolabs/pdblister\nhttps://github.com/wbenny/pdbex\nhttps://github.com/wbenny/pdbex/releases\n\nhttps://stackoverflow.com/questions/44000217/mimicking-acrylic-in-a-win32-app\nhttps://github.com/riverar/sample-win32-acrylicblur\nhttps://github.com/riverar/sample-win10-aeroglass\nhttps://guerra24blog.wordpress.com/2017/12/20/windows-10-use-acrylic-in-win32-apps/\nhttps://news.ycombinator.com/item?id=14432951\nhttps://gist.github.com/ethanhs/0e157e4003812e99bf5bc7cb6f73459f ACCENTPOLICY\nhttps://github.com/TranslucentTB/Tools\nhttps://withinrafael.com/2015/07/08/adding-the-aero-glass-blur-to-your-windows-10-apps/\nhttps://withinrafael.com/2018/02/01/adding-acrylic-blur-to-your-windows-10-apps-redstone-4-desktop-apps/\n\ndiversion: DwmEnableBlurBehindWindow {\nhttps://docs.microsoft.com/en-us/windows/desktop/api/dwmapi/nf-dwmapi-dwmenableblurbehindwindow\nhttps://docs.microsoft.com/en-us/windows/desktop/api/dwmapi/ns-dwmapi-_dwm_blurbehind\nhttps://docs.microsoft.com/en-us/windows/desktop/api/dwmapi/nf-dwmapi-dwmenableblurbehindwindow\nhttp://www.danielmoth.com/Blog/Vista-Glass-Answers-And-DwmEnableBlurBehindWindow.aspx\nhttp://www.danielmoth.com/Blog/Glass-In-C-An-Alternative-Approach.aspx\nhttp://www.danielmoth.com/Blog/Vista-Glass-In-C.aspx\nhttp://www.danielmoth.com/Blog/Glass-In-C-An-Alternative-Approach.aspx\nhttps://weblogs.asp.net/kennykerr/Windows-Vista-for-Developers-_1320_-Part-3-_1320_-The-Desktop-Window-Manager\n}\n"
  },
  {
    "path": "_notes/winflags",
    "content": "\t# TODO is there a -Wno-switch equivalent?\n\t# TODO /sdl turns C4996 into an ERROR\n\t# don't use /analyze; that requires us to write annotations everywhere\n\t# TODO undecided flags from qo?\n\t# 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)\n\t# /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\n"
  },
  {
    "path": "_wip/attrstr_metrics/common_OLD_drawtext.c",
    "content": "// 10 february 2017\n#include \"../ui.h\"\n#include \"uipriv.h\"\n\n// TODO this doesn't handle the case where nLines == 0\n// TODO this should never happen even if there are no characters??\n// TODO figure out how to make this work on GTK+\nvoid uiDrawCaret(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout, size_t pos, int *line)\n{\n\tdouble xoff;\n\tuiDrawTextLayoutLineMetrics m;\n\tstruct caretDrawParams cdp;\n\tuiDrawPath *path;\n\tuiDrawBrush brush;\n\n\tif (*line < 0)\n\t\t*line = 0;\n\tif (*line > (uiDrawTextLayoutNumLines(layout) - 1))\n\t\t*line = (uiDrawTextLayoutNumLines(layout) - 1);\n\t// TODO cap pos\n\txoff = uiDrawTextLayoutByteLocationInLine(layout, pos, *line);\n\twhile (xoff < 0) {\n\t\tsize_t start, end;\n\n\t\tuiDrawTextLayoutLineByteRange(layout, *line, &start, &end);\n\t\tif (end < pos)\t\t// too far up\n\t\t\t(*line)++;\n\t\telse\n\t\t\t(*line)--;\n\t\txoff = uiDrawTextLayoutByteLocationInLine(layout, pos, *line);\n\t}\n\tuiDrawTextLayoutLineGetMetrics(layout, *line, &m);\n\n\tcaretDrawParams(c, m.Height, &cdp);\n\n\tuiDrawSave(c);\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathAddRectangle(path,\n\t\t// TODO add m.X?\n\t\tx + xoff - cdp.xoff, y + m.Y,\n\t\tcdp.width, m.Height);\n\tuiDrawPathEnd(path);\n\tbrush.Type = uiDrawBrushTypeSolid;\n\tbrush.R = cdp.r;\n\tbrush.G = cdp.g;\n\tbrush.B = cdp.b;\n\tbrush.A = cdp.a;\n\tuiDrawFill(c, path, &brush);\n\tuiDrawFreePath(path);\n\n\tuiDrawRestore(c);\n}\n\nvoid drawTextBackground(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout, size_t start, size_t end, uiDrawBrush *brush, int isSelection)\n{\n\tint line, nLines;\n\tsize_t lstart, lend;\n\tdouble layoutwid, layoutht;\n\n\tuiDrawTextLayoutExtents(layout, &layoutwid, &layoutht);\n\tnLines = uiDrawTextLayoutNumLines(layout);\n\tfor (line = 0; line < nLines; line++) {\n\t\tuiDrawTextLayoutLineByteRange(layout, line, &lstart, &lend);\n\t\tif (start >= lstart && start < lend)\n\t\t\tbreak;\n\t}\n\twhile (end - start > 0) {\n\t\tuiDrawTextLayoutLineMetrics m;\n\t\tdouble startx, endx;\n\t\tuiDrawPath *path;\n\n\t\tuiDrawTextLayoutLineByteRange(layout, line, &lstart, &lend);\n\t\tif (lend > end)\t\t// don't cross lines\n\t\t\tlend = end;\n\t\tstartx = uiDrawTextLayoutByteLocationInLine(layout, start, line);\n\t\t// TODO explain this; note the use of start with lend\n\t\tendx = layoutwid;\n\t\tif (!isSelection || end <= lend)\n\t\t\tendx = uiDrawTextLayoutByteLocationInLine(layout, lend, line);\n\t\tuiDrawTextLayoutLineGetMetrics(layout, line, &m);\n\t\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\t\tuiDrawPathAddRectangle(path,\n\t\t\tx + startx, y + m.Y,\n\t\t\tendx - startx, m.Height);\n\t\tuiDrawPathEnd(path);\n\t\tuiDrawFill(c, path, brush);\n\t\tuiDrawFreePath(path);\n\t\tline++;\n\t\tstart = lend;\n\t}\n}\n"
  },
  {
    "path": "_wip/attrstr_metrics/common_OLD_uipriv_attrstr.h",
    "content": "\n\n// TODO split these into a separate header file?\n\n// drawtext.c\nstruct caretDrawParams {\n\tdouble r;\n\tdouble g;\n\tdouble b;\n\tdouble a;\n\tdouble xoff;\n\tdouble width;\n};\nextern void caretDrawParams(uiDrawContext *c, double height, struct caretDrawParams *p);\nextern void drawTextBackground(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout, size_t start, size_t end, uiDrawBrush *brush, int isSelection);\n"
  },
  {
    "path": "_wip/attrstr_metrics/darwin_OLD__appkit_drawtext.m",
    "content": "// 2 january 2017\n#import \"uipriv_darwin.h\"\n#import \"draw.h\"\n\n@interface lineInfo : NSObject\n@property NSRange glyphRange;\n@property NSRange characterRange;\n@property NSRect lineRect;\n@property NSRect lineUsedRect;\n@property NSRect glyphBoundingRect;\n@property CGFloat baselineOffset;\n@property double ascent;\n@property double descent;\n@property double leading;\n@end\n\n@implementation lineInfo\n@end\n\nstruct uiDrawTextLayout {\n\t// NSTextStorage is subclassed from NSMutableAttributedString\n\tNSTextStorage *attrstr;\n\tNSTextContainer *container;\n\tNSLayoutManager *layoutManager;\n\n\t// the width as passed into uiDrawTextLayout constructors\n\tdouble width;\n\n#if 0 /* TODO */\n\t// the *actual* size of the frame\n\t// note: technically, metrics returned from frame are relative to CGPathGetPathBoundingBox(tl->path)\n\t// 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\n\t// (this I confirmed through experimentation)\n\t// so we can just use tl->size for adjustments\n\t// we don't need to adjust coordinates by any origin since our rect origin is (0, 0)\n\tCGSize size;\n#endif\n\n\tNSMutableArray<lineInfo *> *lineInfo;\n\n\t// for converting CFAttributedString indices to byte offsets\n\tsize_t *u16tou8;\n\tsize_t nu16tou8;\t\t// TODO I don't like the casing of this name\n};\n\nstatic NSFont *fontdescToNSFont(uiDrawFontDescriptor *fd)\n{\n\tNSFontDescriptor *desc;\n\tNSFont *font;\n\n\tdesc = fontdescToNSFontDescriptor(fd);\n\tfont = [NSFont fontWithDescriptor:desc size:fd->Size];\n\t[desc release];\n\treturn font;\n}\n\nstatic NSTextStorage *attrstrToTextStorage(uiAttributedString *s, uiDrawFontDescriptor *defaultFont)\n{\n\tNSString *nsstr;\n\tNSMutableDictionary *defaultAttrs;\n\tNSTextStorage *attrstr;\n\n\tnsstr = [[NSString alloc] initWithCharacters:attrstrUTF16(s)\n\t\tlength:attrstrUTF16Len(s)];\n\n\tdefaultAttrs = [NSMutableDictionary new];\n\t[defaultAttrs setObject:fontdescToNSFont(defaultFont)\n\t\tforKey:NSFontAttributeName];\n\n\tattrstr = [[NSTextStorage alloc] initWithString:nsstr\n\t\tattributes:defaultAttrs];\n\t[defaultAttrs release];\n\t[nsstr release];\n\n\t[attrstr beginEditing];\n\t// TODO copy in the attributes\n\t[attrstr endEditing];\n\n\treturn attrstr;\n}\n\n// TODO fine-tune all the properties\nuiDrawTextLayout *uiDrawNewTextLayout(uiAttributedString *s, uiDrawFontDescriptor *defaultFont, double width)\n{\n\tuiDrawTextLayout *tl;\n\tCGFloat cgwidth;\n\t// TODO correct type?\n\tNSUInteger index;\n\n\ttl = uiNew(uiDrawTextLayout);\n\ttl->attrstr = attrstrToTextStorage(s, defaultFont);\n\ttl->width = width;\n\n\t// TODO the documentation on the size property implies this might not be necessary?\n\tcgwidth = (CGFloat) width;\n\tif (cgwidth < 0)\n\t\tcgwidth = CGFLOAT_MAX;\n\t// TODO rename to tl->textContainer\n\ttl->container = [[NSTextContainer alloc] initWithSize:NSMakeSize(cgwidth, CGFLOAT_MAX)];\n\t// TODO pull the reference for this\n\t[tl->container setLineFragmentPadding:0];\n\n\ttl->layoutManager = [[NSLayoutManager alloc] init];\n\t[tl->layoutManager setTypesetterBehavior:NSTypesetterLatestBehavior];\n\n\t[tl->layoutManager addTextContainer:tl->container];\n\t[tl->attrstr addLayoutManager:tl->layoutManager];\n\t// and force a re-layout (TODO get source\n\t[tl->layoutManager glyphRangeForTextContainer:tl->container];\n\n\t// TODO equivalent of CTFrameProgression for RTL/LTR?\n\n\t// now collect line information; see https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/TextLayout/Tasks/CountLines.html\n\ttl->lineInfo = [NSMutableArray<lineInfo *> new];\n\tindex = 0;\n\twhile (index < [tl->layoutManager numberOfGlyphs]) {\n\t\tNSRange glyphRange;\n\t\t__block lineInfo *li;\n\n\t\tli = [lineInfo new];\n\t\tli.lineRect = [tl->layoutManager lineFragmentRectForGlyphAtIndex:index effectiveRange:&glyphRange];\n\t\tli.glyphRange = glyphRange;\n\t\tli.characterRange = [tl->layoutManager characterRangeForGlyphRange:li.glyphRange actualGlyphRange:NULL];\n\t\tli.lineUsedRect = [tl->layoutManager lineFragmentUsedRectForGlyphAtIndex:index effectiveRange:NULL];\n\t\tli.glyphBoundingRect = [tl->layoutManager boundingRectForGlyphRange:li.glyphRange inTextContainer:tl->container];\n\t\t// 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\n\t\tli.baselineOffset = [[tl->layoutManager typesetter] baselineOffsetInLayoutManager:tl->layoutManager glyphIndex:index];\n\t\tli.ascent = 0;\n\t\tli.descent = 0;\n\t\tli.leading = 0;\n\t\t// imitate what AppKit actually does (or seems to)\n\t\t[tl->attrstr enumerateAttributesInRange:li.characterRange options:0 usingBlock:^(NSDictionary<NSString *,id> *attrs, NSRange range, BOOL *stop) {\n\t\t\tNSFont *f;\n\t\t\tNSRect boundingRect;\n\t\t\tdouble v, realAscent, realDescent, realLeading;\n\t\t\tBOOL skipAdjust, doAdjust;\n\n\t\t\tf = (NSFont *) [attrs objectForKey:NSFontAttributeName];\n\t\t\tif (f == nil) {\n\t\t\t\tf = [NSFont fontWithName:@\"Helvetica\" size:12.0];\n\t\t\t\tif (f == nil)\n\t\t\t\t\tf = [NSFont systemFontOfSize:12.0];\n\t\t\t}\n\n\t\t\tboundingRect = [f boundingRectForFont];\n\t\t\trealAscent = [f ascender];\n\t\t\trealDescent = -[f descender];\n\t\t\trealLeading = [f leading];\n\t\t\tskipAdjust = NO;\n\t\t\tdoAdjust = NO;\n\t\t\tif (NSMaxY(boundingRect) <= realAscent) {\n\t\t\t\t// ascent entirely within bounding box\n\t\t\t\t// 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)\n\t\t\t\tif (realLeading != 0)\n\t\t\t\t\tskipAdjust = YES;\n\t\t\t\t// does the descent slip outside the bounding box?\n\t\t\t\tif (-realDescent <= NSMinY(boundingRect))\n\t\t\t\t\t// 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...\n\t\t\t\t\tskipAdjust = YES;\n\t\t\t} else {\n\t\t\t\t// ascent outside bounding box — ascent does not include accents\n\t\t\t\t// only skip adjustment if there isn't leading (apparently some fonts use the previous line's leading for accents? :/ )\n\t\t\t\tif (realLeading != 0)\n\t\t\t\t\tskipAdjust = YES;\n\t\t\t}\n\t\t\tif (!skipAdjust) {\n\t\t\t\tUniChar ch = 0xC0;\n\t\t\t\tCGGlyph glyph;\n\n\t\t\t\t// there does not seem to be an AppKit API for this...\n\t\t\t\tif (CTFontGetGlyphsForCharacters((CTFontRef) f, &ch, &glyph, 1) != false) {\n\t\t\t\t\tNSRect bbox;\n\n\t\t\t\t\tbbox = [f boundingRectForGlyph:glyph];\n\t\t\t\t\tif (NSMaxY(bbox) > realAscent)\n\t\t\t\t\t\tdoAdjust = YES;\n\t\t\t\t\tif (-realDescent > NSMinY(bbox))\n\t\t\t\t\t\tdoAdjust = YES;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// TODO vertical\n\n\t\t\tv = [f ascender];\n\t\t\t// TODO get this one back out\n\t\t\tif (doAdjust)\n\t\t\t\tv += 0.2 * ([f ascender] + [f descender]);\n\t\t\t//v = floor(v + 0.5);\n\t\t\tif (li.ascent < v)\n\t\t\t\tli.ascent = v;\n\n\t\t\tv = -[f descender];// floor(-[f descender] + 0.5);\n\t\t\tif (li.descent < v)\n\t\t\t\tli.descent = v;\n\n\t\t\tv = [f leading];//floor([f leading] + 0.5);\n\t\t\tif (li.leading < v)\n\t\t\t\tli.leading = v;\n\t\t}];\n\t\tli.ascent = floor(li.ascent + 0.5);\n\t\tli.descent = floor(li.descent + 0.5);\n\t\tli.leading = floor(li.leading + 0.5);\n\t\t[tl->lineInfo addObject:li];\n\t\t[li release];\n\t\tindex = glyphRange.location + glyphRange.length;\n\t}\n\n\t// and finally copy the UTF-16 to UTF-8 index conversion table\n\ttl->u16tou8 = attrstrCopyUTF16ToUTF8(s, &(tl->nu16tou8));\n\n\treturn tl;\n}\n\nvoid uiDrawFreeTextLayout(uiDrawTextLayout *tl)\n{\n\tuiFree(tl->u16tou8);\n\t[tl->lineInfo release];\n\t[tl->layoutManager release];\n\t[tl->container release];\n\t[tl->attrstr release];\n\tuiFree(tl);\n}\n\n// TODO document that (x,y) is the top-left corner of the *entire frame*\nvoid uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)\n{\n\tNSGraphicsContext *gc;\n\n\tCGContextFlush(c->c);\t\t// just to be safe\n\t[NSGraphicsContext saveGraphicsState];\n\tgc = [NSGraphicsContext graphicsContextWithGraphicsPort:c->c flipped:YES];\n\t[NSGraphicsContext setCurrentContext:gc];\n\n\t// TODO is this the right point?\n\t// TODO does this draw with the proper default styles?\n\t[tl->layoutManager drawGlyphsForGlyphRange:[tl->layoutManager glyphRangeForTextContainer:tl->container]\n\t\tatPoint:NSMakePoint(x, y)];\n\n\t[gc flushGraphics];\t\t// just to be safe\n\t[NSGraphicsContext restoreGraphicsState];\n\t// TODO release gc?\n}\n\n// TODO update all of these {\n// 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\n// TODO width doesn't include trailing whitespace...\n// TODO figure out how paragraph spacing should play into this\n// }\nvoid uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height)\n{\n\tNSRect r;\n\n\t// see https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/TextLayout/Tasks/StringHeight.html\n\tr = [tl->layoutManager usedRectForTextContainer:tl->container];\n\t*width = r.size.width;\n\t*height = r.size.height;\n}\n\nint uiDrawTextLayoutNumLines(uiDrawTextLayout *tl)\n{\n\treturn [tl->lineInfo count];\n}\n\nvoid uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end)\n{\n\tlineInfo *li;\n\n\tli = (lineInfo *) [tl->lineInfo objectAtIndex:line];\n\t*start = tl->u16tou8[li.characterRange.location];\n\t*end = tl->u16tou8[li.characterRange.location + li.characterRange.length];\n}\n\nvoid uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m)\n{\n\tlineInfo *li;\n\n\tli = (lineInfo *) [tl->lineInfo objectAtIndex:line];\n\n\tm->X = li.lineRect.origin.x;\n\tm->Y = li.lineRect.origin.y;\n\t// if we use li.lineRect here we get the whole line, not just the part with stuff in it\n\tm->Width = li.lineUsedRect.size.width;\n\tm->Height = li.lineRect.size.height;\n\n\t// TODO is this correct?\n\tm->BaselineY = (m->Y + m->Height) - li.baselineOffset;\n\tm->Ascent = li.ascent;\n\tm->Descent = li.descent;\n\tm->Leading = li.leading;\n\n\t// TODO\n\tm->ParagraphSpacingBefore = 0;\n\tm->LineHeightSpace = 0;\n\tm->LineSpacing = 0;\n\tm->ParagraphSpacing = 0;\n}\n\nvoid uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, uiDrawTextLayoutHitTestResult *result)\n{\n#if 0 /* TODO */\n\tCFIndex i;\n\tCTLineRef line;\n\tCFIndex pos;\n\n\tif (y >= 0) {\n\t\tfor (i = 0; i < tl->nLines; i++) {\n\t\t\tdouble ltop, lbottom;\n\n\t\t\tltop = tl->lineMetrics[i].Y;\n\t\t\tlbottom = ltop + tl->lineMetrics[i].Height;\n\t\t\tif (y >= ltop && y < lbottom)\n\t\t\t\tbreak;\n\t\t}\n\t\tresult->YPosition = uiDrawTextLayoutHitTestPositionInside;\n\t\tif (i == tl->nLines) {\n\t\t\ti--;\n\t\t\tresult->YPosition = uiDrawTextLayoutHitTestPositionAfter;\n\t\t}\n\t} else {\n\t\ti = 0;\n\t\tresult->YPosition = uiDrawTextLayoutHitTestPositionBefore;\n\t}\n\tresult->Line = i;\n\n\tresult->XPosition = uiDrawTextLayoutHitTestPositionInside;\n\tif (x < tl->lineMetrics[i].X) {\n\t\tresult->XPosition = uiDrawTextLayoutHitTestPositionBefore;\n\t\t// and forcibly return the first character\n\t\tx = tl->lineMetrics[i].X;\n\t} else if (x > (tl->lineMetrics[i].X + tl->lineMetrics[i].Width)) {\n\t\tresult->XPosition = uiDrawTextLayoutHitTestPositionAfter;\n\t\t// and forcibly return the last character\n\t\tx = tl->lineMetrics[i].X + tl->lineMetrics[i].Width;\n\t}\n\n\tline = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, i);\n\t// TODO copy the part from the docs about this point\n\tpos = CTLineGetStringIndexForPosition(line, CGPointMake(x, 0));\n\tif (pos == kCFNotFound) {\n\t\t// TODO\n\t}\n\tresult->Pos = tl->u16tou8[pos];\n#endif\n}\n\nvoid uiDrawTextLayoutByteRangeToRectangle(uiDrawTextLayout *tl, size_t start, size_t end, uiDrawTextLayoutByteRangeRectangle *r)\n{\n}\n"
  },
  {
    "path": "_wip/attrstr_metrics/darwin_OLD__appkit_fontmatch.m",
    "content": "// 3 january 2017\n#import \"uipriv_darwin.h\"\n\n// Stupidity: Core Text requires an **exact match for the entire traits dictionary**, otherwise it will **drop ALL the traits**.\n// 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\n// - CTFontDescriptorCreateMatchingFontDescriptors() (though the conditions here seem odd)\n// - CTFontCreateWithFontDescriptor()\n// - CTFontCreateCopyWithAttributes()\n// And as a bonus prize, this also applies to Cocoa's NSFontDescriptor methods as well!\n// We have to implement the closest match ourselves.\n// 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.\n\nstruct closeness {\n\tNSUInteger index;\n\tdouble weight;\n\tdouble italic;\n\tdouble stretch;\n\tdouble distance;\n};\n\nstatic double doubleAttr(NSDictionary *traits, NSString *attr)\n{\n\tNSNumber *n;\n\n\tn = (NSNumber *) [traits objectForKey:attr];\n\treturn [n doubleValue];\n}\n\nstruct italicCloseness {\n\tdouble normal;\n\tdouble oblique;\n\tdouble italic;\n};\n\n// remember that in closeness, 0 means exact\n// 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\"\nstatic const struct italicCloseness italicClosenesses[] = {\n\t[uiDrawTextItalicNormal] = { 0, 1, 1 },\n\t[uiDrawTextItalicOblique] = { 1, 0, 0.5 },\n\t[uiDrawTextItalicItalic] = { 1, 0.5, 0 },\n};\n\n// Italics are hard because Core Text does NOT distinguish between italic and oblique.\n// All Core Text provides is a slant value and the italic bit of the symbolic traits mask.\n// 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.\n// 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.)\n// 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)\n// TODO see if the above applies to Cocoa as well\nstatic double italicCloseness(NSFontDescriptor *desc, NSDictionary *traits, uiDrawTextItalic italic)\n{\n\tconst struct italicCloseness *ic = &(italicClosenesses[italic]);\n\tNSNumber *num;\n\tNSFontSymbolicTraits symbolic;\n\tNSString *styleName;\n\tNSRange range;\n\tBOOL isOblique;\n\n\tnum = (NSNumber *) [traits objectForKey:NSFontSymbolicTrait];\n\t// TODO this should really be a uint32_t-specific one\n\tsymbolic = (NSFontSymbolicTraits) [num unsignedIntegerValue];\n\tif ((symbolic & NSFontItalicTrait) == 0)\n\t\treturn ic->normal;\n\n\t// Okay, now we know it's either Italic or Oblique\n\t// 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\n\t// note that NSFontFaceAttribute is the Cocoa equivalent of the style name\n\tisOblique = NO;\t\t// default value\n\tstyleName = (NSString *) [desc objectForKey:NSFontFaceAttribute];\n\t// TODO is styleName guaranteed?\n\t// TODO NSLiteralSearch?\n\trange = [styleName rangeOfString:@\"Oblique\" options:NSCaseInsensitiveSearch];\n\tif (range.location != NSNotFound)\n\t\treturn ic->oblique;\n\treturn ic->italic;\n}\n\nstatic NSFontDescriptor *matchTraits(NSFontDescriptor *against, double targetWeight, uiDrawTextItalic targetItalic, double targetStretch)\n{\n\tNSArray<NSFontDescriptor *> *matching;\n\tNSUInteger i, n;\n\tstruct closeness *closeness;\n\tNSFontDescriptor *current;\n\tNSFontDescriptor *out;\n\n\tmatching = [against matchingFontDescriptorsWithMandatoryKeys:nil];\n\tif (matching == nil)\n\t\t// no matches; give the original back and hope for the best\n\t\treturn against;\n\tn = [matching count];\n\tif (n == 0) {\n\t\t// likewise\n//TODO\t\t[matching release];\n\t\treturn against;\n\t}\n\n\tcloseness = (struct closeness *) uiAlloc(n * sizeof (struct closeness), \"struct closeness[]\");\n\tfor (i = 0; i < n; i++) {\n\t\tNSDictionary *traits;\n\n\t\tcloseness[i].index = i;\n\t\tcurrent = (NSFontDescriptor *) [matching objectAtIndex:i];\n\t\ttraits = (NSDictionary *) [current objectForKey:NSFontTraitsAttribute];\n\t\tif (traits == nil) {\n\t\t\t// couldn't get traits; be safe by ranking it lowest\n\t\t\t// LONGTERM figure out what the longest possible distances are\n\t\t\tcloseness[i].weight = 3;\n\t\t\tcloseness[i].italic = 2;\n\t\t\tcloseness[i].stretch = 3;\n\t\t\tcontinue;\n\t\t}\n\t\tcloseness[i].weight = doubleAttr(traits, NSFontWeightTrait) - targetWeight;\n\t\tcloseness[i].italic = italicCloseness(current, traits, targetItalic);\n\t\tcloseness[i].stretch = doubleAttr(traits, NSFontWidthTrait) - targetStretch;\n\t\t// TODO release traits?\n\t}\n\n\t// now figure out the 3-space difference between the three and sort by that\n\t// TODO merge this loop with the previous loop?\n\tfor (i = 0; i < n; i++) {\n\t\tdouble weight, italic, stretch;\n\n\t\tweight = closeness[i].weight;\n\t\tweight *= weight;\n\t\titalic = closeness[i].italic;\n\t\titalic *= italic;\n\t\tstretch = closeness[i].stretch;\n\t\tstretch *= stretch;\n\t\tcloseness[i].distance = sqrt(weight + italic + stretch);\n\t}\n\tqsort_b(closeness, n, sizeof (struct closeness), ^(const void *aa, const void *bb) {\n\t\tconst struct closeness *a = (const struct closeness *) aa;\n\t\tconst struct closeness *b = (const struct closeness *) bb;\n\n\t\t// via http://www.gnu.org/software/libc/manual/html_node/Comparison-Functions.html#Comparison-Functions\n\t\t// 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; ?\n\t\treturn (a->distance > b->distance) - (a->distance < b->distance);\n\t});\n\t// and the first element of the sorted array is what we want\n\tout = (NSFontDescriptor *) [matching objectAtIndex:closeness[0].index];\n\t// TODO is this correct?\n\t[out retain];\t\t\t// get rule\n\n\t// release everything\n\tuiFree(closeness);\n//TODO\t[matching release];\n\t// and release the original descriptor since we no longer need it\n\t[against release];\n\n\treturn out;\n}\n\n// 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\n// 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\n// 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)\nstatic const double weightsToCTWeights[] = {\n\t-1.0,\t\t// 0..99\n\t-0.7,\t\t// 100..199\n\t-0.5,\t\t// 200..299\n\t-0.23,\t// 300..399\n\t0.0,\t\t// 400..499\n\t0.2,\t\t// 500..599\n\t0.3,\t\t// 600..699\n\t0.4,\t\t// 700..799\n\t0.6,\t\t// 800..899\n\t0.8,\t\t// 900..999\n\t1.0,\t\t// 1000\n};\n\nstatic double weightToCTWeight(uiDrawTextWeight weight)\n{\n\tint weightClass;\n\tdouble ctclass;\n\tdouble rest, weightFloor, nextFloor;\n\n\tif (weight <= 0)\n\t\treturn -1.0;\n\tif (weight >= 1000)\n\t\treturn 1.0;\n\n\tweightClass = weight / 100;\n\trest = (double) weight;\n\tweightFloor = (double) (weightClass * 100);\n\tnextFloor = (double) ((weightClass + 1) * 100);\n\trest = (rest - weightFloor) / (nextFloor - weightFloor);\n\n\tctclass = weightsToCTWeights[weightClass];\n\treturn fma(rest,\n\t\tweightsToCTWeights[weightClass + 1] - ctclass,\n\t\tctclass);\n}\n\n// 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)\nstatic const double stretchesToCTWidths[] = {\n\t[uiDrawTextStretchUltraCondensed] = -0.400000,\n\t[uiDrawTextStretchExtraCondensed] = -0.300000,\n\t[uiDrawTextStretchCondensed] = -0.200000,\n\t[uiDrawTextStretchSemiCondensed] = -0.100000,\n\t[uiDrawTextStretchNormal] = 0.000000,\n\t[uiDrawTextStretchSemiExpanded] = 0.100000,\n\t[uiDrawTextStretchExpanded] = 0.200000,\n\t[uiDrawTextStretchExtraExpanded] = 0.300000,\n\t// 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)\n\t[uiDrawTextStretchUltraExpanded] = 0.400000,\n};\n\nNSFontDescriptor *fontdescToNSFontDescriptor(uiDrawFontDescriptor *fd)\n{\n\tNSMutableDictionary *attrs;\n\tNSFontDescriptor *basedesc;\n\n\tattrs = [NSMutableDictionary new];\n\t[attrs setObject:[NSString stringWithUTF8String:fd->Family]\n\t\tforKey:NSFontFamilyAttribute];\n\t[attrs setObject:[NSNumber numberWithDouble:fd->Size]\n\t\tforKey:NSFontSizeAttribute];\n\n\tbasedesc = [[NSFontDescriptor alloc] initWithFontAttributes:attrs];\n\t[attrs release];\n\treturn matchTraits(basedesc,\n\t\tweightToCTWeight(fd->Weight),\n\t\tfd->Italic,\n\t\tstretchesToCTWidths[fd->Stretch]);\n}\n"
  },
  {
    "path": "_wip/attrstr_metrics/darwin_OLD__old_drawtext.m",
    "content": "// 6 september 2015\n#import \"uipriv_darwin.h\"\n\n// TODO double-check that we are properly handling allocation failures (or just toll free bridge from cocoa)\nstruct uiDrawFontFamilies {\n\tCFArrayRef fonts;\n};\n\nuiDrawFontFamilies *uiDrawListFontFamilies(void)\n{\n\tuiDrawFontFamilies *ff;\n\n\tff = uiNew(uiDrawFontFamilies);\n\tff->fonts = CTFontManagerCopyAvailableFontFamilyNames();\n\tif (ff->fonts == NULL)\n\t\timplbug(\"error getting available font names (no reason specified) (TODO)\");\n\treturn ff;\n}\n\nint uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff)\n{\n\treturn CFArrayGetCount(ff->fonts);\n}\n\nchar *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, int n)\n{\n\tCFStringRef familystr;\n\tchar *family;\n\n\tfamilystr = (CFStringRef) CFArrayGetValueAtIndex(ff->fonts, n);\n\t// toll-free bridge\n\tfamily = uiDarwinNSStringToText((NSString *) familystr);\n\t// Get Rule means we do not free familystr\n\treturn family;\n}\n\nvoid uiDrawFreeFontFamilies(uiDrawFontFamilies *ff)\n{\n\tCFRelease(ff->fonts);\n\tuiFree(ff);\n}\n\nstruct uiDrawTextFont {\n\tCTFontRef f;\n};\n\nuiDrawTextFont *mkTextFont(CTFontRef f, BOOL retain)\n{\n\tuiDrawTextFont *font;\n\n\tfont = uiNew(uiDrawTextFont);\n\tfont->f = f;\n\tif (retain)\n\t\tCFRetain(font->f);\n\treturn font;\n}\n\nuiDrawTextFont *mkTextFontFromNSFont(NSFont *f)\n{\n\t// toll-free bridging; we do retain, though\n\treturn mkTextFont((CTFontRef) f, YES);\n}\n\nstatic CFMutableDictionaryRef newAttrList(void)\n{\n\tCFMutableDictionaryRef attr;\n\n\tattr = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);\n\tif (attr == NULL)\n\t\tcomplain(\"error creating attribute dictionary in newAttrList()()\");\n\treturn attr;\n}\n\nstatic void addFontFamilyAttr(CFMutableDictionaryRef attr, const char *family)\n{\n\tCFStringRef cfstr;\n\n\tcfstr = CFStringCreateWithCString(NULL, family, kCFStringEncodingUTF8);\n\tif (cfstr == NULL)\n\t\tcomplain(\"error creating font family name CFStringRef in addFontFamilyAttr()\");\n\tCFDictionaryAddValue(attr, kCTFontFamilyNameAttribute, cfstr);\n\tCFRelease(cfstr);\t\t\t// dictionary holds its own reference\n}\n\nstatic void addFontSizeAttr(CFMutableDictionaryRef attr, double size)\n{\n\tCFNumberRef n;\n\n\tn = CFNumberCreate(NULL, kCFNumberDoubleType, &size);\n\tCFDictionaryAddValue(attr, kCTFontSizeAttribute, n);\n\tCFRelease(n);\n}\n\n#if 0\nTODO\n// 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\n// And fortunately, unlike the traits (see below), unmatched features are simply ignored without affecting the other features :D\nstatic void addFontSmallCapsAttr(CFMutableDictionaryRef attr)\n{\n\tCFMutableArrayRef outerArray;\n\tCFMutableDictionaryRef innerDict;\n\tCFNumberRef numType, numSelector;\n\tint num;\n\n\touterArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);\n\tif (outerArray == NULL)\n\t\tcomplain(\"error creating outer CFArray for adding small caps attributes in addFontSmallCapsAttr()\");\n\n\t// Apple's headers say these are deprecated, but a few fonts still rely on them\n\tnum = kLetterCaseType;\n\tnumType = CFNumberCreate(NULL, kCFNumberIntType, &num);\n\tnum = kSmallCapsSelector;\n\tnumSelector = CFNumberCreate(NULL, kCFNumberIntType, &num);\n\tinnerDict = newAttrList();\n\tCFDictionaryAddValue(innerDict, kCTFontFeatureTypeIdentifierKey, numType);\n\tCFRelease(numType);\n\tCFDictionaryAddValue(innerDict, kCTFontFeatureSelectorIdentifierKey, numSelector);\n\tCFRelease(numSelector);\n\tCFArrayAppendValue(outerArray, innerDict);\n\tCFRelease(innerDict);\t\t// and likewise for CFArray\n\n\t// these are the non-deprecated versions of the above; some fonts have these instead\n\tnum = kLowerCaseType;\n\tnumType = CFNumberCreate(NULL, kCFNumberIntType, &num);\n\tnum = kLowerCaseSmallCapsSelector;\n\tnumSelector = CFNumberCreate(NULL, kCFNumberIntType, &num);\n\tinnerDict = newAttrList();\n\tCFDictionaryAddValue(innerDict, kCTFontFeatureTypeIdentifierKey, numType);\n\tCFRelease(numType);\n\tCFDictionaryAddValue(innerDict, kCTFontFeatureSelectorIdentifierKey, numSelector);\n\tCFRelease(numSelector);\n\tCFArrayAppendValue(outerArray, innerDict);\n\tCFRelease(innerDict);\t\t// and likewise for CFArray\n\n\tCFDictionaryAddValue(attr, kCTFontFeatureSettingsAttribute, outerArray);\n\tCFRelease(outerArray);\n}\n#endif\n\n#if 0\n// 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 :(\n// kode54 got these for me before I had access to El Capitan; thanks to him.\n#define ourNSFontWeightUltraLight -0.800000\n#define ourNSFontWeightThin -0.600000\n#define ourNSFontWeightLight -0.400000\n#define ourNSFontWeightRegular 0.000000\n#define ourNSFontWeightMedium 0.230000\n#define ourNSFontWeightSemibold 0.300000\n#define ourNSFontWeightBold 0.400000\n#define ourNSFontWeightHeavy 0.560000\n#define ourNSFontWeightBlack 0.620000\n#endif\n\n// 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.\nCFMutableDictionaryRef extractAttributes(CTFontDescriptorRef desc)\n{\n\tCFDictionaryRef dict;\n\tCFMutableDictionaryRef mdict;\n\n\tdict = CTFontDescriptorCopyAttributes(desc);\n\t// this might not be mutable, so make a mutable copy\n\tmdict = CFDictionaryCreateMutableCopy(NULL, 0, dict);\n\tCFRelease(dict);\n\treturn mdict;\n}\n\nuiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc)\n{\n\tCTFontRef f;\n\tCFMutableDictionaryRef attr;\n\tCTFontDescriptorRef cfdesc;\n\n\tattr = newAttrList();\n\taddFontFamilyAttr(attr, desc->Family);\n\taddFontSizeAttr(attr, desc->Size);\n\n\t// now we have to do the traits matching, so create a descriptor, match the traits, and then get the attributes back\n\tcfdesc = CTFontDescriptorCreateWithAttributes(attr);\n\t// TODO release attr?\n\tcfdesc = matchTraits(cfdesc, desc->Weight, desc->Italic, desc->Stretch);\n\n\t// specify the initial size again just to be safe\n\tf = CTFontCreateWithFontDescriptor(cfdesc, desc->Size, NULL);\n\t// TODO release cfdesc?\n\n\treturn mkTextFont(f, NO);\t\t// we hold the initial reference; no need to retain again\n}\n\nvoid uiDrawFreeTextFont(uiDrawTextFont *font)\n{\n\tCFRelease(font->f);\n\tuiFree(font);\n}\n\nuintptr_t uiDrawTextFontHandle(uiDrawTextFont *font)\n{\n\treturn (uintptr_t) (font->f);\n}\n\nvoid uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc)\n{\n\t// TODO\n}\n\n// text sizes and user space points are identical:\n// - 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\n// - 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\nvoid uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics)\n{\n\tmetrics->Ascent = CTFontGetAscent(font->f);\n\tmetrics->Descent = CTFontGetDescent(font->f);\n\tmetrics->Leading = CTFontGetLeading(font->f);\n\tmetrics->UnderlinePos = CTFontGetUnderlinePosition(font->f);\n\tmetrics->UnderlineThickness = CTFontGetUnderlineThickness(font->f);\n}\n\n// LONGTERM allow line separation and leading to be factored into a wrapping text layout\n\n// TODO reconcile differences in character wrapping on platforms\nvoid uiDrawTextLayoutExtents(uiDrawTextLayout *layout, double *width, double *height)\n{\n\tstruct framesetter fs;\n\n\tmkFramesetter(layout, &fs);\n\t*width = fs.extents.width;\n\t*height = fs.extents.height;\n\tfreeFramesetter(&fs);\n}\n\n// LONGTERM provide an equivalent to CTLineGetTypographicBounds() on uiDrawTextLayout?\n\n// LONGTERM keep this for later features and documentation purposes\n#if 0\n\n\t// LONGTERM provide a way to get the image bounds as a separate function later\n\tbounds = CTLineGetImageBounds(line, c);\n\t// though CTLineGetImageBounds() returns CGRectNull on error, it also returns CGRectNull on an empty string, so we can't reasonably check for error\n\n\t// CGContextSetTextPosition() positions at the baseline in the case of CTLineDraw(); we need the top-left corner instead\n\tCTLineGetTypographicBounds(line, &yoff, NULL, NULL);\n\t// remember that we're flipped, so we subtract\n\ty -= yoff;\n\tCGContextSetTextPosition(c, x, y);\n#endif\n\n#if 0\nvoid uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, int startChar, int endChar, double r, double g, double b, double a)\n{\n\tCGColorSpaceRef colorspace;\n\tCGFloat components[4];\n\tCGColorRef color;\n\n\t// for consistency with windows, use sRGB\n\tcolorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);\n\tcomponents[0] = r;\n\tcomponents[1] = g;\n\tcomponents[2] = b;\n\tcomponents[3] = a;\n\tcolor = CGColorCreate(colorspace, components);\n\tCGColorSpaceRelease(colorspace);\n\n\tCFAttributedStringSetAttribute(layout->mas,\n\t\trangeToCFRange(),\n\t\tkCTForegroundColorAttributeName,\n\t\tcolor);\n\tCGColorRelease(color);\t\t// TODO safe?\n}\n#endif\n"
  },
  {
    "path": "_wip/attrstr_metrics/darwin_OLD_drawtext.m",
    "content": "// 2 january 2017\n#import \"uipriv_darwin.h\"\n#import \"draw.h\"\n\n// TODO on an empty string nLines == 0\n// we must prevent this somehow\n// TODO in general, every function could be more robust, but we cannot have a situation where there are zero lines\n// TODO what happens to extents if only whitespace?\n\nstruct uiDrawTextLayout {\n\tCFAttributedStringRef attrstr;\n\n\t// the width as passed into uiDrawTextLayout constructors\n\tdouble width;\n\n\tCTFramesetterRef framesetter;\n\n\t// the *actual* size of the frame\n\t// note: technically, metrics returned from frame are relative to CGPathGetPathBoundingBox(tl->path)\n\t// 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\n\t// (this I confirmed through experimentation)\n\t// so we can just use tl->size for adjustments\n\t// we don't need to adjust coordinates by any origin since our rect origin is (0, 0)\n\tCGSize size;\n\n\tCGPathRef path;\n\tCTFrameRef frame;\n\n\tCFArrayRef lines;\n\tCFIndex nLines;\n\t// we compute this once when first creating the layout\n\tuiDrawTextLayoutLineMetrics *lineMetrics;\n\n\tNSArray *backgroundBlocks;\n\n\t// for converting CFAttributedString indices from/to byte offsets\n\tsize_t *u8tou16;\n\tsize_t nUTF8;\n\tsize_t *u16tou8;\n\tsize_t nUTF16;\n};\n\n// TODO document that lines may or may not overlap because ours do in the case of multiple combining characters\nstatic uiDrawTextLayoutLineMetrics *computeLineMetrics(CTFrameRef frame, CGSize size)\n{\n\tuiDrawTextLayoutLineMetrics *metrics;\n\tCFArrayRef lines;\n\tCTLineRef line;\n\tCFIndex i, n;\n\tCGFloat ypos;\n\tCGRect bounds, boundsNoLeading;\n\tCGFloat ascent, descent, leading;\n\tCGPoint *origins;\n\n\tlines = CTFrameGetLines(frame);\n\tn = CFArrayGetCount(lines);\n\tmetrics = (uiDrawTextLayoutLineMetrics *) uiAlloc(n * sizeof (uiDrawTextLayoutLineMetrics), \"uiDrawTextLayoutLineMetrics[] (text layout)\");\n\n\torigins = (CGPoint *) uiAlloc(n * sizeof (CGPoint), \"CGPoint[] (text layout)\");\n\tCTFrameGetLineOrigins(frame, CFRangeMake(0, n), origins);\n\n\typos = size.height;\n\tfor (i = 0; i < n; i++) {\n\t\tline = (CTLineRef) CFArrayGetValueAtIndex(lines, i);\n\t\tbounds = CTLineGetBoundsWithOptions(line, 0);\n\t\tboundsNoLeading = CTLineGetBoundsWithOptions(line, kCTLineBoundsExcludeTypographicLeading);\n\n\t\t// this is equivalent to boundsNoLeading.size.height + boundsNoLeading.origin.y (manually verified)\n\t\tascent = bounds.size.height + bounds.origin.y;\n\t\tdescent = -boundsNoLeading.origin.y;\n\t\tleading = -bounds.origin.y - descent;\n\n\t\t// 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)\n\t\tascent = floor(ascent + 0.5);\n\t\tdescent = floor(descent + 0.5);\n\t\tif (leading > 0)\n\t\t\tleading = floor(leading + 0.5);\n\n\t\tmetrics[i].X = origins[i].x;\n\t\tmetrics[i].Y = origins[i].y - descent - leading;\n\t\tmetrics[i].Width = bounds.size.width;\n\t\tmetrics[i].Height = ascent + descent + leading;\n\n\t\tmetrics[i].BaselineY = origins[i].y;\n\t\tmetrics[i].Ascent = ascent;\n\t\tmetrics[i].Descent = descent;\n\t\tmetrics[i].Leading = leading;\n\n\t\t// TODO\n\t\tmetrics[i].ParagraphSpacingBefore = 0;\n\t\tmetrics[i].LineHeightSpace = 0;\n\t\tmetrics[i].LineSpacing = 0;\n\t\tmetrics[i].ParagraphSpacing = 0;\n\n\t\t// and finally advance to the next line\n\t\typos += metrics[i].Height;\n\t}\n\n\t// okay, but now all these metrics are unflipped\n\t// we need to flip them\n\tfor (i = 0; i < n; i++) {\n\t\tmetrics[i].Y = size.height - metrics[i].Y;\n\t\t// go from bottom-left corner to top-left\n\t\tmetrics[i].Y -= metrics[i].Height;\n\t\tmetrics[i].BaselineY = size.height - metrics[i].BaselineY;\n\t}\n\n\tuiFree(origins);\n\treturn metrics;\n}\n\nuiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *p)\n{\n\tuiDrawTextLayout *tl;\n\tCGFloat cgwidth;\n\tCFRange range, unused;\n\tCGRect rect;\n\n\ttl = uiNew(uiDrawTextLayout);\n\ttl->attrstr = attrstrToCoreFoundation(p, &(tl->backgroundBlocks));\n\trange.location = 0;\n\trange.length = CFAttributedStringGetLength(tl->attrstr);\n\ttl->width = p->Width;\n\n\t// TODO kCTParagraphStyleSpecifierMaximumLineSpacing, kCTParagraphStyleSpecifierMinimumLineSpacing, kCTParagraphStyleSpecifierLineSpacingAdjustment for line spacing\n\ttl->framesetter = CTFramesetterCreateWithAttributedString(tl->attrstr);\n\tif (tl->framesetter == NULL) {\n\t\t// TODO\n\t}\n\n\tcgwidth = (CGFloat) (tl->width);\n\tif (cgwidth < 0)\n\t\tcgwidth = CGFLOAT_MAX;\n\ttl->size = CTFramesetterSuggestFrameSizeWithConstraints(tl->framesetter,\n\t\trange,\n\t\t// TODO kCTFramePathWidthAttributeName?\n\t\tNULL,\n\t\tCGSizeMake(cgwidth, CGFLOAT_MAX),\n\t\t&unused);\t\t\t// not documented as accepting NULL (TODO really?)\n\n\trect.origin = CGPointZero;\n\trect.size = tl->size;\n\ttl->path = CGPathCreateWithRect(rect, NULL);\n\ttl->frame = CTFramesetterCreateFrame(tl->framesetter,\n\t\trange,\n\t\ttl->path,\n\t\t// TODO kCTFramePathWidthAttributeName?\n\t\tNULL);\n\tif (tl->frame == NULL) {\n\t\t// TODO\n\t}\n\n\ttl->lines = CTFrameGetLines(tl->frame);\n\ttl->nLines = CFArrayGetCount(tl->lines);\n\ttl->lineMetrics = computeLineMetrics(tl->frame, tl->size);\n\n\t// and finally copy the UTF-8/UTF-16 conversion tables\n\ttl->u8tou16 = attrstrCopyUTF8ToUTF16(p->String, &(tl->nUTF8));\n\ttl->u16tou8 = attrstrCopyUTF16ToUTF8(p->String, &(tl->nUTF16));\n\n\treturn tl;\n}\n\nvoid uiDrawFreeTextLayout(uiDrawTextLayout *tl)\n{\n\tuiFree(tl->u16tou8);\n\tuiFree(tl->u8tou16);\n\t[tl->backgroundBlocks release];\n\tuiFree(tl->lineMetrics);\n\tCFRelease(tl->frame);\n\tCFRelease(tl->path);\n\tCFRelease(tl->framesetter);\n\tCFRelease(tl->attrstr);\n\tuiFree(tl);\n}\n\n// TODO document that (x,y) is the top-left corner of the *entire frame*\nvoid uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)\n{\n\tbackgroundBlock b;\n\tCGAffineTransform textMatrix;\n\n\tCGContextSaveGState(c->c);\n\t// save the text matrix because it's not part of the graphics state\n\ttextMatrix = CGContextGetTextMatrix(c->c);\n\n\tfor (b in tl->backgroundBlocks)\n\t\tb(c, tl, x, y);\n\n\t// Core Text doesn't draw onto a flipped view correctly; we have to pretend it was unflipped\n\t// 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)\n\t// TODO how is this affected by a non-identity CTM?\n\tCGContextTranslateCTM(c->c, 0, c->height);\n\tCGContextScaleCTM(c->c, 1.0, -1.0);\n\tCGContextSetTextMatrix(c->c, CGAffineTransformIdentity);\n\n\t// wait, that's not enough; we need to offset y values to account for our new flipping\n\t// TODO explain this calculation\n\ty = c->height - tl->size.height - y;\n\n\t// CTFrameDraw() draws in the path we specified when creating the frame\n\t// this means that in our usage, CTFrameDraw() will draw at (0,0)\n\t// so move the origin to be at (x,y) instead\n\t// TODO are the signs correct?\n\tCGContextTranslateCTM(c->c, x, y);\n\n\tCTFrameDraw(tl->frame, c->c);\n\n\tCGContextSetTextMatrix(c->c, textMatrix);\n\tCGContextRestoreGState(c->c);\n}\n\n// TODO document that the width and height of a layout is not necessarily the sum of the widths and heights of its constituent lines\n// TODO width doesn't include trailing whitespace...\n// TODO figure out how paragraph spacing should play into this\nvoid uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height)\n{\n\t*width = tl->size.width;\n\t*height = tl->size.height;\n}\n\nint uiDrawTextLayoutNumLines(uiDrawTextLayout *tl)\n{\n\treturn tl->nLines;\n}\n\nvoid uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end)\n{\n\tCTLineRef lr;\n\tCFRange range;\n\n\tlr = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, line);\n\trange = CTLineGetStringRange(lr);\n\t*start = tl->u16tou8[range.location];\n\t*end = tl->u16tou8[range.location + range.length];\n}\n\nvoid uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m)\n{\n\t*m = tl->lineMetrics[line];\n}\n\n// 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\n// TODO should we document this?\nvoid uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *pos, int *line)\n{\n\tint i;\n\tCTLineRef ln;\n\tCFIndex p;\n\n\tfor (i = 0; i < tl->nLines; i++) {\n\t\tdouble ltop, lbottom;\n\n\t\tltop = tl->lineMetrics[i].Y;\n\t\tlbottom = ltop + tl->lineMetrics[i].Height;\n\t\t// y will already >= ltop at this point since the past lbottom should == (or at least >=, see above) ltop\n\t\tif (y < lbottom)\n\t\t\tbreak;\n\t}\n\tif (i == tl->nLines)\n\t\ti--;\n\t*line = i;\n\n\tln = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, i);\n\t// 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)\n\t// note: x is relative to the line origin\n\tx -= tl->lineMetrics[*line].X;\n\tp = CTLineGetStringIndexForPosition(ln, CGPointMake(x, 0));\n\tif (p == kCFNotFound) {\n\t\t// TODO\n\t}\n\t*pos = tl->u16tou8[p];\n}\n\ndouble uiDrawTextLayoutByteLocationInLine(uiDrawTextLayout *tl, size_t pos, int line)\n{\n\tCTLineRef lr;\n\tCFRange range;\n\n\tpos = tl->u8tou16[pos];\n\tif (line < 0 || line >= tl->nLines)\n\t\treturn -1;\n\tlr = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, line);\n\trange = CTLineGetStringRange(lr);\n\t// note: >, not >=, because the position at end is valid!\n\tif (pos < range.location || pos > (range.location + range.length))\n\t\treturn -1;\n\t// no point in checking the return; we already validated everything and 0 is a valid return for the first index :/\n\t// note: the result is relative to the line origin (TODO find documentation to support this)\n\t// TODO document that these functions do this\n\treturn CTLineGetOffsetForStringIndex(lr, pos, NULL) + tl->lineMetrics[line].X;\n}\n\nvoid caretDrawParams(uiDrawContext *c, double height, struct caretDrawParams *p)\n{\n\tNSColor *cc;\n\tCGFloat cr, cg, cb, ca;\n\n\t// Interface Builder sets this as the insertion point color for a NSTextView by default\n\tcc = [NSColor controlTextColor];\n\t// the given color may not be an RGBA color, which will cause the -getRed:green:blue:alpha: call to throw an exception\n\tcc = [cc colorUsingColorSpace:[NSColorSpace sRGBColorSpace]];\n\t[cc getRed:&cr green:&cg blue:&cb alpha:&ca];\n\tp->r = cr;\n\tp->g = cg;\n\tp->b = cb;\n\tp->a = ca;\n\t// both cc and the controlTextColor it was made from will be autoreleased since they aren't new or init calls\n\t// TODO disabled carets have some blending applied...\n\n\t// okay there doesn't seem to be any defined metrics for these, argh...\n\tp->width = 1;\n\tp->xoff = 0;\n}\n"
  },
  {
    "path": "_wip/attrstr_metrics/numlinesbyterange",
    "content": "darwin\nint uiDrawTextLayoutNumLines(uiDrawTextLayout *tl)\n{\n\treturn CFArrayGetCount([tl->forLines lines]);\n}\n\nvoid uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end)\n{\n\tCTLineRef lr;\n\tCFRange range;\n\n\tlr = (CTLineRef) CFArrayGetValueAtIndex([tl->forLines lines], line);\n\trange = CTLineGetStringRange(lr);\n\t*start = tl->u16tou8[range.location];\n\tif (tl->empty)\n\t\t*end = *start;\n\telse\n\t\t*end = tl->u16tou8[range.location + range.length];\n}\n\n\nunix\nint uiDrawTextLayoutNumLines(uiDrawTextLayout *tl)\n{\n\treturn pango_layout_get_line_count(tl->layout);\n}\n\nvoid uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end)\n{\n\tPangoLayoutLine *pll;\n\n\tpll = pango_layout_get_line_readonly(tl->layout, line);\n\t*start = pll->start_index;\n\t*end = pll->start_index + pll->length;\n\t// TODO unref pll?\n}\n\n\nwindows\nint uiDrawTextLayoutNumLines(uiDrawTextLayout *tl)\n{\nreturn 0;\n#if 0\nTODO\n\treturn tl->nLines;\n#endif\n}\n\n// DirectWrite doesn't provide a direct way to do this, so we have to do this manually\n// TODO does that comment still apply here or to the code at the top of this file?\nvoid uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end)\n{\n#if 0\nTODO\n\t*start = tl->lineInfo[line].startPos;\n\t*start = tl->u16tou8[*start];\n\t*end = tl->lineInfo[line].endPos - tl->lineInfo[line].newlineCount;\n\t*end = tl->u16tou8[*end];\n#endif\n}\n"
  },
  {
    "path": "_wip/attrstr_metrics/old_ui_attrstr.h",
    "content": "\ntypedef struct uiDrawTextLayoutLineMetrics uiDrawTextLayoutLineMetrics;\n\n// Height will equal ParagraphSpacingBefore + LineHeightSpace + Ascent + Descent + Leading + LineSpacing + ParagraphSpacing.\n// The above values are listed in vertical order, from top to bottom.\n// Ascent + Descent + Leading will give you the typographic bounds\n// of the text. BaselineY is the boundary between Ascent and Descent.\n// X, Y, and BaselineY are all in the layout's coordinate system, so the\n// start point of the baseline will be at (X, BaselineY). All values are\n// nonnegative.\nstruct uiDrawTextLayoutLineMetrics {\n\t// This describes the overall bounding box of the line.\n\tdouble X;\n\tdouble Y;\n\tdouble Width;\n\tdouble Height;\n\n\t// This describes the typographic bounds of the line.\n\tdouble BaselineY;\n\tdouble Ascent;\n\tdouble Descent;\n\tdouble Leading;\n\n\t// This describes any additional whitespace.\n\t// TODO come up with better names for these.\n\tdouble ParagraphSpacingBefore;\n\tdouble LineHeightSpace;\n\tdouble LineSpacing;\n\tdouble ParagraphSpacing;\n\n\t// TODO trailing whitespace?\n};\n\n// uiDrawTextLayoutNumLines() returns the number of lines in tl.\n// This number will always be greater than or equal to 1; a text\n// layout with no text only has one line.\n_UI_EXTERN int uiDrawTextLayoutNumLines(uiDrawTextLayout *tl);\n\n// uiDrawTextLayoutLineByteRange() returns the byte indices of the\n// text that falls into the given line of tl as [start, end).\n_UI_EXTERN void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end);\n\n_UI_EXTERN void uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m);\n\n// TODO rewrite this documentation\n\n// uiDrawTextLayoutHitTest() returns the byte offset and line closest\n// to the given point. The point is relative to the top-left of the layout.\n// If the point is outside the layout itself, the closest point is chosen;\n// this allows the function to be used for cursor positioning with the\n// mouse. Do keep the returned line in mind if used in this way; the\n// user might click on the end of a line, at which point the cursor\n// might be at the trailing edge of the last grapheme on the line\n// (subject to the operating system's APIs).\n_UI_EXTERN void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *pos, int *line);\n\n// uiDrawTextLayoutByteLocationInLine() returns the point offset\n// into the given line that the given byte position stands. This is\n// relative to the line's X position (as returned by\n// uiDrawTextLayoutLineGetMetrics()), which in turn is relative to\n// the top-left of the layout. This function can be used for cursor\n// positioning: if start and end are the start and end of the line\n// (as returned by uiDrawTextLayoutLineByteRange()), you will get\n// the correct offset, even if pos is at the end of the line. If pos is not\n// in the range [start, end], a negative value will be returned,\n// indicating you need to move the cursor to another line.\n// TODO make sure this works right for right-aligned and center-aligned lines and justified lines and RTL text\n_UI_EXTERN double uiDrawTextLayoutByteLocationInLine(uiDrawTextLayout *tl, size_t pos, int line);\n\n_UI_EXTERN void uiDrawCaret(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout, size_t pos, int *line);\n// TODO allow blinking\n// TODO allow secondary carets\n"
  },
  {
    "path": "_wip/attrstr_metrics/textDarwinEmptyLayout.diff",
    "content": "diff --git a/darwin/attrstr.m b/darwin/attrstr.m\nindex fd45ec25..86039fad 100644\n--- a/darwin/attrstr.m\n+++ b/darwin/attrstr.m\n@@ -403,8 +403,13 @@ static CTParagraphStyleRef mkParagraphStyle(uiDrawTextLayoutParams *p)\n \treturn ps;\n }\n \n-CFAttributedStringRef attrstrToCoreFoundation(uiDrawTextLayoutParams *p, NSArray **backgroundBlocks)\n+static const UniChar emptyChars[] = { 0x20, 0x0 };\n+static const CFIndex emptyCharCount = 1;\n+\n+CFAttributedStringRef attrstrToCoreFoundation(uiDrawTextLayoutParams *p, BOOL *isEmpty, NSArray **backgroundBlocks)\n {\n+\tconst UniChar *chars;\n+\tCFIndex charCount;\n \tCFStringRef cfstr;\n \tCFMutableDictionaryRef defaultAttrs;\n \tCTFontRef defaultCTFont;\n@@ -413,7 +418,15 @@ CFAttributedStringRef attrstrToCoreFoundation(uiDrawTextLayoutParams *p, NSArray\n \tCFMutableAttributedStringRef mas;\n \tstruct foreachParams fep;\n \n-\tcfstr = CFStringCreateWithCharacters(NULL, attrstrUTF16(p->String), attrstrUTF16Len(p->String));\n+\t*isEmpty = NO;\n+\tchars = attrstrUTF16(p->String);\n+\tcharCount = attrstrUTF16Len(p->String);\n+\tif (charCount == 0) {\n+\t\t*isEmpty = YES;\n+\t\tchars = emptyChars;\n+\t\tcharCount = emptyCharCount;\n+\t}\n+\tcfstr = CFStringCreateWithCharacters(NULL, chars, charCount);\n \tif (cfstr == NULL) {\n \t\t// TODO\n \t}\ndiff --git a/darwin/drawtext.m b/darwin/drawtext.m\nindex 1fa5920e..65912383 100644\n--- a/darwin/drawtext.m\n+++ b/darwin/drawtext.m\n@@ -2,13 +2,16 @@\n #import \"uipriv_darwin.h\"\n #import \"draw.h\"\n \n-// TODO on an empty string nLines == 0\n-// we must prevent this somehow\n-// TODO in general, every function could be more robust, but we cannot have a situation where there are zero lines\n+// TODO in general, every function could be more robust\n // TODO what happens to extents if only whitespace?\n+// TODO for empty layouts:\n+// - check if alignment correct compared to other OSs, or expected behavior at all\n+// - double-check if uiAttributedString allows zero-length attributes; I forget if I did\n \n struct uiDrawTextLayout {\n \tCFAttributedStringRef attrstr;\n+\t// this is needed because Core Text will give us an empty line array on a frame made with an empty string\n+\tBOOL isEmpty;\n \n \t// the width as passed into uiDrawTextLayout constructors\n \tdouble width;\n@@ -41,7 +44,7 @@\n };\n \n // TODO document that lines may or may not overlap because ours do in the case of multiple combining characters\n-static uiDrawTextLayoutLineMetrics *computeLineMetrics(CTFrameRef frame, CGSize size)\n+static uiDrawTextLayoutLineMetrics *computeLineMetrics(CTFrameRef frame, CGSize size, BOOL isEmpty)\n {\n \tuiDrawTextLayoutLineMetrics *metrics;\n \tCFArrayRef lines;\n@@ -79,6 +82,8 @@\n \t\tmetrics[i].X = origins[i].x;\n \t\tmetrics[i].Y = origins[i].y - descent - leading;\n \t\tmetrics[i].Width = bounds.size.width;\n+\t\tif (isEmpty)\n+\t\t\tmetrics[i].Width = 0;\n \t\tmetrics[i].Height = ascent + descent + leading;\n \n \t\tmetrics[i].BaselineY = origins[i].y;\n@@ -117,7 +122,7 @@\n \tCGRect rect;\n \n \ttl = uiNew(uiDrawTextLayout);\n-\ttl->attrstr = attrstrToCoreFoundation(p, &(tl->backgroundBlocks));\n+\ttl->attrstr = attrstrToCoreFoundation(p, &(tl->isEmpty), &(tl->backgroundBlocks));\n \trange.location = 0;\n \trange.length = CFAttributedStringGetLength(tl->attrstr);\n \ttl->width = p->Width;\n@@ -152,7 +157,7 @@\n \n \ttl->lines = CTFrameGetLines(tl->frame);\n \ttl->nLines = CFArrayGetCount(tl->lines);\n-\ttl->lineMetrics = computeLineMetrics(tl->frame, tl->size);\n+\ttl->lineMetrics = computeLineMetrics(tl->frame, tl->size, tl->isEmpty);\n \n \t// and finally copy the UTF-8/UTF-16 conversion tables\n \ttl->u8tou16 = attrstrCopyUTF8ToUTF16(p->String, &(tl->nUTF8));\n@@ -180,6 +185,9 @@ void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)\n \tbackgroundBlock b;\n \tCGAffineTransform textMatrix;\n \n+\tif (tl->isEmpty)\n+\t\treturn;\n+\n \tCGContextSaveGState(c->c);\n \t// save the text matrix because it's not part of the graphics state\n \ttextMatrix = CGContextGetTextMatrix(c->c);\n@@ -216,6 +224,8 @@ void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)\n void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height)\n {\n \t*width = tl->size.width;\n+\tif (tl->isEmpty)\n+\t\t*width = 0;\n \t*height = tl->size.height;\n }\n \n@@ -233,6 +243,8 @@ void uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start\n \trange = CTLineGetStringRange(lr);\n \t*start = tl->u16tou8[range.location];\n \t*end = tl->u16tou8[range.location + range.length];\n+\tif (tl->isEmpty)\n+\t\t*start = *end;\n }\n \n void uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m)\n@@ -262,14 +274,17 @@ void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *p\n \t*line = i;\n \n \tln = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, i);\n-\t// 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)\n-\t// note: x is relative to the line origin\n \tx -= tl->lineMetrics[*line].X;\n-\tp = CTLineGetStringIndexForPosition(ln, CGPointMake(x, 0));\n-\tif (p == kCFNotFound) {\n-\t\t// TODO\n+\t*pos = 0;\n+\tif (!tl->isEmpty) {\n+\t\t// 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)\n+\t\t// note: x is relative to the line origin\n+\t\tp = CTLineGetStringIndexForPosition(ln, CGPointMake(x, 0));\n+\t\tif (p == kCFNotFound) {\n+\t\t\t// TODO\n+\t\t}\n+\t\t*pos = tl->u16tou8[p];\n \t}\n-\t*pos = tl->u16tou8[p];\n }\n \n double uiDrawTextLayoutByteLocationInLine(uiDrawTextLayout *tl, size_t pos, int line)\n@@ -282,6 +297,9 @@ void uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *p\n \t\treturn -1;\n \tlr = (CTLineRef) CFArrayGetValueAtIndex(tl->lines, line);\n \trange = CTLineGetStringRange(lr);\n+\t// TODO is the behavior of this part correct?\n+\tif (tl->isEmpty)\n+\t\trange.length = 0;\n \t// note: >, not >=, because the position at end is valid!\n \tif (pos < range.location || pos > (range.location + range.length))\n \t\treturn -1;\ndiff --git a/darwin/uipriv_darwin.h b/darwin/uipriv_darwin.h\nindex 0303c32c..d44ee410 100644\n--- a/darwin/uipriv_darwin.h\n+++ b/darwin/uipriv_darwin.h\n@@ -151,7 +151,7 @@ extern void fontdescFromCTFontDescriptor(CTFontDescriptorRef ctdesc, uiDrawFontD\n extern void initUnderlineColors(void);\n extern void uninitUnderlineColors(void);\n typedef void (^backgroundBlock)(uiDrawContext *c, uiDrawTextLayout *layout, double x, double y);\n-extern CFAttributedStringRef attrstrToCoreFoundation(uiDrawTextLayoutParams *p, NSArray **backgroundBlocks);\n+extern CFAttributedStringRef attrstrToCoreFoundation(uiDrawTextLayoutParams *p, BOOL *isEmpty, NSArray **backgroundBlocks);\n \n // aat.m\n typedef void (^aatBlock)(uint16_t type, uint16_t selector);\n"
  },
  {
    "path": "_wip/attrstr_metrics/unix_OLD__old_drawtext.c",
    "content": "// 6 september 2015\n#include \"uipriv_unix.h\"\n#include \"draw.h\"\n\nstruct uiDrawFontFamilies {\n\tPangoFontFamily **f;\n\tint n;\n};\n\nuiDrawFontFamilies *uiDrawListFontFamilies(void)\n{\n\tuiDrawFontFamilies *ff;\n\tPangoFontMap *map;\n\n\tff = uiNew(uiDrawFontFamilies);\n\tmap = pango_cairo_font_map_get_default();\n\tpango_font_map_list_families(map, &(ff->f), &(ff->n));\n\t// do not free map; it's a shared resource\n\treturn ff;\n}\n\nint uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff)\n{\n\treturn ff->n;\n}\n\nchar *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, int n)\n{\n\tPangoFontFamily *f;\n\n\tf = ff->f[n];\n\treturn uiUnixStrdupText(pango_font_family_get_name(f));\n}\n\nvoid uiDrawFreeFontFamilies(uiDrawFontFamilies *ff)\n{\n\tg_free(ff->f);\n\tuiFree(ff);\n}\n\nstruct uiDrawTextFont {\n\tPangoFont *f;\n};\n\nuiDrawTextFont *mkTextFont(PangoFont *f, gboolean ref)\n{\n\tuiDrawTextFont *font;\n\n\tfont = uiNew(uiDrawTextFont);\n\tfont->f = f;\n\tif (ref)\n\t\tg_object_ref(font->f);\n\treturn font;\n}\n\n\n\nPangoFont *pangoDescToPangoFont(PangoFontDescription *pdesc)\n{\n\tPangoFont *f;\n\tPangoContext *context;\n\n\t// in this case, the context is necessary for the metrics to be correct\n\tcontext = mkGenericPangoCairoContext();\n\tf = pango_font_map_load_font(pango_cairo_font_map_get_default(), context, pdesc);\n\tif (f == NULL) {\n\t\t// LONGTERM\n\t\tg_error(\"[libui] no match in pangoDescToPangoFont(); report to andlabs\");\n\t}\n\tg_object_unref(context);\n\treturn f;\n}\n\nuiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc)\n{\n\tPangoFont *f;\n\tPangoFontDescription *pdesc;\n\n\tpdesc = pango_font_description_new();\n\tpango_font_description_set_family(pdesc,\n\t\tdesc->Family);\n\tpango_font_description_set_size(pdesc,\n\t\t(gint) (desc->Size * PANGO_SCALE));\n\tpango_font_description_set_weight(pdesc,\n\t\tpangoWeights[desc->Weight]);\n\tpango_font_description_set_style(pdesc,\n\t\tpangoItalics[desc->Italic]);\n\tpango_font_description_set_stretch(pdesc,\n\t\tpangoStretches[desc->Stretch]);\n\tf = pangoDescToPangoFont(pdesc);\n\tpango_font_description_free(pdesc);\n\treturn mkTextFont(f, FALSE);\t\t\t// we hold the initial reference; no need to ref\n}\n\nvoid uiDrawFreeTextFont(uiDrawTextFont *font)\n{\n\tg_object_unref(font->f);\n\tuiFree(font);\n}\n\nuintptr_t uiDrawTextFontHandle(uiDrawTextFont *font)\n{\n\treturn (uintptr_t) (font->f);\n}\n\nvoid uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc)\n{\n\tPangoFontDescription *pdesc;\n\n\t// this creates a copy; we free it later\n\tpdesc = pango_font_describe(font->f);\n\n\t// TODO\n\n\tpango_font_description_free(pdesc);\n}\n\n// See https://developer.gnome.org/pango/1.30/pango-Cairo-Rendering.html#pango-Cairo-Rendering.description\n// Note that we convert to double before dividing to make sure the floating-point stuff is right\n#define pangoToCairo(pango) (((double) (pango)) / PANGO_SCALE)\n#define cairoToPango(cairo) ((gint) ((cairo) * PANGO_SCALE))\n\nvoid uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics)\n{\n\tPangoFontMetrics *pm;\n\n\tpm = pango_font_get_metrics(font->f, NULL);\n\tmetrics->Ascent = pangoToCairo(pango_font_metrics_get_ascent(pm));\n\tmetrics->Descent = pangoToCairo(pango_font_metrics_get_descent(pm));\n\t// Pango doesn't seem to expose this :( Use 0 and hope for the best.\n\tmetrics->Leading = 0;\n\tmetrics->UnderlinePos = pangoToCairo(pango_font_metrics_get_underline_position(pm));\n\tmetrics->UnderlineThickness = pangoToCairo(pango_font_metrics_get_underline_thickness(pm));\n\tpango_font_metrics_unref(pm);\n}\n\n// note: PangoCairoLayouts are tied to a given cairo_t, so we can't store one in this device-independent structure\nstruct uiDrawTextLayout {\n\tchar *s;\n\tptrdiff_t *graphemes;\n\tPangoFont *defaultFont;\n\tdouble width;\n\tPangoAttrList *attrs;\n};\n\nuiDrawTextLayout *uiDrawNewTextLayout(const char *text, uiDrawTextFont *defaultFont, double width)\n{\n\tuiDrawTextLayout *layout;\n\tPangoContext *context;\n\n\tlayout = uiNew(uiDrawTextLayout);\n\tlayout->s = g_strdup(text);\n\tcontext = mkGenericPangoCairoContext();\n\tlayout->graphemes = graphemes(layout->s, context);\n\tg_object_unref(context);\n\tlayout->defaultFont = defaultFont->f;\n\tg_object_ref(layout->defaultFont);\t\t// retain a copy\n\tuiDrawTextLayoutSetWidth(layout, width);\n\tlayout->attrs = pango_attr_list_new();\n\treturn layout;\n}\n\nvoid uiDrawFreeTextLayout(uiDrawTextLayout *layout)\n{\n\tpango_attr_list_unref(layout->attrs);\n\tg_object_unref(layout->defaultFont);\n\tuiFree(layout->graphemes);\n\tg_free(layout->s);\n\tuiFree(layout);\n}\n\nvoid uiDrawTextLayoutSetWidth(uiDrawTextLayout *layout, double width)\n{\n\tlayout->width = width;\n}\n\nstatic void prepareLayout(uiDrawTextLayout *layout, PangoLayout *pl)\n{\n\t// again, this makes a copy\n\tdesc = pango_font_describe(layout->defaultFont);\n\n\tpango_layout_set_attributes(pl, layout->attrs);\n}\n\nvoid uiDrawText(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout)\n{\n\tPangoLayout *pl;\n\n\tpl = pango_cairo_create_layout(c->cr);\n}\n\nstatic void addAttr(uiDrawTextLayout *layout, PangoAttribute *attr, int startChar, int endChar)\n{\n\tattr->start_index = layout->graphemes[startChar];\n\tattr->end_index = layout->graphemes[endChar];\n\tpango_attr_list_insert(layout->attrs, attr);\n\t// pango_attr_list_insert() takes attr; we don't free it\n}\n\nvoid uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, int startChar, int endChar, double r, double g, double b, double a)\n{\n\tPangoAttribute *attr;\n\tguint16 rr, gg, bb, aa;\n\n\trr = (guint16) (r * 65535);\n\tgg = (guint16) (g * 65535);\n\tbb = (guint16) (b * 65535);\n\taa = (guint16) (a * 65535);\n\n\tattr = pango_attr_foreground_new(rr, gg, bb);\n\taddAttr(layout, attr, startChar, endChar);\n\n\t// TODO what if aa == 0?\n\tattr = FUTURE_pango_attr_foreground_alpha_new(aa);\n\tif (attr != NULL)\n\t\taddAttr(layout, attr, startChar, endChar);\n}\n"
  },
  {
    "path": "_wip/attrstr_metrics/unix_OLD_drawtext.c",
    "content": "// 17 january 2017\n#include \"uipriv_unix.h\"\n#include \"draw.h\"\n\n// TODO\n// - if the RTL override is at the beginning of a line, the preceding space is included?\n// - nLines == 0: mostly works, except the width is wrong if the paragraph alignment is center or right...\n// - TODO check whitespace and line bounds\n\nstruct uiDrawTextLayout {\n\tPangoLayout *layout;\n\tGPtrArray *backgroundClosures;\n\tuiDrawTextLayoutLineMetrics *lineMetrics;\n\t// TODO change everything to use this\n\tint nLines;\n};\n\n// TODO neither these nor the overall extents seem to include trailing whitespace... we need to figure that out too\nstatic void computeLineMetrics(uiDrawTextLayout *tl)\n{\n\tPangoLayoutIter *iter;\n\tPangoLayoutLine *pll;\n\tPangoRectangle lineStartPos, lineExtents;\n\tint i, n;\n\tuiDrawTextLayoutLineMetrics *m;\n\n\tn = tl->nLines;\t\t// TODO remove this variable\n\ttl->lineMetrics = (uiDrawTextLayoutLineMetrics *) uiAlloc(n * sizeof (uiDrawTextLayoutLineMetrics), \"uiDrawTextLayoutLineMetrics[] (text layout)\");\n\titer = pango_layout_get_iter(tl->layout);\n\n\tm = tl->lineMetrics;\n\tfor (i = 0; i < n; i++) {\n\t\tint baselineY;\n\n\t\t// 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...\n\t\tbaselineY = pango_layout_iter_get_baseline(iter);\n\t\tpll = pango_layout_iter_get_line_readonly(iter);\n\t\tpango_layout_index_to_pos(tl->layout, pll->start_index, &lineStartPos);\n\t\tpango_layout_line_get_extents(pll, NULL, &lineExtents);\n\t\t// TODO unref pll?\n\n\t\t// TODO is this correct for RTL glyphs?\n\t\tm->X = pangoToCairo(lineStartPos.x);\n\t\t// TODO fix the whole combined not being updated shenanigans in the static build (here because ugh)\n\t\tm->Y = pangoToCairo(baselineY - PANGO_ASCENT(lineExtents));\n\t\t// TODO this does not include the last space if any\n\t\tm->Width = pangoToCairo(lineExtents.width);\n\t\tm->Height = pangoToCairo(lineExtents.height);\n\n\t\tm->BaselineY = pangoToCairo(baselineY);\n\t\tm->Ascent = pangoToCairo(PANGO_ASCENT(lineExtents));\n\t\tm->Descent = pangoToCairo(PANGO_DESCENT(lineExtents));\n\t\tm->Leading = 0;\t\t// TODO\n\n\t\tm->ParagraphSpacingBefore = 0;\t\t// TODO\n\t\tm->LineHeightSpace = 0;\t\t\t\t// TODO\n\t\tm->LineSpacing = 0;\t\t\t\t// TODO\n\t\tm->ParagraphSpacing = 0;\t\t\t// TODO\n\n\t\t// don't worry about the return value; we're not using this after the last line\n\t\tpango_layout_iter_next_line(iter);\n\t\tm++;\n\t}\n\n\tpango_layout_iter_free(iter);\n}\n\nuiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *p)\n{\n\tuiDrawTextLayout *tl;\n\tPangoContext *context;\n\tPangoFontDescription *desc;\n\tPangoAttrList *attrs;\n\tint pangoWidth;\n\n\ttl = uiNew(uiDrawTextLayout);\n\n\t// in this case, the context is necessary to create the layout\n\t// the layout takes a ref on the context so we can unref it afterward\n\tcontext = mkGenericPangoCairoContext();\n\ttl->layout = pango_layout_new(context);\n\tg_object_unref(context);\n\n\t// this is safe; pango_layout_set_text() copies the string\n\tpango_layout_set_text(tl->layout, uiAttributedStringString(p->String), -1);\n\n\tdesc = pango_font_description_new();\n\tpango_font_description_set_family(desc, p->DefaultFont->Family);\n\tpango_font_description_set_style(desc, pangoItalics[p->DefaultFont->Italic]);\n\t// for the most part, pango weights correlate to ours\n\t// the differences:\n\t// - Book — libui: 350, Pango: 380\n\t// - Ultra Heavy — libui: 950, Pango: 1000\n\t// TODO figure out what to do about this misalignment\n\tpango_font_description_set_weight(desc, p->DefaultFont->Weight);\n\tpango_font_description_set_stretch(desc, pangoStretches[p->DefaultFont->Stretch]);\n\t// 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\n\tpango_font_description_set_size(desc, pango_units_from_double(p->DefaultFont->Size));\n\tpango_layout_set_font_description(tl->layout, desc);\n\t// this is safe; the description is copied\n\tpango_font_description_free(desc);\n\n\tpangoWidth = cairoToPango(p->Width);\n\tif (p->Width < 0)\n\t\tpangoWidth = -1;\n\tpango_layout_set_width(tl->layout, pangoWidth);\n\n\tpango_layout_set_alignment(tl->layout, pangoAligns[p->Align]);\n\n\tattrs = attrstrToPangoAttrList(p, &(tl->backgroundClosures));\n\tpango_layout_set_attributes(tl->layout, attrs);\n\tpango_attr_list_unref(attrs);\n\n\ttl->nLines = pango_layout_get_line_count(tl->layout);\n\tcomputeLineMetrics(tl);\n\n\treturn tl;\n}\n\nvoid uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m)\n{\n\t*m = tl->lineMetrics[line];\n}\n\n// TODO\n#if 0\n{\n\tPangoLayoutLine *pll;\n\n\tpll = pango_layout_get_line_readonly(tl->layout, line);\n\t// TODO unref?\n}\n#endif\n\n// 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\n// 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...\nvoid uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *pos, int *line)\n{\n\tint p, trailing;\n\tint i;\n\n\t// this is layout-global, so it takes line origins into account\n\tpango_layout_xy_to_index(tl->layout,\n\t\tcairoToPango(x), cairoToPango(y),\n\t\t&p, &trailing);\n\t// on a trailing hit, align to the nearest cluster\n\t// fortunately Pango provides that info directly\n\tif (trailing != 0)\n\t\tp += trailing;\n\t*pos = p;\n\n\tfor (i = 0; i < tl->nLines; i++) {\n\t\tdouble ltop, lbottom;\n\n\t\tltop = tl->lineMetrics[i].Y;\n\t\tlbottom = ltop + tl->lineMetrics[i].Height;\n\t\t// y will already >= ltop at this point since the past lbottom should == ltop\n\t\tif (y < lbottom)\n\t\t\tbreak;\n\t}\n\tif (i == pango_layout_get_line_count(tl->layout))\n\t\ti--;\n\t*line = i;\n}\n\ndouble uiDrawTextLayoutByteLocationInLine(uiDrawTextLayout *tl, size_t pos, int line)\n{\n\tPangoLayoutLine *pll;\n\tgboolean trailing;\n\tint pangox;\n\n\tif (line < 0 || line >= tl->nLines)\n\t\treturn -1;\n\tpll = pango_layout_get_line_readonly(tl->layout, line);\n\t// note: >, not >=, because the position at end is valid!\n\tif (pos < pll->start_index || pos > (pll->start_index + pll->length))\n\t\treturn -1;\n\t// this behavior seems correct\n\t// there's also PadWrite's TextEditor::GetCaretRect() but that requires state...\n\t// TODO where does this fail?\n\t// TODO optimize everything to avoid calling strlen()\n\ttrailing = 0;\n\tif (pos != 0 && pos != strlen(pango_layout_get_text(tl->layout)) && pos == (pll->start_index + pll->length)) {\n\t\tpos--;\n\t\ttrailing = 1;\n\t}\n\tpango_layout_line_index_to_x(pll, pos, trailing, &pangox);\n\t// TODO unref pll?\n\t// this is relative to the beginning of the line\n\treturn pangoToCairo(pangox) + tl->lineMetrics[line].X;\n}\n\n// note: we can't use gtk_render_insertion_cursor() because that doesn't take information about what line to render on\n// we'll just recreate what it does\nvoid caretDrawParams(uiDrawContext *c, double height, struct caretDrawParams *p)\n{\n\tGdkColor *color;\n\tGdkRGBA rgba;\n\tgfloat aspectRatio;\n\tgint width, xoff;\n\n\tgtk_style_context_get_style(c->style,\n\t\t\"cursor-color\", &color,\n\t\t\"cursor-aspect-ratio\", &aspectRatio,\n\t\tNULL);\n\tif (color != NULL) {\n\t\tp->r = ((double) (color->red)) / 65535.0;\n\t\tp->g = ((double) (color->green)) / 65535.0;\n\t\tp->b = ((double) (color->blue)) / 65535.0;\n\t\tp->a = 1.0;\n\t\tgdk_color_free(color);\n\t} else {\n\t\tgtk_style_context_get_color(c->style, GTK_STATE_FLAG_NORMAL, &rgba);\n\t\tp->r = rgba.red;\n\t\tp->g = rgba.green;\n\t\tp->b = rgba.blue;\n\t\tp->a = rgba.alpha;\n\t}\n\n\t// GTK+ itself uses integer arithmetic here; let's do the same\n\twidth = height * aspectRatio + 1;\n\t// TODO this is for LTR\n\txoff = width / 2;\n\n\tp->xoff = xoff;\n\tp->width = width;\n}\n"
  },
  {
    "path": "_wip/attrstr_metrics/windows_OLD__old_drawtext.cpp",
    "content": "// 22 december 2015\n#include \"uipriv_windows.hpp\"\n#include \"draw.hpp\"\n// TODO really migrate\n\n// notes:\n// only available in windows 8 and newer:\n// - character spacing\n// - kerning control\n// - justficiation (how could I possibly be making this up?!)\n// - 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!)\n\nstruct uiDrawFontFamilies {\n\tfontCollection *fc;\n};\n\nuiDrawFontFamilies *uiDrawListFontFamilies(void)\n{\n\tstruct uiDrawFontFamilies *ff;\n\n\tff = uiNew(struct uiDrawFontFamilies);\n\tff->fc = loadFontCollection();\n\treturn ff;\n}\n\nint uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff)\n{\n\treturn ff->fc->fonts->GetFontFamilyCount();\n}\n\nchar *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, int n)\n{\n\tIDWriteFontFamily *family;\n\tWCHAR *wname;\n\tchar *name;\n\tHRESULT hr;\n\n\thr = ff->fc->fonts->GetFontFamily(n, &family);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error getting font out of collection\", hr);\n\twname = fontCollectionFamilyName(ff->fc, family);\n\tname = toUTF8(wname);\n\tuiFree(wname);\n\tfamily->Release();\n\treturn name;\n}\n\nvoid uiDrawFreeFontFamilies(uiDrawFontFamilies *ff)\n{\n\tfontCollectionFree(ff->fc);\n\tuiFree(ff);\n}\n\nstruct uiDrawTextFont {\n\tIDWriteFont *f;\n\tWCHAR *family;\t\t// save for convenience in uiDrawNewTextLayout()\n\tdouble size;\n};\n\nuiDrawTextFont *mkTextFont(IDWriteFont *df, BOOL addRef, WCHAR *family, BOOL copyFamily, double size)\n{\n\tuiDrawTextFont *font;\n\tWCHAR *copy;\n\tHRESULT hr;\n\n\tfont = uiNew(uiDrawTextFont);\n\tfont->f = df;\n\tif (addRef)\n\t\tfont->f->AddRef();\n\tif (copyFamily) {\n\t\tcopy = (WCHAR *) uiAlloc((wcslen(family) + 1) * sizeof (WCHAR), \"WCHAR[]\");\n\t\twcscpy(copy, family);\n\t\tfont->family = copy;\n\t} else\n\t\tfont->family = family;\n\tfont->size = size;\n\treturn font;\n}\n\n// TODO MinGW-w64 is missing this one\n#define DWRITE_FONT_WEIGHT_SEMI_LIGHT (DWRITE_FONT_WEIGHT(350))\n\nuiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc)\n{\n\tuiDrawTextFont *font;\n\tIDWriteFontCollection *collection;\n\tUINT32 index;\n\tBOOL exists;\n\tstruct dwriteAttr attr;\n\tIDWriteFontFamily *family;\n\tWCHAR *wfamily;\n\tIDWriteFont *match;\n\tHRESULT hr;\n\n\t// always get the latest available font information\n\thr = dwfactory->GetSystemFontCollection(&collection, TRUE);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error getting system font collection\", hr);\n\n\twfamily = toUTF16(desc->Family);\n\thr = collection->FindFamilyName(wfamily, &index, &exists);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error finding font family\", hr);\n\tif (!exists)\n\t\timplbug(\"LONGTERM family not found in uiDrawLoadClosestFont()\", hr);\n\thr = collection->GetFontFamily(index, &family);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error loading font family\", hr);\n\n\tattr.weight = desc->Weight;\n\tattr.italic = desc->Italic;\n\tattr.stretch = desc->Stretch;\n\tattrToDWriteAttr(&attr);\n\thr = family->GetFirstMatchingFont(\n\t\tattr.dweight,\n\t\tattr.dstretch,\n\t\tattr.ditalic,\n\t\t&match);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error loading font\", hr);\n\n\tfont = mkTextFont(match,\n\t\tFALSE,\t\t\t\t// we own the initial reference; no need to add another one\n\t\twfamily, FALSE,\t\t// will be freed with font\n\t\tdesc->Size);\n\n\tfamily->Release();\n\tcollection->Release();\n\n\treturn font;\n}\n\nvoid uiDrawFreeTextFont(uiDrawTextFont *font)\n{\n\tfont->f->Release();\n\tuiFree(font->family);\n\tuiFree(font);\n}\n\nuintptr_t uiDrawTextFontHandle(uiDrawTextFont *font)\n{\n\treturn (uintptr_t) (font->f);\n}\n\nvoid uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc)\n{\n\t// TODO\n\n\tdesc->Size = font->size;\n\n\t// TODO\n}\n\n// text sizes are 1/72 of an inch\n// 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)\n// As for the actual conversion from design units, see:\n// - http://cboard.cprogramming.com/windows-programming/136733-directwrite-font-height-issues.html\n// - https://sourceforge.net/p/vstgui/mailman/message/32483143/\n// - http://xboxforums.create.msdn.com/forums/t/109445.aspx\n// - https://msdn.microsoft.com/en-us/library/dd183564%28v=vs.85%29.aspx\n// - http://www.fontbureau.com/blog/the-em/\n// 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\nstatic double scaleUnits(double what, double designUnitsPerEm, double size)\n{\n\treturn (what / designUnitsPerEm) * (size * (96.0 / 72.0));\n}\n\nvoid uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics)\n{\n\tDWRITE_FONT_METRICS dm;\n\n\tfont->f->GetMetrics(&dm);\n\tmetrics->Ascent = scaleUnits(dm.ascent, dm.designUnitsPerEm, font->size);\n\tmetrics->Descent = scaleUnits(dm.descent, dm.designUnitsPerEm, font->size);\n\t// TODO what happens if dm.xxx is negative?\n\t// TODO remember what this was for\n\tmetrics->Leading = scaleUnits(dm.lineGap, dm.designUnitsPerEm, font->size);\n\tmetrics->UnderlinePos = scaleUnits(dm.underlinePosition, dm.designUnitsPerEm, font->size);\n\tmetrics->UnderlineThickness = scaleUnits(dm.underlineThickness, dm.designUnitsPerEm, font->size);\n}\n\n// 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\n// 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\nenum layoutAttrType {\n\tlayoutAttrColor,\n};\n\nstruct layoutAttr {\n\tenum layoutAttrType type;\n\tint start;\n\tint end;\n\tdouble components[4];\n};\n\nstruct uiDrawTextLayout {\n\tWCHAR *text;\n\tsize_t textlen;\n\tsize_t *graphemes;\n\tdouble width;\n\tIDWriteTextFormat *format;\n\tstd::vector<struct layoutAttr> *attrs;\n};\n\nuiDrawTextLayout *uiDrawNewTextLayout(const char *text, uiDrawTextFont *defaultFont, double width)\n{\n\tuiDrawTextLayout *layout;\n\tHRESULT hr;\n\n\tlayout = uiNew(uiDrawTextLayout);\n\n\thr = dwfactory->CreateTextFormat(defaultFont->family,\n\t\tNULL,\n\t\tdefaultFont->f->GetWeight(),\n\t\tdefaultFont->f->GetStyle(),\n\t\tdefaultFont->f->GetStretch(),\n\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating IDWriteTextFormat\", hr);\n\n\tlayout->text = toUTF16(text);\n\tlayout->textlen = wcslen(layout->text);\n\tlayout->graphemes = graphemes(layout->text);\n\n\tuiDrawTextLayoutSetWidth(layout, width);\n\n\tlayout->attrs = new std::vector<struct layoutAttr>;\n\n\treturn layout;\n}\n\nvoid uiDrawFreeTextLayout(uiDrawTextLayout *layout)\n{\n\tdelete layout->attrs;\n\tlayout->format->Release();\n\tuiFree(layout->graphemes);\n\tuiFree(layout->text);\n\tuiFree(layout);\n}\n\n\nIDWriteTextLayout *prepareLayout(uiDrawTextLayout *layout, ID2D1RenderTarget *rt)\n{\n\tIDWriteTextLayout *dl;\n\tDWRITE_TEXT_RANGE range;\n\tIUnknown *unkBrush;\n\tHRESULT hr;\n\n\tfor (const struct layoutAttr &attr : *(layout->attrs)) {\n\t\trange.startPosition = layout->graphemes[attr.start];\n\t\trange.length = layout->graphemes[attr.end] - layout->graphemes[attr.start];\n\t\tswitch (attr.type) {\n\t\tcase layoutAttrColor:\n\t\t\tif (rt == NULL)\t\t// determining extents, not drawing\n\t\t\t\tbreak;\n\t\t\tunkBrush = mkSolidBrush(rt,\n\t\t\t\tattr.components[0],\n\t\t\t\tattr.components[1],\n\t\t\t\tattr.components[2],\n\t\t\t\tattr.components[3]);\n\t\t\thr = dl->SetDrawingEffect(unkBrush, range);\n\t\t\tunkBrush->Release();\t\t// associated with dl\n\t\t\tbreak;\n\t\tdefault:\n\t\t\thr = E_FAIL;\n\t\t\tlogHRESULT(L\"invalid text attribute type\", hr);\n\t\t}\n\t\tif (hr != S_OK)\n\t\t\tlogHRESULT(L\"error adding attribute to text layout\", hr);\n\t}\n\n\n\n\treturn dl;\n}\n\n\nvoid uiDrawText(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout)\n{\n\tIDWriteTextLayout *dl;\n\tD2D1_POINT_2F pt;\n\tID2D1Brush *black;\n\tHRESULT hr;\n\n\t// TODO document that fully opaque black is the default text color; figure out whether this is upheld in various scenarios on other platforms\n\tblack = mkSolidBrush(c->rt, 0.0, 0.0, 0.0, 1.0);\n\n\tdl = prepareLayout(layout, c->rt);\n\tpt.x = x;\n\tpt.y = y;\n\t// TODO D2D1_DRAW_TEXT_OPTIONS_NO_SNAP?\n\t// TODO D2D1_DRAW_TEXT_OPTIONS_CLIP?\n\t// TODO when setting 8.1 as minimum, D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT?\n\tc->rt->DrawTextLayout(pt, dl, black, D2D1_DRAW_TEXT_OPTIONS_NONE);\n\tdl->Release();\n\n\tblack->Release();\n}\n\nvoid uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, int startChar, int endChar, double r, double g, double b, double a)\n{\n\tstruct layoutAttr attr;\n\n\tattr.type = layoutAttrColor;\n\tattr.start = startChar;\n\tattr.end = endChar;\n\tattr.components[0] = r;\n\tattr.components[1] = g;\n\tattr.components[2] = b;\n\tattr.components[3] = a;\n\tlayout->attrs->push_back(attr);\n}\n"
  },
  {
    "path": "_wip/attrstr_metrics/windows_OLD_drawtext.cpp",
    "content": "// 17 january 2017\n#include \"uipriv_windows.hpp\"\n#include \"draw.hpp\"\n\n// TODO\n// - consider the warnings about antialiasing in the PadWrite sample\n// - if that's not a problem, do we have overlapping rects in the hittest sample? I can't tell...\n// - empty string: nLines == 1 and all checks out except extents has x == 0 when not left aligned\n// - paragraph alignment is subject to RTL mirroring; see if it is on other platforms\n// - add overhang info to metrics?\n\n// TODO verify our renderer is correct, especially with regards to snapping\n\nstruct uiDrawTextLayout {\n\tIDWriteTextFormat *format;\n\tIDWriteTextLayout *layout;\n\tstd::vector<backgroundFunc> *backgroundFuncs;\n\tUINT32 nLines;\n\tstruct lineInfo *lineInfo;\n\t// for converting DirectWrite indices from/to byte offsets\n\tsize_t *u8tou16;\n\tsize_t nUTF8;\n\tsize_t *u16tou8;\n\tsize_t nUTF16;\n};\n\n// TODO copy notes about DirectWrite DIPs being equal to Direct2D DIPs here\n\n// typographic points are 1/72 inch; this parameter is 1/96 inch\n// fortunately Microsoft does this too, in https://msdn.microsoft.com/en-us/library/windows/desktop/dd371554%28v=vs.85%29.aspx\n#define pointSizeToDWriteSize(size) (size * (96.0 / 72.0))\n\nstruct lineInfo {\n\tsize_t startPos;\t\t\t// in UTF-16 points\n\tsize_t endPos;\n\tsize_t newlineCount;\n\tdouble x;\n\tdouble y;\n\tdouble width;\n\tdouble height;\n\tdouble baseline;\n};\n\n// 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\nstatic void computeLineInfo(uiDrawTextLayout *tl)\n{\n\tDWRITE_LINE_METRICS *dlm;\n\tsize_t nextStart;\n\tUINT32 i, j;\n\tDWRITE_HIT_TEST_METRICS *htm;\n\tUINT32 nFragments, unused;\n\tHRESULT hr;\n\n\t// TODO make sure this is legal; if not, switch to GetMetrics() and use its line count field instead\n\thr = tl->layout->GetLineMetrics(NULL, 0, &(tl->nLines));\n\t// ugh, HRESULT_TO_WIN32() is an inline function and is not constexpr so we can't use switch here\n\tif (hr == S_OK) {\n\t\t// TODO what do we do here\n\t} else if (hr != E_NOT_SUFFICIENT_BUFFER)\n\t\tlogHRESULT(L\"error getting number of lines in IDWriteTextLayout\", hr);\n\ttl->lineInfo = (struct lineInfo *) uiAlloc(tl->nLines * sizeof (struct lineInfo), \"struct lineInfo[] (text layout)\");\n\n\tdlm = new DWRITE_LINE_METRICS[tl->nLines];\n\t// we can't pass NULL here; it outright crashes if we do\n\t// TODO verify the numbers haven't changed\n\thr = tl->layout->GetLineMetrics(dlm, tl->nLines, &unused);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error getting IDWriteTextLayout line metrics\", hr);\n\n\t// assume the first line starts at position 0 and the string flow is incremental\n\tnextStart = 0;\n\tfor (i = 0; i < tl->nLines; i++) {\n\t\ttl->lineInfo[i].startPos = nextStart;\n\t\ttl->lineInfo[i].endPos = nextStart + dlm[i].length;\n\t\ttl->lineInfo[i].newlineCount = dlm[i].newlineLength;\n\t\tnextStart = tl->lineInfo[i].endPos;\n\n\t\t// a line can have multiple fragments; for example, if there's a bidirectional override in the middle of a line\n\t\thr = tl->layout->HitTestTextRange(tl->lineInfo[i].startPos, (tl->lineInfo[i].endPos - tl->lineInfo[i].newlineCount) - tl->lineInfo[i].startPos,\n\t\t\t0, 0,\n\t\t\tNULL, 0, &nFragments);\n\t\tif (hr != S_OK && hr != E_NOT_SUFFICIENT_BUFFER)\n\t\t\tlogHRESULT(L\"error getting IDWriteTextLayout line fragment count\", hr);\n\t\thtm = new DWRITE_HIT_TEST_METRICS[nFragments];\n\t\t// TODO verify unused == nFragments?\n\t\thr = tl->layout->HitTestTextRange(tl->lineInfo[i].startPos, (tl->lineInfo[i].endPos - tl->lineInfo[i].newlineCount) - tl->lineInfo[i].startPos,\n\t\t\t0, 0,\n\t\t\thtm, nFragments, &unused);\n\t\t// TODO can this return E_NOT_SUFFICIENT_BUFFER again?\n\t\tif (hr != S_OK)\n\t\t\tlogHRESULT(L\"error getting IDWriteTextLayout line fragment metrics\", hr);\n\t\t// TODO verify htm.textPosition and htm.length against dtm[i]/tl->lineInfo[i]?\n\t\ttl->lineInfo[i].x = htm[0].left;\n\t\ttl->lineInfo[i].y = htm[0].top;\n\t\t// TODO does this not include trailing whitespace? I forget\n\t\ttl->lineInfo[i].width = htm[0].width;\n\t\ttl->lineInfo[i].height = htm[0].height;\n\t\tfor (j = 1; j < nFragments; j++) {\n\t\t\t// this is correct even if the leftmost fragment on the line is RTL\n\t\t\tif (tl->lineInfo[i].x > htm[j].left)\n\t\t\t\ttl->lineInfo[i].x = htm[j].left;\n\t\t\ttl->lineInfo[i].width += htm[j].width;\n\t\t\t// TODO verify y and height haven't changed?\n\t\t}\n\t\t// TODO verify dlm[i].height == htm.height?\n\t\tdelete[] htm;\n\n\t\t// 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?\n\t\t// TODO and even on those platforms can we somehow split tyographic leading from spacing?\n\t\t// TODO and on that note, can we have both line spacing proportionally above and uniformly below?\n\t\ttl->lineInfo[i].baseline = dlm[i].baseline;\n\t}\n\n\tdelete[] dlm;\n}\n\n// 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)\nstatic std::map<uiDrawTextAlign, DWRITE_TEXT_ALIGNMENT> dwriteAligns = {\n\t{ uiDrawTextAlignLeft, DWRITE_TEXT_ALIGNMENT_LEADING },\n\t{ uiDrawTextAlignCenter, DWRITE_TEXT_ALIGNMENT_CENTER },\n\t{ uiDrawTextAlignRight, DWRITE_TEXT_ALIGNMENT_TRAILING },\n};\n\nuiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *p)\n{\n\tuiDrawTextLayout *tl;\n\tWCHAR *wDefaultFamily;\n\tDWRITE_WORD_WRAPPING wrap;\n\tFLOAT maxWidth;\n\tHRESULT hr;\n\n\ttl = uiNew(uiDrawTextLayout);\n\n\twDefaultFamily = toUTF16(p->DefaultFont->Family);\n\thr = dwfactory->CreateTextFormat(\n\t\twDefaultFamily, NULL,\n\t\tuiprivWeightToDWriteWeight(p->DefaultFont->Weight),\n\t\tuiprivItalicToDWriteStyle(p->DefaultFont->Italic),\n\t\tuiprivStretchToDWriteStretch(p->DefaultFont->Stretch),\n\t\tpointSizeToDWriteSize(p->DefaultFont->Size),\n\t\t// see http://stackoverflow.com/questions/28397971/idwritefactorycreatetextformat-failing and https://msdn.microsoft.com/en-us/library/windows/desktop/dd368203.aspx\n\t\t// TODO use the current locale?\n\t\tL\"\",\n\t\t&(tl->format));\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating IDWriteTextFormat\", hr);\n\thr = tl->format->SetTextAlignment(dwriteAligns[p->Align]);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error applying text layout alignment\", hr);\n\n\thr = dwfactory->CreateTextLayout(\n\t\t(const WCHAR *) attrstrUTF16(p->String), attrstrUTF16Len(p->String),\n\t\ttl->format,\n\t\t// FLOAT is float, not double, so this should work... TODO\n\t\tFLT_MAX, FLT_MAX,\n\t\t&(tl->layout));\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating IDWriteTextLayout\", hr);\n\n\t// and set the width\n\t// 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)\n\twrap = DWRITE_WORD_WRAPPING_WRAP;\n\tmaxWidth = (FLOAT) (p->Width);\n\tif (p->Width < 0) {\n\t\t// TODO is this wrapping juggling even necessary?\n\t\twrap = DWRITE_WORD_WRAPPING_NO_WRAP;\n\t\t// 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\n\t\tmaxWidth = FLT_MAX;\t\t// see TODO above\n\t}\n\thr = tl->layout->SetWordWrapping(wrap);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error setting IDWriteTextLayout word wrapping mode\", hr);\n\thr = tl->layout->SetMaxWidth(maxWidth);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error setting IDWriteTextLayout max layout width\", hr);\n\n\tattrstrToIDWriteTextLayoutAttrs(p, tl->layout, &(tl->backgroundFuncs));\n\n\tcomputeLineInfo(tl);\n\n\t// and finally copy the UTF-8/UTF-16 index conversion tables\n\ttl->u8tou16 = attrstrCopyUTF8ToUTF16(p->String, &(tl->nUTF8));\n\ttl->u16tou8 = attrstrCopyUTF16ToUTF8(p->String, &(tl->nUTF16));\n\n\t// TODO can/should this be moved elsewhere?\n\tuiFree(wDefaultFamily);\n\treturn tl;\n}\n\nvoid uiDrawFreeTextLayout(uiDrawTextLayout *tl)\n{\n\tuiFree(tl->u16tou8);\n\tuiFree(tl->u8tou16);\n\tuiFree(tl->lineInfo);\n\tdelete tl->backgroundFuncs;\n\ttl->layout->Release();\n\ttl->format->Release();\n\tuiFree(tl);\n}\n\nstatic HRESULT mkSolidBrush(ID2D1RenderTarget *rt, double r, double g, double b, double a, ID2D1SolidColorBrush **brush)\n{\n\tD2D1_BRUSH_PROPERTIES props;\n\tD2D1_COLOR_F color;\n\n\tZeroMemory(&props, sizeof (D2D1_BRUSH_PROPERTIES));\n\tprops.opacity = 1.0;\n\t// identity matrix\n\tprops.transform._11 = 1;\n\tprops.transform._22 = 1;\n\tcolor.r = r;\n\tcolor.g = g;\n\tcolor.b = b;\n\tcolor.a = a;\n\treturn rt->CreateSolidColorBrush(\n\t\t&color,\n\t\t&props,\n\t\tbrush);\n}\n\nstatic ID2D1SolidColorBrush *mustMakeSolidBrush(ID2D1RenderTarget *rt, double r, double g, double b, double a)\n{\n\tID2D1SolidColorBrush *brush;\n\tHRESULT hr;\n\n\thr = mkSolidBrush(rt, r, g, b, a, &brush);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating solid brush\", hr);\n\treturn brush;\n}\n\n// some of the stuff we want to do isn't possible with what DirectWrite provides itself; we need to do it ourselves\n// this is based on http://www.charlespetzold.com/blog/2014/01/Character-Formatting-Extensions-with-DirectWrite.html\nclass textRenderer : public IDWriteTextRenderer {\n\tULONG refcount;\n\tID2D1RenderTarget *rt;\n\tBOOL snap;\n\tID2D1SolidColorBrush *black;\npublic:\n\ttextRenderer(ID2D1RenderTarget *rt, BOOL snap, ID2D1SolidColorBrush *black)\n\t{\n\t\tthis->refcount = 1;\n\t\tthis->rt = rt;\n\t\tthis->snap = snap;\n\t\tthis->black = black;\n\t}\n\n\t// IUnknown\n\tvirtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject)\n\t{\n\t\tif (ppvObject == NULL)\n\t\t\treturn E_POINTER;\n\t\tif (riid == IID_IUnknown ||\n\t\t\triid == __uuidof (IDWritePixelSnapping) ||\n\t\t\triid == __uuidof (IDWriteTextRenderer)) {\n\t\t\tthis->AddRef();\n\t\t\t*ppvObject = this;\n\t\t\treturn S_OK;\n\t\t}\n\t\t*ppvObject = NULL;\n\t\treturn E_NOINTERFACE;\n\t}\n\n\tvirtual ULONG STDMETHODCALLTYPE AddRef(void)\n\t{\n\t\tthis->refcount++;\n\t\treturn this->refcount;\n\t}\n\n\tvirtual ULONG STDMETHODCALLTYPE Release(void)\n\t{\n\t\tthis->refcount--;\n\t\tif (this->refcount == 0) {\n\t\t\tdelete this;\n\t\t\treturn 0;\n\t\t}\n\t\treturn this->refcount;\n\t}\n\n\t// IDWritePixelSnapping\n\tvirtual HRESULT STDMETHODCALLTYPE GetCurrentTransform(void *clientDrawingContext, DWRITE_MATRIX *transform)\n\t{\n\t\tD2D1_MATRIX_3X2_F d2dtf;\n\n\t\tif (transform == NULL)\n\t\t\treturn E_POINTER;\n\t\tthis->rt->GetTransform(&d2dtf);\n\t\ttransform->m11 = d2dtf._11;\n\t\ttransform->m12 = d2dtf._12;\n\t\ttransform->m21 = d2dtf._21;\n\t\ttransform->m22 = d2dtf._22;\n\t\ttransform->dx = d2dtf._31;\n\t\ttransform->dy = d2dtf._32;\n\t\treturn S_OK;\n\t}\n\n\tvirtual HRESULT STDMETHODCALLTYPE GetPixelsPerDip(void *clientDrawingContext, FLOAT *pixelsPerDip)\n\t{\n\t\tFLOAT dpix, dpiy;\n\n\t\tif (pixelsPerDip == NULL)\n\t\t\treturn E_POINTER;\n\t\tthis->rt->GetDpi(&dpix, &dpiy);\n\t\t*pixelsPerDip = dpix / 96;\n\t\treturn S_OK;\n\t}\n\n\tvirtual HRESULT STDMETHODCALLTYPE IsPixelSnappingDisabled(void *clientDrawingContext, BOOL *isDisabled)\n\t{\n\t\tif (isDisabled == NULL)\n\t\t\treturn E_POINTER;\n\t\t*isDisabled = !this->snap;\n\t\treturn S_OK;\n\t}\n\n\t// IDWriteTextRenderer\n\tvirtual 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)\n\t{\n\t\tD2D1_POINT_2F baseline;\n\t\ttextDrawingEffect *t = (textDrawingEffect *) clientDrawingEffect;\n\t\tID2D1SolidColorBrush *brush;\n\n\t\tbaseline.x = baselineOriginX;\n\t\tbaseline.y = baselineOriginY;\n\t\tbrush = this->black;\n\t\tif (t != NULL && t->hasColor) {\n\t\t\tHRESULT hr;\n\n\t\t\thr = mkSolidBrush(this->rt, t->r, t->g, t->b, t->a, &brush);\n\t\t\tif (hr != S_OK)\n\t\t\t\treturn hr;\n\t\t}\n\t\tthis->rt->DrawGlyphRun(\n\t\t\tbaseline,\n\t\t\tglyphRun,\n\t\t\tbrush,\n\t\t\tmeasuringMode);\n\t\tif (t != NULL && t->hasColor)\n\t\t\tbrush->Release();\n\t\treturn S_OK;\n\t}\n\n\tvirtual HRESULT STDMETHODCALLTYPE DrawInlineObject(void *clientDrawingContext, FLOAT originX, FLOAT originY, IDWriteInlineObject *inlineObject, BOOL isSideways, BOOL isRightToLeft, IUnknown *clientDrawingEffect)\n\t{\n\t\tif (inlineObject == NULL)\n\t\t\treturn E_POINTER;\n\t\treturn inlineObject->Draw(clientDrawingContext, this,\n\t\t\toriginX, originY,\n\t\t\tisSideways, isRightToLeft,\n\t\t\tclientDrawingEffect);\n\t}\n\n\tvirtual HRESULT STDMETHODCALLTYPE DrawStrikethrough(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, const DWRITE_STRIKETHROUGH *strikethrough, IUnknown *clientDrawingEffect)\n\t{\n\t\t// we don't support strikethrough\n\t\treturn E_UNEXPECTED;\n\t}\n\n\tvirtual HRESULT STDMETHODCALLTYPE DrawUnderline(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, const DWRITE_UNDERLINE *underline, IUnknown *clientDrawingEffect)\n\t{\n\t\ttextDrawingEffect *t = (textDrawingEffect *) clientDrawingEffect;\n\t\tID2D1SolidColorBrush *brush;\n\t\tD2D1_RECT_F rect;\n\t\tD2D1::Matrix3x2F pixeltf;\n\t\tFLOAT dpix, dpiy;\n\t\tD2D1_POINT_2F pt;\n\n\t\tif (underline == NULL)\n\t\t\treturn E_POINTER;\n\t\tif (t == NULL)\t\t// we can only get here through an underline\n\t\t\treturn E_UNEXPECTED;\n\t\tbrush = this->black;\n\t\tif (t->hasUnderlineColor) {\n\t\t\tHRESULT hr;\n\n\t\t\thr = mkSolidBrush(this->rt, t->ur, t->ug, t->ub, t->ua, &brush);\n\t\t\tif (hr != S_OK)\n\t\t\t\treturn hr;\n\t\t} else if (t->hasColor) {\n\t\t\t// TODO formalize this rule\n\t\t\tHRESULT hr;\n\n\t\t\thr = mkSolidBrush(this->rt, t->r, t->g, t->b, t->a, &brush);\n\t\t\tif (hr != S_OK)\n\t\t\t\treturn hr;\n\t\t}\n\t\trect.left = baselineOriginX;\n\t\trect.top = baselineOriginY + underline->offset;\n\t\trect.right = rect.left + underline->width;\n\t\trect.bottom = rect.top + underline->thickness;\n\t\tswitch (t->u) {\n\t\tcase uiDrawUnderlineStyleSingle:\n\t\t\tthis->rt->FillRectangle(&rect, brush);\n\t\t\tbreak;\n\t\tcase uiDrawUnderlineStyleDouble:\n\t\t\t// TODO do any of the matrix methods return errors?\n\t\t\t// TODO standardize double-underline shape across platforms? wavy underline shape?\n\t\t\tthis->rt->GetTransform(&pixeltf);\n\t\t\tthis->rt->GetDpi(&dpix, &dpiy);\n\t\t\tpixeltf = pixeltf * D2D1::Matrix3x2F::Scale(dpix / 96, dpiy / 96);\n\t\t\tpt.x = 0;\n\t\t\tpt.y = rect.top;\n\t\t\tpt = pixeltf.TransformPoint(pt);\n\t\t\trect.top = (FLOAT) ((int) (pt.y + 0.5));\n\t\t\tpixeltf.Invert();\n\t\t\tpt = pixeltf.TransformPoint(pt);\n\t\t\trect.top = pt.y;\n\t\t\t// first line\n\t\t\trect.top -= underline->thickness;\n\t\t\t// and it seems we need to recompute this\n\t\t\trect.bottom = rect.top + underline->thickness;\n\t\t\tthis->rt->FillRectangle(&rect, brush);\n\t\t\t// second line\n\t\t\trect.top += 2 * underline->thickness;\n\t\t\trect.bottom = rect.top + underline->thickness;\n\t\t\tthis->rt->FillRectangle(&rect, brush);\n\t\t\tbreak;\n\t\tcase uiDrawUnderlineStyleSuggestion:\n\t\t\t{\t\t// TODO get rid of the extra block\n\t\t\t\t\t// TODO properly clean resources on failure\n\t\t\t\t\t// TODO use fully qualified C overloads for all methods\n\t\t\t\t\t// TODO ensure all methods properly have errors handled\n\t\t\t\tID2D1PathGeometry *path;\n\t\t\t\tID2D1GeometrySink *sink;\n\t\t\t\tdouble amplitude, period, xOffset, yOffset;\n\t\t\t\tdouble t;\n\t\t\t\tbool first = true;\n\t\t\t\tHRESULT hr;\n\n\t\t\t\thr = d2dfactory->CreatePathGeometry(&path);\n\t\t\t\tif (hr != S_OK)\n\t\t\t\t\treturn hr;\n\t\t\t\thr = path->Open(&sink);\n\t\t\t\tif (hr != S_OK)\n\t\t\t\t\treturn hr;\n\t\t\t\tamplitude = underline->thickness;\n\t\t\t\tperiod = 5 * underline->thickness;\n\t\t\t\txOffset = baselineOriginX;\n\t\t\t\tyOffset = baselineOriginY + underline->offset;\n\t\t\t\tfor (t = 0; t < underline->width; t++) {\n\t\t\t\t\tdouble x, angle, y;\n\t\t\t\t\tD2D1_POINT_2F pt;\n\n\t\t\t\t\tx = t + xOffset;\n\t\t\t\t\tangle = 2 * uiPi * fmod(x, period) / period;\n\t\t\t\t\ty = amplitude * sin(angle) + yOffset;\n\t\t\t\t\tpt.x = x;\n\t\t\t\t\tpt.y = y;\n\t\t\t\t\tif (first) {\n\t\t\t\t\t\tsink->BeginFigure(pt, D2D1_FIGURE_BEGIN_HOLLOW);\n\t\t\t\t\t\tfirst = false;\n\t\t\t\t\t} else\n\t\t\t\t\t\tsink->AddLine(pt);\n\t\t\t\t}\n\t\t\t\tsink->EndFigure(D2D1_FIGURE_END_OPEN);\n\t\t\t\thr = sink->Close();\n\t\t\t\tif (hr != S_OK)\n\t\t\t\t\treturn hr;\n\t\t\t\tsink->Release();\n\t\t\t\tthis->rt->DrawGeometry(path, brush, underline->thickness);\n\t\t\t\tpath->Release();\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tif (t->hasUnderlineColor || t->hasColor)\n\t\t\tbrush->Release();\n\t\treturn S_OK;\n\t}\n};\n\n// TODO this ignores clipping?\nvoid uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)\n{\n\tD2D1_POINT_2F pt;\n\tID2D1SolidColorBrush *black;\n\ttextRenderer *renderer;\n\tHRESULT hr;\n\n\tfor (const auto &f : *(tl->backgroundFuncs))\n\t\tf(c, tl, x, y);\n\n\t// TODO document that fully opaque black is the default text color; figure out whether this is upheld in various scenarios on other platforms\n\t// TODO figure out if this needs to be cleaned out\n\tblack = mustMakeSolidBrush(c->rt, 0.0, 0.0, 0.0, 1.0);\n\n#define renderD2D 0\n#define renderOur 1\n#if renderD2D\n\tpt.x = x;\n\tpt.y = y;\n\t// TODO D2D1_DRAW_TEXT_OPTIONS_NO_SNAP?\n\t// TODO D2D1_DRAW_TEXT_OPTIONS_CLIP?\n\t// TODO LONGTERM when setting 8.1 as minimum (TODO verify), D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT?\n\t// TODO what is our pixel snapping setting related to the OPTIONS enum values?\n\tc->rt->DrawTextLayout(pt, tl->layout, black, D2D1_DRAW_TEXT_OPTIONS_NONE);\n#endif\n#if renderD2D && renderOur\n\t// draw ours semitransparent so we can check\n\t// TODO get the actual color Charles Petzold uses and use that\n\tblack->Release();\n\tblack = mustMakeSolidBrush(c->rt, 1.0, 0.0, 0.0, 0.75);\n#endif\n#if renderOur\n\trenderer = new textRenderer(c->rt,\n\t\tTRUE,\t\t\t// TODO FALSE for no-snap?\n\t\tblack);\n\thr = tl->layout->Draw(NULL,\n\t\trenderer,\n\t\tx, y);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error drawing IDWriteTextLayout\", hr);\n\trenderer->Release();\n#endif\n\n\tblack->Release();\n}\n\n// 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...\n// TODO width does not include trailing whitespace\nvoid uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height)\n{\n\tDWRITE_TEXT_METRICS metrics;\n\tHRESULT hr;\n\n\thr = tl->layout->GetMetrics(&metrics);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error getting IDWriteTextLayout layout metrics\", hr);\n\t*width = metrics.width;\n\t// 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)\n\t*height = metrics.height;\n}\n\nint uiDrawTextLayoutNumLines(uiDrawTextLayout *tl)\n{\n\treturn tl->nLines;\n}\n\n// DirectWrite doesn't provide a direct way to do this, so we have to do this manually\n// TODO does that comment still apply here or to the code at the top of this file?\nvoid uiDrawTextLayoutLineByteRange(uiDrawTextLayout *tl, int line, size_t *start, size_t *end)\n{\n\t*start = tl->lineInfo[line].startPos;\n\t*start = tl->u16tou8[*start];\n\t*end = tl->lineInfo[line].endPos - tl->lineInfo[line].newlineCount;\n\t*end = tl->u16tou8[*end];\n}\n\nvoid uiDrawTextLayoutLineGetMetrics(uiDrawTextLayout *tl, int line, uiDrawTextLayoutLineMetrics *m)\n{\n\tm->X = tl->lineInfo[line].x;\n\tm->Y = tl->lineInfo[line].y;\n\tm->Width = tl->lineInfo[line].width;\n\tm->Height = tl->lineInfo[line].height;\n\n\t// TODO rename tl->lineInfo[line].baseline to .baselineOffset or something of the sort to make its meaning more clear\n\tm->BaselineY = tl->lineInfo[line].y + tl->lineInfo[line].baseline;\n\tm->Ascent = tl->lineInfo[line].baseline;\n\tm->Descent = tl->lineInfo[line].height - tl->lineInfo[line].baseline;\n\tm->Leading = 0;\t\t// TODO\n\n\tm->ParagraphSpacingBefore = 0;\t\t// TODO\n\tm->LineHeightSpace = 0;\t\t\t\t// TODO\n\tm->LineSpacing = 0;\t\t\t\t// TODO\n\tm->ParagraphSpacing = 0;\t\t\t// TODO\n}\n\n// this algorithm comes from Microsoft's PadWrite sample, following TextEditor::SetSelectionFromPoint()\n// TODO go back through all of these and make sure we convert coordinates properly\n// TODO same for OS X\nvoid uiDrawTextLayoutHitTest(uiDrawTextLayout *tl, double x, double y, size_t *pos, int *line)\n{\n\tDWRITE_HIT_TEST_METRICS m;\n\tBOOL trailing, inside;\n\tsize_t p;\n\tUINT32 i;\n\tHRESULT hr;\n\n\thr = tl->layout->HitTestPoint(x, y,\n\t\t&trailing, &inside,\n\t\t&m);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error hit-testing IDWriteTextLayout\", hr);\n\tp = m.textPosition;\n\t// on a trailing hit, align to the nearest cluster\n\tif (trailing) {\n\t\tDWRITE_HIT_TEST_METRICS m2;\n\t\tFLOAT x, y;\t\t\t\t// crashes if I skip these :/\n\n\t\thr = tl->layout->HitTestTextPosition(m.textPosition, trailing,\n\t\t\t&x, &y, &m2);\n\t\tif (hr != S_OK)\n\t\t\tlogHRESULT(L\"error aligning trailing hit to nearest cluster\", hr);\n\t\tp = m2.textPosition + m2.length;\n\t}\n\t*pos = tl->u16tou8[p];\n\n\tfor (i = 0; i < tl->nLines; i++) {\n\t\tdouble ltop, lbottom;\n\n\t\tltop = tl->lineInfo[i].y;\n\t\tlbottom = ltop + tl->lineInfo[i].height;\n\t\t// y will already >= ltop at this point since the past lbottom should == ltop\n\t\tif (y < lbottom)\n\t\t\tbreak;\n\t}\n\tif (i == tl->nLines)\n\t\ti--;\n\t*line = i;\n}\n\ndouble uiDrawTextLayoutByteLocationInLine(uiDrawTextLayout *tl, size_t pos, int line)\n{\n\tBOOL trailing;\n\tDWRITE_HIT_TEST_METRICS m;\n\tFLOAT x, y;\n\tHRESULT hr;\n\n\tif (line < 0 || line >= tl->nLines)\n\t\treturn -1;\n\tpos = tl->u8tou16[pos];\n\t// note: >, not >=, because the position at endPos is valid!\n\tif (pos < tl->lineInfo[line].startPos || pos > tl->lineInfo[line].endPos)\n\t\treturn -1;\n\t// this behavior seems correct\n\t// there's also PadWrite's TextEditor::GetCaretRect() but that requires state...\n\t// TODO where does this fail?\n\ttrailing = FALSE;\n\tif (pos != 0 && pos != tl->nUTF16 && pos == tl->lineInfo[line].endPos) {\n\t\tpos--;\n\t\ttrailing = TRUE;\n\t}\n\thr = tl->layout->HitTestTextPosition(pos, trailing,\n\t\t&x, &y, &m);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error calling IDWriteTextLayout::HitTestTextPosition()\", hr);\n\treturn x;\n}\n\nvoid caretDrawParams(uiDrawContext *c, double height, struct caretDrawParams *p)\n{\n\tDWORD caretWidth;\n\n\t// there seems to be no defined caret color\n\t// 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)\n\t// just return black for now\n\tp->r = 0.0;\n\tp->g = 0.0;\n\tp->b = 0.0;\n\tp->a = 1.0;\n\n\tif (SystemParametersInfoW(SPI_GETCARETWIDTH, 0, &caretWidth, 0) == 0)\n\t\t// don't log the failure, fall back gracefully\n\t\t// the instruction to use this comes from https://msdn.microsoft.com/en-us/library/windows/desktop/ms648399(v=vs.85).aspx\n\t\t// and we have to assume GetSystemMetrics() always succeeds, so\n\t\tcaretWidth = GetSystemMetrics(SM_CXBORDER);\n\t// TODO make this a function and split it out of areautil.cpp\n\t{\n\t\tFLOAT dpix, dpiy;\n\n\t\t// TODO can we pass NULL for dpiy?\n\t\tc->rt->GetDpi(&dpix, &dpiy);\n\t\t// see https://msdn.microsoft.com/en-us/library/windows/desktop/dd756649%28v=vs.85%29.aspx (and others; search \"direct2d mouse\")\n\t\tp->width = ((double) (caretWidth * 96)) / dpix;\n\t}\n\t// and there doesn't seem to be this either... (TODO check what PadWrite does?)\n\tp->xoff = 0;\n}\n"
  },
  {
    "path": "_wip/examples_drawtext/attributes.c",
    "content": "// 11 february 2017\n#include \"drawtext.h\"\n\nstatic uiAttributedString *attrstr;\n\n#define nFeatures 256\nstatic uiOpenTypeFeatures *features[nFeatures];\nstatic int curFeature = 0;\n\nstatic uiOpenTypeFeatures *addFeature(const char tag[4], uint32_t value)\n{\n\tuiOpenTypeFeatures *otf;\n\n\tif (curFeature >= nFeatures) {\n\t\tfprintf(stderr, \"TODO (also TODO is there a panic function?)\\n\");\n\t\texit(EXIT_FAILURE);\n\t}\n\totf = uiNewOpenTypeFeatures();\n\tuiOpenTypeFeaturesAdd(otf, tag[0], tag[1], tag[2], tag[3], value);\n\tfeatures[curFeature] = otf;\n\tcurFeature++;\n\treturn otf;\n}\n\n// 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\nstatic void setupAttributedString(void)\n{\n\tuiAttributeSpec spec;\n\tsize_t start, end;\n\tconst char *next;\n\tuiOpenTypeFeatures *otf;\n\tint i;\n\n\tattrstr = uiNewAttributedString(\"uiAttributedString isn't just for plain text! It supports \");\n\n\tnext = \"multiple fonts\";\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFamily;\n\tspec.Family = \"Courier New\";\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"multiple sizes\";\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeSize;\n\tspec.Double = 18;\n\tuiAttributedStringSetAttribute(attrstr,\n\t\t&spec, start, end);\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"multiple weights\";\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeWeight;\n\tspec.Value = (uintptr_t) uiDrawTextWeightBold;\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"multiple italics\";\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeItalic;\n\tspec.Value = (uintptr_t) uiDrawTextItalicItalic;\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"multiple stretches\";\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeStretch;\n\tspec.Value = (uintptr_t) uiDrawTextStretchCondensed;\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"multiple colors\";\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeColor;\n\t// Direct2D \"Crimson\" (#DC143C)\n\tspec.R = 0.8627450980392156;\n\tspec.G = 0.0784313725490196;\n\tspec.B = 0.2352941176470588;\n\tspec.A = 0.75;\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"multiple backgrounds\";\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeBackground;\n\t// Direct2D \"Peach Puff\" (#FFDAB9)\n\t// TODO choose a darker color\n\tspec.R = 1.0;\n\tspec.G = 0.85490196078431372;\n\tspec.B = 0.7254901960784313;\n\tspec.A = 0.5;\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"multiple\";\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeUnderline;\n\tspec.Value = uiDrawUnderlineStyleSingle;\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \" \");\n\tnext = \"underlines\";\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeUnderline;\n\tspec.Value = uiDrawUnderlineStyleDouble;\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tspec.Type = uiAttributeUnderlineColor;\n\tspec.Value = uiDrawUnderlineColorCustom;\n\tspec.R = 0.5;\n\tspec.G = 0.0;\n\tspec.B = 1.0;\n\tspec.A = 1.0;\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \" (\");\n\tnext = \"including underlines for spelling correction and the like\";\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeUnderline;\n\tspec.Value = uiDrawUnderlineStyleSuggestion;\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tspec.Type = uiAttributeUnderlineColor;\n\tspec.Value = uiDrawUnderlineColorSpelling;\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\t// TODO randomize these ranges better\n\t// TODO make some overlap to test that\n\t// TODO also change colors to light foreground dark background\n\tnext = \"or any combination of the above\";\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeWeight;\n\tspec.Value = (uintptr_t) uiDrawTextWeightBold;\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end - 8);\n\tspec.Type = uiAttributeItalic;\n\tspec.Value = (uintptr_t) uiDrawTextItalicItalic;\n\tuiAttributedStringSetAttribute(attrstr, &spec, start + 3, end - 4);\n\tspec.Type = uiAttributeColor;\n\tspec.R = 0.8627450980392156;\n\tspec.G = 0.0784313725490196;\n\tspec.B = 0.2352941176470588;\n\tspec.A = 0.75;\n\tuiAttributedStringSetAttribute(attrstr, &spec, start + 12, end);\n\tspec.Type = uiAttributeFamily;\n\tspec.Family = \"Helvetica\";\n\tuiAttributedStringSetAttribute(attrstr, &spec, start + 8, end - 1);\n\tspec.Type = uiAttributeBackground;\n\tspec.R = 1.0;\n\tspec.G = 0.85490196078431372;\n\tspec.B = 0.7254901960784313;\n\tspec.A = 0.5;\n\tuiAttributedStringSetAttribute(attrstr, &spec, start + 5, end - 7);\n\tspec.Type = uiAttributeUnderline;\n\tspec.Value = uiDrawUnderlineStyleSingle;\n\tuiAttributedStringSetAttribute(attrstr, &spec, start + 9, end - 1);\n\n\t// TODO rewrite this to talk about OpenTpe instead\n\t// 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)\n\tuiAttributedStringAppendUnattributed(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): \");\n\n\tnext = \"fi\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"standard ligatures like f+i (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"liga\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\t// 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)\n\t// see also: https://www.w3.org/International/articles/inline-bidi-markup/#nomarkup\n\tnext = \"\\xD9\\x84\\xD8\\xA7\";\n\tuiAttributedStringAppendUnattributed(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\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"rlig\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \"\\xE2\\x80\\xAC)\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"ct\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"discretionary/rare ligatures like c+t (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"dlig\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"the\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"contextual ligatures like h+e in the (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"clig\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\totf = addFeature(\"hlig\", 1);\n\t// 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\n\tuiOpenTypeFeaturesAdd(otf, 'h', 'i', 's', 't', 1);\n\tnext = \"\\xC3\\x9F\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"historical ligatures like the decomposition of \\xC3\\x9F (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = otf;\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\t// TODO a different word than \"writing\"?\n\tnext = \"UnICasE wRITInG\";\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"unic\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"316\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"proportional (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"pnum\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \") and tabular/monospaced (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"tnum\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \") numbers\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"123\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"superscipts (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"sups\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"123\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"subscripts (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"subs\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"1st\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"ordinals (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"ordn\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"H2O\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"scientific inferiors (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"sinf\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"2/3\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"fraction forms (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n#if 0 /* TODO */\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFractionForms;\n\tspec.Value = uiAttributeFractionFormNone;\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n#endif\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"afrc\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"frac\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"0\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"slashed zeroes (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"zero\", 0);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \" vs. \");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"zero\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"\\xCE\\xA0\\xCE\\xA3\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"mathematical greek (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"mgrk\", 0);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \" vs. \");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"mgrk\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"qwertyuiop\\xE2\\x80\\xA2\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"ornamental forms (\");\n\tfor (i = 1; i < 11; i++) {\n\t\tstart = uiAttributedStringLen(attrstr);\n\t\tend = start + strlen(next);\n\t\tuiAttributedStringAppendUnattributed(attrstr, next);\n\t\tspec.Type = uiAttributeFeatures;\n\t\tspec.Features = addFeature(\"ornm\", i);\n\t\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\t\tnext = \"\\xE2\\x80\\xA2\";\n\t}\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"g\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"specific forms/alternates (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"aalt\", 0);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \" vs. \");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"aalt\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"ABCDEFGQWERTY\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"titling capital forms (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"titl\", 0);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \" vs. \");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"titl\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"\\xE7\\x80\\x86\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"alternate Han character forms (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"jp78\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \" vs. \");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"jp83\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\totf = addFeature(\"onum\", 0);\n\t// Core Text's internal AAT-to-OpenType mapping says to include this, so we include it too\n\t// TODO is it always set?\n\tuiOpenTypeFeaturesAdd(otf, 'l', 'n', 'u', 'm', 0);\n\tnext = \"0123456789\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"lowercase numbers (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = otf;\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \" vs. \");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\totf = addFeature(\"onum\", 1);\n\tuiOpenTypeFeaturesAdd(otf, 'l', 'n', 'u', 'm', 1);\n\tspec.Features = otf;\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"\\xE4\\xBC\\xBD\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"hanja to hangul translation (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"hngl\", 0);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \" vs. \");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"hngl\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"\\xE3\\x81\\x82\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"annotated glyph forms (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"nalt\", 0);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \" vs. \");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"nalt\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \" vs. \");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"nalt\", 4);\t\t\t// AAT inverted circle\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"\\xE3\\x81\\x82\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"ruby forms of kana (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"ruby\", 0);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \" vs. \");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"ruby\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"now is the time\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"italic forms of Latin letters in CJK fonts (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"ital\", 0);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \" vs. \");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"ital\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"{I} > {J}\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"case-sensitive character forms, such as punctuation (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"case\", 0);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \" vs. \");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"case\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"ABC\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"specialized spacing between uppercase letters (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"cpsp\", 0);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \" vs. \");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"cpsp\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"\\xE3\\x82\\xB9\\xE3\\x83\\x98\\xE3\\x83\\x88\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"alternate horizontal (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"hkna\", 0);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \" vs. \");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"hkna\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \") and vertical (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"vkna\", 0);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \" vs. \");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"vkna\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \") kana forms\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"g\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"stylistic alternates (\");\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tfor (i = 1; i <= 20; i++) {\n\t\tchar tag[4];\n\n\t\ttag[0] = 's';\n\t\ttag[1] = 's';\n\t\ttag[2] = '0';\n\t\tif (i >= 10)\n\t\t\ttag[2] = '1';\n\t\ttag[3] = (i % 10) + '0';\t\t// TODO see how I wrote this elsewhere\n\t\tstart = uiAttributedStringLen(attrstr);\n\t\tend = start + strlen(next);\n\t\tuiAttributedStringAppendUnattributed(attrstr, next);\n\t\tspec.Features = addFeature(tag, 1);\n\t\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\t}\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"first\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"contextual alternates (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"calt\", 0);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \" vs. \");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"calt\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"FONT\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"swashes (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"swsh\", 0);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \" vs. \");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"swsh\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"Font\";\n\tuiAttributedStringAppendUnattributed(attrstr, \"contextual swashes (\");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"cswh\", 0);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \" vs. \");\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"cswh\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \")\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"Small Caps\";\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"smcp\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\tnext = \"Petite Caps\";\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"pcap\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tnext = \"SMALL UPPERCASES\";\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"c2sp\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\tuiAttributedStringAppendUnattributed(attrstr, \", and \");\n\tnext = \"PETITE UPPERCASES\";\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(next);\n\tuiAttributedStringAppendUnattributed(attrstr, next);\n\tspec.Type = uiAttributeFeatures;\n\tspec.Features = addFeature(\"c2pc\", 1);\n\tuiAttributedStringSetAttribute(attrstr, &spec, start, end);\n\n\tuiAttributedStringAppendUnattributed(attrstr, \".\");\n\n\t// TODO write a dedicated example for experimenting with typographic features like the one in gtk3-demo\n}\n\nstatic char fontFamily[] = \"Times New Roman\";\n// TODO should be const; look at constructor function?\nstatic uiDrawFontDescriptor defaultFont = {\n\t.Family = fontFamily,\n\t.Size = 12,\n\t.Weight = uiDrawTextWeightNormal,\n\t.Italic = uiDrawTextItalicNormal,\n\t.Stretch = uiDrawTextStretchNormal,\n};\nstatic uiDrawTextLayoutParams params;\n\n#define margins 10\n\nstatic uiBox *panel;\nstatic uiCheckbox *showLineBounds;\nstatic uiFontButton *fontButton;\n\n// TODO should be const?\nstatic uiDrawBrush fillBrushes[4] = {\n\t{\n\t\t.Type = uiDrawBrushTypeSolid,\n\t\t.R = 1.0,\n\t\t.G = 0.0,\n\t\t.B = 0.0,\n\t\t.A = 0.5,\n\t},\n\t{\n\t\t.Type = uiDrawBrushTypeSolid,\n\t\t.R = 0.0,\n\t\t.G = 1.0,\n\t\t.B = 0.0,\n\t\t.A = 0.5,\n\t},\n\t{\n\t\t.Type = uiDrawBrushTypeSolid,\n\t\t.R = 0.0,\n\t\t.G = 0.0,\n\t\t.B = 1.0,\n\t\t.A = 0.5,\n\t},\n\t{\n\t\t.Type = uiDrawBrushTypeSolid,\n\t\t.R = 0.0,\n\t\t.G = 1.0,\n\t\t.B = 1.0,\n\t\t.A = 0.5,\n\t},\n};\n\nstatic void draw(uiAreaDrawParams *p)\n{\n\tuiDrawPath *path;\n\tuiDrawTextLayout *layout;\n\tuiDrawBrush b;\n\n\tb.Type = uiDrawBrushTypeSolid;\n\n\t// only clip the text, not the guides\n\tuiDrawSave(p->Context);\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathAddRectangle(path, margins, margins,\n\t\tp->AreaWidth - 2 * margins,\n\t\tp->AreaHeight - 2 * margins);\n\tuiDrawPathEnd(path);\n\tuiDrawClip(p->Context, path);\n\tuiDrawFreePath(path);\n\n\tparams.Width = p->AreaWidth - 2 * margins;\n\tlayout = uiDrawNewTextLayout(&params);\n\tuiDrawText(p->Context, layout, margins, margins);\n\n\tuiDrawRestore(p->Context);\n\n\tif (uiCheckboxChecked(showLineBounds)) {\n\t\tuiDrawTextLayoutLineMetrics m;\n\t\tint i, n;\n\t\tint fill = 0;\n\n\t\tn = uiDrawTextLayoutNumLines(layout);\n\t\tfor (i = 0; i < n; i++) {\n\t\t\tuiDrawTextLayoutLineGetMetrics(layout, i, &m);\n\n\t\t\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\t\t\tuiDrawPathAddRectangle(path, margins + m.X, margins + m.Y,\n\t\t\t\tm.Width, m.Height);\n\t\t\tuiDrawPathEnd(path);\n\t\t\tuiDrawFill(p->Context, path, fillBrushes + fill);\n\t\t\tuiDrawFreePath(path);\n\t\t\tfill = (fill + 1) % 4;\n\t\t}\n\t}\n\n\tuiDrawFreeTextLayout(layout);\n}\n\nstatic struct example attributesExample;\n\nstatic void changeFont(uiFontButton *b, void *data)\n{\n\tif (defaultFont.Family != fontFamily)\n\t\tuiFreeText(defaultFont.Family);\n\t// TODO rename defaultFont\n\tuiFontButtonFont(fontButton, &defaultFont);\n\tredraw();\n}\n\n// TODO share?\nstatic void checkboxChecked(uiCheckbox *c, void *data)\n{\n\tredraw();\n}\n\nstatic uiCheckbox *newCheckbox(const char *text)\n{\n\tuiCheckbox *c;\n\n\tc = uiNewCheckbox(text);\n\tuiCheckboxOnToggled(c, checkboxChecked, NULL);\n\tuiBoxAppend(panel, uiControl(c), 0);\n\treturn c;\n}\n\nstruct example *mkAttributesExample(void)\n{\n\tpanel = uiNewVerticalBox();\n\tshowLineBounds = newCheckbox(\"Show Line Bounds\");\n\tfontButton = uiNewFontButton();\n\tuiFontButtonOnChanged(fontButton, changeFont, NULL);\n\t// TODO set the font button to the current defaultFont\n\tuiBoxAppend(panel, uiControl(fontButton), 0);\n\n\tattributesExample.name = \"Attributed Text\";\n\tattributesExample.panel = uiControl(panel);\n\tattributesExample.draw = draw;\n\tattributesExample.mouse = NULL;\n\tattributesExample.key = NULL;\n\n\tsetupAttributedString();\n\tparams.String = attrstr;\n\tparams.DefaultFont = &defaultFont;\n\tparams.Align = uiDrawTextAlignLeft;\n\n\treturn &attributesExample;\n}\n"
  },
  {
    "path": "_wip/examples_drawtext/basic.c",
    "content": "// 17 january 2017\n#include \"drawtext.h\"\n\nstatic const char *text =\n\t\"It is with a kind of fear that I begin to write the history of my life. \"\n\t\"I have, as it were, a superstitious hesitation in lifting the veil that \"\n\t\"clings about my childhood like a golden mist. The task of writing an \"\n\t\"autobiography is a difficult one. When I try to classify my earliest \"\n\t\"impressions, I find that fact and fancy look alike across the years that \"\n\t\"link the past with the present. The woman paints the child's experiences \"\n\t\"in her own fantasy. A few impressions stand out vividly from the first \"\n\t\"years of my life; but \\\"the shadows of the prison-house are on the rest.\\\" \"\n\t\"Besides, many of the joys and sorrows of childhood have lost their \"\n\t\"poignancy; and many incidents of vital importance in my early education \"\n\t\"have been forgotten in the excitement of great discoveries. In order, \"\n\t\"therefore, not to be tedious I shall try to present in a series of \"\n\t\"sketches only the episodes that seem to me to be the most interesting \"\n\t\"and important.\"\n\t\"\";\nstatic char fontFamily[] = \"Palatino\";\n// TODO should be const; look at constructor function?\nstatic uiDrawFontDescriptor defaultFont = {\n\t.Family = fontFamily,\n\t.Size = 18,\n\t.Weight = uiDrawTextWeightNormal,\n\t.Italic = uiDrawTextItalicNormal,\n\t.Stretch = uiDrawTextStretchNormal,\n};\nstatic uiAttributedString *attrstr;\nstatic uiDrawTextLayoutParams params;\n\n#define margins 10\n\nstatic uiBox *panel;\nstatic uiCheckbox *showExtents;\nstatic uiCheckbox *showLineBounds;\nstatic uiCheckbox *showLineGuides;\n\n// TODO should this be const?\nstatic double strokeDashes[] = { 5, 2 };\n// TODO this should be const\nstatic uiDrawStrokeParams strokeParams = {\n\t.Cap = uiDrawLineCapFlat,\n\t.Join = uiDrawLineJoinMiter,\n\t.Thickness = 1,\n\t.MiterLimit = uiDrawDefaultMiterLimit,\n\t.Dashes = strokeDashes,\n\t.NumDashes = 2,\n\t.DashPhase = 0,\n};\n\n// TODO should be const?\nstatic uiDrawBrush fillBrushes[4] = {\n\t{\n\t\t.Type = uiDrawBrushTypeSolid,\n\t\t.R = 1.0,\n\t\t.G = 0.0,\n\t\t.B = 0.0,\n\t\t.A = 0.5,\n\t},\n\t{\n\t\t.Type = uiDrawBrushTypeSolid,\n\t\t.R = 0.0,\n\t\t.G = 1.0,\n\t\t.B = 0.0,\n\t\t.A = 0.5,\n\t},\n\t{\n\t\t.Type = uiDrawBrushTypeSolid,\n\t\t.R = 0.0,\n\t\t.G = 0.0,\n\t\t.B = 1.0,\n\t\t.A = 0.5,\n\t},\n\t{\n\t\t.Type = uiDrawBrushTypeSolid,\n\t\t.R = 0.0,\n\t\t.G = 1.0,\n\t\t.B = 1.0,\n\t\t.A = 0.5,\n\t},\n};\n// TODO come up with better colors\nstatic uiDrawBrush strokeBrushes[3] = {\n\t// baseline\n\t{\n\t\t.Type = uiDrawBrushTypeSolid,\n\t\t.R = 0.5,\n\t\t.G = 0.5,\n\t\t.B = 0.0,\n\t\t.A = 0.75,\n\t},\n\t// ascent\n\t{\n\t\t.Type = uiDrawBrushTypeSolid,\n\t\t.R = 1.0,\n\t\t.G = 0.0,\n\t\t.B = 1.0,\n\t\t.A = 0.75,\n\t},\n\t// descent\n\t{\n\t\t.Type = uiDrawBrushTypeSolid,\n\t\t.R = 0.5,\n\t\t.G = 0.75,\n\t\t.B = 1.0,\n\t\t.A = 0.75,\n\t},\n};\n\nstatic void draw(uiAreaDrawParams *p)\n{\n\tuiDrawPath *path;\n\tuiDrawTextLayout *layout;\n\tuiDrawBrush b;\n\n\tb.Type = uiDrawBrushTypeSolid;\n\n\t// only clip the text, not the guides\n\tuiDrawSave(p->Context);\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathAddRectangle(path, margins, margins,\n\t\tp->AreaWidth - 2 * margins,\n\t\tp->AreaHeight - 2 * margins);\n\tuiDrawPathEnd(path);\n\tuiDrawClip(p->Context, path);\n\tuiDrawFreePath(path);\n\n\t// TODO get rid of this later\n#if 0\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathAddRectangle(path, -100, -100,\n\t\tp->AreaWidth * 2,\n\t\tp->AreaHeight * 2);\n\tuiDrawPathEnd(path);\n\tb.R = 0.0;\n\tb.G = 1.0;\n\tb.B = 0.0;\n\tb.A = 1.0;\n\tuiDrawFill(p->Context, path, &b);\n\tuiDrawFreePath(path);\n#endif\n\n\tparams.Width = p->AreaWidth - 2 * margins;\n\tlayout = uiDrawNewTextLayout(&params);\n\tuiDrawText(p->Context, layout, margins, margins);\n\n\tuiDrawRestore(p->Context);\n\n\tif (uiCheckboxChecked(showExtents)) {\n\t\tdouble width, height;\n\n\t\tuiDrawTextLayoutExtents(layout, &width, &height);\n\t\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\t\tuiDrawPathAddRectangle(path, margins, margins,\n\t\t\twidth, height);\n\t\tuiDrawPathEnd(path);\n\t\tb.R = 1.0;\n\t\tb.G = 0.0;\n\t\tb.B = 1.0;\n\t\tb.A = 0.5;\n\t\tuiDrawStroke(p->Context, path, &b, &strokeParams);\n\t\tuiDrawFreePath(path);\n\t}\n\n\tif (uiCheckboxChecked(showLineBounds) || uiCheckboxChecked(showLineGuides)) {\n\t\tuiDrawTextLayoutLineMetrics m;\n\t\tint i, n;\n\t\tint fill = 0;\n\n\t\tn = uiDrawTextLayoutNumLines(layout);\n\t\tfor (i = 0; i < n; i++) {\n\t\t\tuiDrawTextLayoutLineGetMetrics(layout, i, &m);\n\n\t\t\tif (uiCheckboxChecked(showLineBounds)) {\n\t\t\t\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\t\t\t\tuiDrawPathAddRectangle(path, margins + m.X, margins + m.Y,\n\t\t\t\t\tm.Width, m.Height);\n\t\t\t\tuiDrawPathEnd(path);\n\t\t\t\tuiDrawFill(p->Context, path, fillBrushes + fill);\n\t\t\t\tuiDrawFreePath(path);\n\t\t\t\tfill = (fill + 1) % 4;\n\t\t\t}\n\t\t\tif (uiCheckboxChecked(showLineGuides)) {\n\t\t\t\t// baseline\n\t\t\t\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\t\t\t\tuiDrawPathNewFigure(path,\n\t\t\t\t\tmargins + m.X,\n\t\t\t\t\tmargins + m.BaselineY);\n\t\t\t\tuiDrawPathLineTo(path,\n\t\t\t\t\tmargins + m.X + m.Width,\n\t\t\t\t\tmargins + m.BaselineY);\n\t\t\t\tuiDrawPathEnd(path);\n\t\t\t\tuiDrawStroke(p->Context, path, &(strokeBrushes[0]), &strokeParams);\n\t\t\t\tuiDrawFreePath(path);\n\n\t\t\t\t// ascent line\n\t\t\t\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\t\t\t\tuiDrawPathNewFigure(path,\n\t\t\t\t\tmargins + m.X,\n\t\t\t\t\tmargins + m.BaselineY - m.Ascent);\n\t\t\t\tuiDrawPathLineTo(path,\n\t\t\t\t\tmargins + m.X + m.Width,\n\t\t\t\t\tmargins + m.BaselineY - m.Ascent);\n\t\t\t\tuiDrawPathEnd(path);\n\t\t\t\tuiDrawStroke(p->Context, path, &(strokeBrushes[1]), &strokeParams);\n\t\t\t\tuiDrawFreePath(path);\n\n\t\t\t\t// descent line\n\t\t\t\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\t\t\t\tuiDrawPathNewFigure(path,\n\t\t\t\t\tmargins + m.X,\n\t\t\t\t\tmargins + m.BaselineY + m.Descent);\n\t\t\t\tuiDrawPathLineTo(path,\n\t\t\t\t\tmargins + m.X + m.Width,\n\t\t\t\t\tmargins + m.BaselineY + m.Descent);\n\t\t\t\tuiDrawPathEnd(path);\n\t\t\t\tuiDrawStroke(p->Context, path, &(strokeBrushes[2]), &strokeParams);\n\t\t\t\tuiDrawFreePath(path);\n\t\t\t}\n\t\t}\n\t}\n\n\tuiDrawFreeTextLayout(layout);\n}\n\nstatic struct example basicExample;\n\n// TODO share?\nstatic void checkboxChecked(uiCheckbox *c, void *data)\n{\n\tredraw();\n}\n\nstatic uiCheckbox *newCheckbox(const char *text)\n{\n\tuiCheckbox *c;\n\n\tc = uiNewCheckbox(text);\n\tuiCheckboxOnToggled(c, checkboxChecked, NULL);\n\tuiBoxAppend(panel, uiControl(c), 0);\n\treturn c;\n}\n\nstruct example *mkBasicExample(void)\n{\n\tpanel = uiNewVerticalBox();\n\tshowExtents = newCheckbox(\"Show Layout Extents\");\n\tshowLineBounds = newCheckbox(\"Show Line Bounds\");\n\tshowLineGuides = newCheckbox(\"Show Line Guides\");\n\n\tbasicExample.name = \"Basic Paragraph of Text\";\n\tbasicExample.panel = uiControl(panel);\n\tbasicExample.draw = draw;\n\tbasicExample.mouse = NULL;\n\tbasicExample.key = NULL;\n\n\tattrstr = uiNewAttributedString(text);\n\tparams.String = attrstr;\n\tparams.DefaultFont = &defaultFont;\n\tparams.Align = uiDrawTextAlignLeft;\n\n\treturn &basicExample;\n}\n\n// TODO on GTK+ an area by itself in a window doesn't get expand properties set properly?\n"
  },
  {
    "path": "_wip/examples_drawtext/drawtext.h",
    "content": "// 20 january 2017\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include \"../../ui.h\"\n\nstruct example {\n\tconst char *name;\n\tuiControl *panel;\n\tvoid (*draw)(uiAreaDrawParams *p);\n\tvoid (*mouse)(uiAreaMouseEvent *e);\n\tint (*key)(uiAreaKeyEvent *e);\n\t// TODO key?\n};\n\n// main.c\nextern void redraw(void);\n\n// basic.c\nextern struct example *mkBasicExample(void);\n\n// hittest.c\nextern struct example *mkHitTestExample(void);\n\n// attributes.c\nextern struct example *mkAttributesExample(void);\n\n// emptystr_hittest.c\nextern struct example *mkEmptyStringExample(void);\n"
  },
  {
    "path": "_wip/examples_drawtext/emptystr_hittest.c",
    "content": "// 20 january 2017\n#include \"drawtext.h\"\n\n// TODO FOR THIS FILE\n// get rid of it once we rewrite this example (which requires having more fine-grained scrolling control)\n\n// TODO double-check ligatures on all platforms to make sure we can place the cursor at the right place\n// TODO the hiding and showing does not work properly on GTK+\n// 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\n// TODO make sure to check the cursor positions of RTL on all platforms\n\nstatic const char *text = \"\";\nstatic char fontFamily[] = \"Helvetica\";\nstatic uiDrawFontDescriptor defaultFont = {\n\t.Family = fontFamily,\n\t.Size = 14,\n\t.Weight = uiDrawTextWeightNormal,\n\t.Italic = uiDrawTextItalicNormal,\n\t.Stretch = uiDrawTextStretchNormal,\n};\nstatic uiAttributedString *attrstr;\nstatic uiDrawTextLayoutParams params;\n\n#define margins 10\n\nstatic uiBox *panel;\nstatic uiBox *vbox;\nstatic uiLabel *caretLabel;\nstatic uiCheckbox *showLineBounds;\nstatic uiFontButton *fontButton;\nstatic uiCombobox *textAlign;\n\nstatic int caretLine = -1;\nstatic size_t caretPos;\n\n// TODO should be const?\nstatic uiDrawBrush fillBrushes[4] = {\n\t{\n\t\t.Type = uiDrawBrushTypeSolid,\n\t\t.R = 1.0,\n\t\t.G = 0.0,\n\t\t.B = 0.0,\n\t\t.A = 0.5,\n\t},\n\t{\n\t\t.Type = uiDrawBrushTypeSolid,\n\t\t.R = 0.0,\n\t\t.G = 1.0,\n\t\t.B = 0.0,\n\t\t.A = 0.5,\n\t},\n\t{\n\t\t.Type = uiDrawBrushTypeSolid,\n\t\t.R = 0.0,\n\t\t.G = 0.0,\n\t\t.B = 1.0,\n\t\t.A = 0.5,\n\t},\n\t{\n\t\t.Type = uiDrawBrushTypeSolid,\n\t\t.R = 0.0,\n\t\t.G = 1.0,\n\t\t.B = 1.0,\n\t\t.A = 0.5,\n\t},\n};\n\nstatic void draw(uiAreaDrawParams *p)\n{\n\tuiDrawPath *path;\n\tuiDrawTextLayout *layout;\n\tuiDrawTextLayoutLineMetrics m;\n\n\t// only clip the text, not the guides\n\tuiDrawSave(p->Context);\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathAddRectangle(path, margins, margins,\n\t\tp->AreaWidth - 2 * margins,\n\t\tp->AreaHeight - 2 * margins);\n\tuiDrawPathEnd(path);\n\tuiDrawClip(p->Context, path);\n\tuiDrawFreePath(path);\n\n\tparams.Width = p->AreaWidth - 2 * margins;\n\tlayout = uiDrawNewTextLayout(&params);\n\tuiDrawText(p->Context, layout, margins, margins);\n\n\tuiDrawRestore(p->Context);\n\n\tif (caretLine == -1) {\n\t\tcaretLine = uiDrawTextLayoutNumLines(layout) - 1;\n\t\tcaretPos = uiAttributedStringLen(attrstr);\n\t}\n\tuiDrawCaret(p->Context, margins, margins,\n\t\tlayout, caretPos, &caretLine);\n\n\tif (uiCheckboxChecked(showLineBounds)) {\n\t\tint i, n;\n\t\tint fill = 0;\n\n\t\tn = uiDrawTextLayoutNumLines(layout);\n\t\tfor (i = 0; i < n; i++) {\n\t\t\tuiDrawTextLayoutLineGetMetrics(layout, i, &m);\n\t\t\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\t\t\tuiDrawPathAddRectangle(path, margins + m.X, margins + m.Y,\n\t\t\t\tm.Width, m.Height);\n\t\t\tuiDrawPathEnd(path);\n\t\t\tuiDrawFill(p->Context, path, fillBrushes + fill);\n\t\t\tuiDrawFreePath(path);\n\t\t\tfill = (fill + 1) % 4;\n\t\t}\n\t}\n\n\tuiDrawFreeTextLayout(layout);\n}\n\nstatic void mouse(uiAreaMouseEvent *e)\n{\n\tuiDrawTextLayout *layout;\n\tchar labelText[128];\n\n\tif (e->Down != 1)\n\t\treturn;\n\n\tparams.Width = e->AreaWidth - 2 * margins;\n\tlayout = uiDrawNewTextLayout(&params);\n\tuiDrawTextLayoutHitTest(layout,\n\t\te->X - margins, e->Y - margins,\n\t\t&caretPos, &caretLine);\n\tuiDrawFreeTextLayout(layout);\n\n\t// TODO move this into the draw handler so it is reflected by keyboard-based position changes\n\t// urgh %zd is not supported by MSVC with sprintf()\n\t// TODO get that part in test/ about having no other option\n\tsprintf(labelText, \"pos %d line %d\",\n\t\t(int) caretPos, caretLine);\n\tuiLabelSetText(caretLabel, labelText);\n\n\tredraw();\n}\n\nstatic int key(uiAreaKeyEvent *e)\n{\n\tsize_t grapheme;\n\n\tif (e->Up)\n\t\treturn 0;\n\tif (e->Key != 0)\n\t\treturn 0;\n\tswitch (e->ExtKey) {\n\tcase uiExtKeyUp:\n\t\t// TODO\n\t\treturn 1;\n\tcase uiExtKeyDown:\n\t\t// TODO\n\t\treturn 1;\n\tcase uiExtKeyLeft:\n\t\tgrapheme = uiAttributedStringByteIndexToGrapheme(attrstr, caretPos);\n\t\tif (grapheme == 0)\n\t\t\treturn 0;\n\t\tgrapheme--;\n\t\tcaretPos = uiAttributedStringGraphemeToByteIndex(attrstr, grapheme);\n\t\tredraw();\n\t\treturn 1;\n\tcase uiExtKeyRight:\n\t\tgrapheme = uiAttributedStringByteIndexToGrapheme(attrstr, caretPos);\n\t\tif (grapheme == uiAttributedStringNumGraphemes(attrstr))\n\t\t\treturn 0;\n\t\tgrapheme++;\n\t\tcaretPos = uiAttributedStringGraphemeToByteIndex(attrstr, grapheme);\n\t\tredraw();\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nstatic struct example hitTestExample;\n\n// TODO share?\nstatic void checkboxChecked(uiCheckbox *c, void *data)\n{\n\tredraw();\n}\n\nstatic void changeFont(uiFontButton *b, void *data)\n{\n\tif (defaultFont.Family != fontFamily)\n\t\tuiFreeText(defaultFont.Family);\n\t// TODO rename defaultFont\n\tuiFontButtonFont(fontButton, &defaultFont);\n\tprintf(\"{\\n\\tfamily: %s\\n\\tsize: %g\\n\\tweight: %d\\n\\titalic: %d\\n\\tstretch: %d\\n}\\n\",\n\t\tdefaultFont.Family,\n\t\tdefaultFont.Size,\n\t\t(int) (defaultFont.Weight),\n\t\t(int) (defaultFont.Italic),\n\t\t(int) (defaultFont.Stretch));\n\tredraw();\n}\n\nstatic void changeTextAlign(uiCombobox *c, void *data)\n{\n\t// note the order of the items added below\n\tparams.Align = (uiDrawTextAlign) uiComboboxSelected(textAlign);\n\tredraw();\n}\n\n// TODO share?\nstatic uiCheckbox *newCheckbox(uiBox *box, const char *text)\n{\n\tuiCheckbox *c;\n\n\tc = uiNewCheckbox(text);\n\tuiCheckboxOnToggled(c, checkboxChecked, NULL);\n\tuiBoxAppend(box, uiControl(c), 0);\n\treturn c;\n}\n\nstruct example *mkEmptyStringExample(void)\n{\n\tpanel = uiNewHorizontalBox();\n\tvbox = uiNewVerticalBox();\n\t// TODO the second vbox causes this not to stretch at least on OS X\n\tuiBoxAppend(panel, uiControl(vbox), 1);\n\tcaretLabel = uiNewLabel(\"Caret information is shown here\");\n\tuiBoxAppend(vbox, uiControl(caretLabel), 0);\n\tshowLineBounds = newCheckbox(vbox, \"Show Line Bounds (for debugging metrics)\");\n\tvbox = uiNewVerticalBox();\n\tuiBoxAppend(panel, uiControl(vbox), 0);\n\tfontButton = uiNewFontButton();\n\tuiFontButtonOnChanged(fontButton, changeFont, NULL);\n\t// TODO set the font button to the current defaultFont\n\tuiBoxAppend(vbox, uiControl(fontButton), 0);\n\ttextAlign = uiNewCombobox();\n\t// note that these are in the order in the enum\n\tuiComboboxAppend(textAlign, \"Left\");\n\tuiComboboxAppend(textAlign, \"Center\");\n\tuiComboboxAppend(textAlign, \"Right\");\n\tuiComboboxOnSelected(textAlign, changeTextAlign, NULL);\n\tuiBoxAppend(vbox, uiControl(textAlign), 0);\n\n\thitTestExample.name = \"Empty String\";\n\thitTestExample.panel = uiControl(panel);\n\thitTestExample.draw = draw;\n\thitTestExample.mouse = mouse;\n\thitTestExample.key = key;\n\n\tattrstr = uiNewAttributedString(text);\n\tparams.String = attrstr;\n\tparams.DefaultFont = &defaultFont;\n\tparams.Align = uiDrawTextAlignLeft;\n\n\treturn &hitTestExample;\n}\n"
  },
  {
    "path": "_wip/examples_drawtext/hittest.c",
    "content": "// 20 january 2017\n#include \"drawtext.h\"\n\n// TODO double-check ligatures on all platforms to make sure we can place the cursor at the right place\n// 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\n// TODO make sure to check the cursor positions of RTL on all platforms\n\nstatic const char *text =\n\t\"Each of the glyphs an end user interacts with are called graphemes. \"\n\t\"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. \"\n\t// TODO rephrase this; I don't think this code will use those grapheme functions...\n\t\"You'll also see the index of the first grapheme; uiAttributedString has facilities for converting between UTF-8 code points and grapheme indices. \"\n\t\"Additionally, click on the string to move the caret. Watch the status text at the bottom change too. \"\n\t\"That being said: \"\n\t\"\\xC3\\x93O\\xCC\\x81 (combining accents) \"\n\t\"A\\xCC\\xAA\\xEF\\xB8\\xA0 (multiple combining characters) \"\n\t\"\\xE2\\x80\\xAE#\\xE2\\x80\\xAC (RTL glyph) \"\n\t\"\\xF0\\x9F\\x92\\xBB (non-BMP character) \"\n\t\"\\xF0\\x9F\\x92\\xBB\\xCC\\x80 (combined non-BMP character; may render strangely) \"\n\t\"\";\nstatic char fontFamily[] = \"Helvetica\";\nstatic uiDrawFontDescriptor defaultFont = {\n\t.Family = fontFamily,\n\t.Size = 14,\n\t.Weight = uiDrawTextWeightNormal,\n\t.Italic = uiDrawTextItalicNormal,\n\t.Stretch = uiDrawTextStretchNormal,\n};\nstatic uiAttributedString *attrstr;\nstatic uiDrawTextLayoutParams params;\n\n#define margins 10\n\nstatic uiBox *panel;\nstatic uiBox *vbox;\nstatic uiLabel *caretLabel;\nstatic uiCheckbox *showLineBounds;\nstatic uiFontButton *fontButton;\nstatic uiCombobox *textAlign;\n\nstatic int caretLine = -1;\nstatic size_t caretPos;\n\n// TODO should be const?\nstatic uiDrawBrush fillBrushes[4] = {\n\t{\n\t\t.Type = uiDrawBrushTypeSolid,\n\t\t.R = 1.0,\n\t\t.G = 0.0,\n\t\t.B = 0.0,\n\t\t.A = 0.5,\n\t},\n\t{\n\t\t.Type = uiDrawBrushTypeSolid,\n\t\t.R = 0.0,\n\t\t.G = 1.0,\n\t\t.B = 0.0,\n\t\t.A = 0.5,\n\t},\n\t{\n\t\t.Type = uiDrawBrushTypeSolid,\n\t\t.R = 0.0,\n\t\t.G = 0.0,\n\t\t.B = 1.0,\n\t\t.A = 0.5,\n\t},\n\t{\n\t\t.Type = uiDrawBrushTypeSolid,\n\t\t.R = 0.0,\n\t\t.G = 1.0,\n\t\t.B = 1.0,\n\t\t.A = 0.5,\n\t},\n};\n\nstatic void draw(uiAreaDrawParams *p)\n{\n\tuiDrawPath *path;\n\tuiDrawTextLayout *layout;\n\tuiDrawTextLayoutLineMetrics m;\n\n\t// only clip the text, not the guides\n\tuiDrawSave(p->Context);\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathAddRectangle(path, margins, margins,\n\t\tp->AreaWidth - 2 * margins,\n\t\tp->AreaHeight - 2 * margins);\n\tuiDrawPathEnd(path);\n\tuiDrawClip(p->Context, path);\n\tuiDrawFreePath(path);\n\n\tparams.Width = p->AreaWidth - 2 * margins;\n\tlayout = uiDrawNewTextLayout(&params);\n\tuiDrawText(p->Context, layout, margins, margins);\n\n\tuiDrawRestore(p->Context);\n\n\tif (caretLine == -1) {\n\t\tcaretLine = uiDrawTextLayoutNumLines(layout) - 1;\n\t\tcaretPos = uiAttributedStringLen(attrstr);\n\t}\n\tuiDrawCaret(p->Context, margins, margins,\n\t\tlayout, caretPos, &caretLine);\n\n\tif (uiCheckboxChecked(showLineBounds)) {\n\t\tint i, n;\n\t\tint fill = 0;\n\n\t\tn = uiDrawTextLayoutNumLines(layout);\n\t\tfor (i = 0; i < n; i++) {\n\t\t\tuiDrawTextLayoutLineGetMetrics(layout, i, &m);\n\t\t\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\t\t\tuiDrawPathAddRectangle(path, margins + m.X, margins + m.Y,\n\t\t\t\tm.Width, m.Height);\n\t\t\tuiDrawPathEnd(path);\n\t\t\tuiDrawFill(p->Context, path, fillBrushes + fill);\n\t\t\tuiDrawFreePath(path);\n\t\t\tfill = (fill + 1) % 4;\n\t\t}\n\t}\n\n\tuiDrawFreeTextLayout(layout);\n}\n\nstatic void mouse(uiAreaMouseEvent *e)\n{\n\tuiDrawTextLayout *layout;\n\tchar labelText[128];\n\n\tif (e->Down != 1)\n\t\treturn;\n\n\tparams.Width = e->AreaWidth - 2 * margins;\n\tlayout = uiDrawNewTextLayout(&params);\n\tuiDrawTextLayoutHitTest(layout,\n\t\te->X - margins, e->Y - margins,\n\t\t&caretPos, &caretLine);\n\tuiDrawFreeTextLayout(layout);\n\n\t// TODO move this into the draw handler so it is reflected by keyboard-based position changes\n\t// urgh %zd is not supported by MSVC with sprintf()\n\t// TODO get that part in test/ about having no other option\n\tsprintf(labelText, \"pos %d line %d\",\n\t\t(int) caretPos, caretLine);\n\tuiLabelSetText(caretLabel, labelText);\n\n\tredraw();\n}\n\nstatic int key(uiAreaKeyEvent *e)\n{\n\tsize_t grapheme;\n\n\tif (e->Up)\n\t\treturn 0;\n\tif (e->Key != 0)\n\t\treturn 0;\n\tswitch (e->ExtKey) {\n\tcase uiExtKeyUp:\n\t\t// TODO\n\t\treturn 1;\n\tcase uiExtKeyDown:\n\t\t// TODO\n\t\treturn 1;\n\tcase uiExtKeyLeft:\n\t\tgrapheme = uiAttributedStringByteIndexToGrapheme(attrstr, caretPos);\n\t\tif (grapheme == 0)\n\t\t\treturn 0;\n\t\tgrapheme--;\n\t\tcaretPos = uiAttributedStringGraphemeToByteIndex(attrstr, grapheme);\n\t\tredraw();\n\t\treturn 1;\n\tcase uiExtKeyRight:\n\t\tgrapheme = uiAttributedStringByteIndexToGrapheme(attrstr, caretPos);\n\t\tif (grapheme == uiAttributedStringNumGraphemes(attrstr))\n\t\t\treturn 0;\n\t\tgrapheme++;\n\t\tcaretPos = uiAttributedStringGraphemeToByteIndex(attrstr, grapheme);\n\t\tredraw();\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nstatic struct example hitTestExample;\n\n// TODO share?\nstatic void checkboxChecked(uiCheckbox *c, void *data)\n{\n\tredraw();\n}\n\nstatic void changeFont(uiFontButton *b, void *data)\n{\n\tif (defaultFont.Family != fontFamily)\n\t\tuiFreeText(defaultFont.Family);\n\t// TODO rename defaultFont\n\tuiFontButtonFont(fontButton, &defaultFont);\n\tprintf(\"{\\n\\tfamily: %s\\n\\tsize: %g\\n\\tweight: %d\\n\\titalic: %d\\n\\tstretch: %d\\n}\\n\",\n\t\tdefaultFont.Family,\n\t\tdefaultFont.Size,\n\t\t(int) (defaultFont.Weight),\n\t\t(int) (defaultFont.Italic),\n\t\t(int) (defaultFont.Stretch));\n\tredraw();\n}\n\nstatic void changeTextAlign(uiCombobox *c, void *data)\n{\n\t// note the order of the items added below\n\tparams.Align = (uiDrawTextAlign) uiComboboxSelected(textAlign);\n\tredraw();\n}\n\n// TODO share?\nstatic uiCheckbox *newCheckbox(uiBox *box, const char *text)\n{\n\tuiCheckbox *c;\n\n\tc = uiNewCheckbox(text);\n\tuiCheckboxOnToggled(c, checkboxChecked, NULL);\n\tuiBoxAppend(box, uiControl(c), 0);\n\treturn c;\n}\n\nstruct example *mkHitTestExample(void)\n{\n\tpanel = uiNewHorizontalBox();\n\tvbox = uiNewVerticalBox();\n\t// TODO the second vbox causes this not to stretch at least on OS X\n\tuiBoxAppend(panel, uiControl(vbox), 1);\n\tcaretLabel = uiNewLabel(\"Caret information is shown here\");\n\tuiBoxAppend(vbox, uiControl(caretLabel), 0);\n\tshowLineBounds = newCheckbox(vbox, \"Show Line Bounds (for debugging metrics)\");\n\tvbox = uiNewVerticalBox();\n\tuiBoxAppend(panel, uiControl(vbox), 0);\n\tfontButton = uiNewFontButton();\n\tuiFontButtonOnChanged(fontButton, changeFont, NULL);\n\t// TODO set the font button to the current defaultFont\n\tuiBoxAppend(vbox, uiControl(fontButton), 0);\n\ttextAlign = uiNewCombobox();\n\t// note that these are in the order in the enum\n\tuiComboboxAppend(textAlign, \"Left\");\n\tuiComboboxAppend(textAlign, \"Center\");\n\tuiComboboxAppend(textAlign, \"Right\");\n\tuiComboboxOnSelected(textAlign, changeTextAlign, NULL);\n\tuiBoxAppend(vbox, uiControl(textAlign), 0);\n\n\thitTestExample.name = \"Hit-Testing and Grapheme Boundaries\";\n\thitTestExample.panel = uiControl(panel);\n\thitTestExample.draw = draw;\n\thitTestExample.mouse = mouse;\n\thitTestExample.key = key;\n\n\tattrstr = uiNewAttributedString(text);\n\tparams.String = attrstr;\n\tparams.DefaultFont = &defaultFont;\n\tparams.Align = uiDrawTextAlignLeft;\n\n\treturn &hitTestExample;\n}\n"
  },
  {
    "path": "_wip/examples_drawtext/main.c",
    "content": "// 17 january 2017\n#include \"drawtext.h\"\n\nstatic uiWindow *mainwin;\nstatic uiBox *box;\nstatic uiCombobox *exampleList;\nstatic uiArea *area;\nstatic uiAreaHandler handler;\n\n#define nExamples 20\nstatic struct example *examples[nExamples];\nstatic int curExample = 0;\n\nstatic void onExampleChanged(uiCombobox *c, void *data)\n{\n\tuiControlHide(examples[curExample]->panel);\n\tcurExample = uiComboboxSelected(exampleList);\n\tuiControlShow(examples[curExample]->panel);\n\tredraw();\n}\n\nvoid redraw(void)\n{\n\tuiAreaQueueRedrawAll(area);\n}\n\nstatic void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *p)\n{\n\texamples[curExample]->draw(p);\n}\n\nstatic void handlerMouseEvent(uiAreaHandler *a, uiArea *area, uiAreaMouseEvent *e)\n{\n\tif (examples[curExample]->mouse != NULL)\n\t\texamples[curExample]->mouse(e);\n}\n\nstatic void handlerMouseCrossed(uiAreaHandler *ah, uiArea *a, int left)\n{\n\t// do nothing\n}\n\nstatic void handlerDragBroken(uiAreaHandler *ah, uiArea *a)\n{\n\t// do nothing\n}\n\nstatic int handlerKeyEvent(uiAreaHandler *ah, uiArea *a, uiAreaKeyEvent *e)\n{\n\tif (examples[curExample]->key != NULL)\n\t\treturn examples[curExample]->key(e);\n\treturn 0;\n}\n\nstatic int onClosing(uiWindow *w, void *data)\n{\n\tuiControlDestroy(uiControl(mainwin));\n\tuiQuit();\n\treturn 0;\n}\n\nstatic int shouldQuit(void *data)\n{\n\tuiControlDestroy(uiControl(mainwin));\n\treturn 1;\n}\n\nint main(void)\n{\n\tuiInitOptions o;\n\tconst char *err;\n\tint n;\n\n\thandler.Draw = handlerDraw;\n\thandler.MouseEvent = handlerMouseEvent;\n\thandler.MouseCrossed = handlerMouseCrossed;\n\thandler.DragBroken = handlerDragBroken;\n\thandler.KeyEvent = handlerKeyEvent;\n\n\tmemset(&o, 0, sizeof (uiInitOptions));\n\terr = uiInit(&o);\n\tif (err != NULL) {\n\t\tfprintf(stderr, \"error initializing ui: %s\\n\", err);\n\t\tuiFreeInitError(err);\n\t\treturn 1;\n\t}\n\n\tuiOnShouldQuit(shouldQuit, NULL);\n\n\tmainwin = uiNewWindow(\"libui Text-Drawing Example\", 640, 480, 1);\n\tuiWindowOnClosing(mainwin, onClosing, NULL);\n\n\tbox = uiNewVerticalBox();\n\tuiWindowSetChild(mainwin, uiControl(box));\n\n\texampleList = uiNewCombobox();\n\tuiBoxAppend(box, uiControl(exampleList), 0);\n\n\tarea = uiNewArea(&handler);\n\tuiBoxAppend(box, uiControl(area), 1);\n\n\tn = 0;\n\texamples[n] = mkBasicExample();\n\tuiComboboxAppend(exampleList, examples[n]->name);\n\tuiControlHide(examples[n]->panel);\n\tuiBoxAppend(box, examples[n]->panel, 0);\n\tn++;\n\texamples[n] = mkHitTestExample();\n\tuiComboboxAppend(exampleList, examples[n]->name);\n\tuiControlHide(examples[n]->panel);\n\tuiBoxAppend(box, examples[n]->panel, 0);\n\tn++;\n\texamples[n] = mkAttributesExample();\n\tuiComboboxAppend(exampleList, examples[n]->name);\n\tuiControlHide(examples[n]->panel);\n\tuiBoxAppend(box, examples[n]->panel, 0);\n\tn++;\n\texamples[n] = mkEmptyStringExample();\n\tuiComboboxAppend(exampleList, examples[n]->name);\n\tuiControlHide(examples[n]->panel);\n\tuiBoxAppend(box, examples[n]->panel, 0);\n\tn++;\n\t// and set things up for the initial state\n\tuiComboboxSetSelected(exampleList, 0);\n\tuiComboboxOnSelected(exampleList, onExampleChanged, NULL);\n\t// and set up the first one\n\tonExampleChanged(NULL, NULL);\n\n\tuiControlShow(uiControl(mainwin));\n\tuiMain();\n\n\t// TODO free examples\n\n\tuiUninit();\n\treturn 0;\n}\n"
  },
  {
    "path": "_wip/examples_drawtext_CMakeLists.txt",
    "content": "# 3 june 2016\n\n_add_example(drawtext\n\tdrawtext/attributes.c\n\tdrawtext/basic.c\n\tdrawtext/emptystr_hittest.c\n\tdrawtext/hittest.c\n\tdrawtext/main.c\n\t${_EXAMPLE_RESOURCES_RC}\n)\ntarget_include_directories(drawtext\n\tPRIVATE drawtext)\n\n_add_example(opentype\n\topentype/main.c\n\t${_EXAMPLE_RESOURCES_RC}\n)\ntarget_include_directories(opentype\n\tPRIVATE opentype)\n"
  },
  {
    "path": "_wip/examples_opentype/main.c",
    "content": "// 10 june 2017\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <time.h>\n#include \"../../ui.h\"\n\n// TODO the grid simply flat out does not work on OS X\n\nuiWindow *mainwin;\nuiFontButton *fontButton;\nuiEntry *textEntry;\nuiCheckbox *nullFeatures;\nuiArea *area;\n\nuiAttributedString *attrstr = NULL;\n\nstatic void remakeAttrStr(void)\n{\n\tchar *text;\n\tuiOpenTypeFeatures *otf;\n\tuiAttributeSpec spec;\n\n\tif (attrstr != NULL)\n\t\tuiFreeAttributedString(attrstr);\n\n\ttext = uiEntryText(textEntry);\n\tattrstr = uiNewAttributedString(text);\n\tuiFreeText(text);\n\n\tif (!uiCheckboxChecked(nullFeatures)) {\n\t\totf = uiNewOpenTypeFeatures();\n\t\t// TODO\n\t\tspec.Type = uiAttributeFeatures;\n\t\tspec.Features = otf;\n\t\tuiAttributedStringSetAttribute(attrstr, &spec,\n\t\t\t0, uiAttributedStringLen(attrstr));\n\t\t// and uiAttributedString copied otf\n\t\tuiFreeOpenTypeFeatures(otf);\n\t}\n\n\tuiAreaQueueRedrawAll(area);\n}\n\n// TODO make a variable of main()? in all programs?\nstatic uiAreaHandler handler;\n\nstatic void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *p)\n{\n\tuiDrawTextLayout *layout;\n\tuiDrawTextLayoutParams lp;\n\tuiDrawFontDescriptor desc;\n\n\tmemset(&lp, 0, sizeof (uiDrawTextLayoutParams));\n\tlp.String = attrstr;\n\tuiFontButtonFont(fontButton, &desc);\n\tlp.DefaultFont = &desc;\n\tlp.Width = p->AreaWidth;\n\tlp.Align = uiDrawTextAlignLeft;\n\tlayout = uiDrawNewTextLayout(&lp);\n\tuiDrawText(p->Context, layout, 0, 0);\n\tuiDrawFreeTextLayout(layout);\n}\n\nstatic void handlerMouseEvent(uiAreaHandler *a, uiArea *area, uiAreaMouseEvent *e)\n{\n\t// do nothing\n}\n\nstatic void handlerMouseCrossed(uiAreaHandler *ah, uiArea *a, int left)\n{\n\t// do nothing\n}\n\nstatic void handlerDragBroken(uiAreaHandler *ah, uiArea *a)\n{\n\t// do nothing\n}\n\nstatic int handlerKeyEvent(uiAreaHandler *ah, uiArea *a, uiAreaKeyEvent *e)\n{\n\t// reject all keys\n\treturn 0;\n}\n\nstatic void onFontChanged(uiFontButton *b, void *data)\n{\n\tremakeAttrStr();\n}\n\nstatic void onTextChanged(uiEntry *e, void *data)\n{\n\tremakeAttrStr();\n}\n\nstatic void onNULLToggled(uiCheckbox *c, void *data)\n{\n\tremakeAttrStr();\n}\n\nstatic int onClosing(uiWindow *w, void *data)\n{\n\t// TODO change the others to be like this? (the others destroy here rather than later)\n\t// TODO move this below uiQuit()?\n\tuiControlHide(uiControl(w));\n\tuiQuit();\n\treturn 0;\n}\n\nstatic int shouldQuit(void *data)\n{\n\tuiControlDestroy(uiControl(mainwin));\n\treturn 1;\n}\n\nint main(void)\n{\n\tuiInitOptions o;\n\tconst char *err;\n\tuiGrid *grid;\n\tuiBox *vbox;\n\n\thandler.Draw = handlerDraw;\n\thandler.MouseEvent = handlerMouseEvent;\n\thandler.MouseCrossed = handlerMouseCrossed;\n\thandler.DragBroken = handlerDragBroken;\n\thandler.KeyEvent = handlerKeyEvent;\n\n\tmemset(&o, 0, sizeof (uiInitOptions));\n\terr = uiInit(&o);\n\tif (err != NULL) {\n\t\tfprintf(stderr, \"error initializing ui: %s\\n\", err);\n\t\tuiFreeInitError(err);\n\t\treturn 1;\n\t}\n\n\tuiOnShouldQuit(shouldQuit, NULL);\n\n\t// TODO 800x600? the size of the GTK+ example?\n\tmainwin = uiNewWindow(\"libui OpenType Features Example\", 640, 480, 1);\n\tuiWindowSetMargined(mainwin, 1);\n\tuiWindowOnClosing(mainwin, onClosing, NULL);\n\n\tgrid = uiNewGrid();\n\tuiGridSetPadded(grid, 1);\n\tuiWindowSetChild(mainwin, uiControl(grid));\n\n\tfontButton = uiNewFontButton();\n\tuiFontButtonOnChanged(fontButton, onFontChanged, NULL);\n\tuiGridAppend(grid, uiControl(fontButton),\n\t\t0, 0, 1, 1,\n\t\t// TODO are these Y values correct?\n\t\t0, uiAlignFill, 0, uiAlignCenter);\n\n\ttextEntry = uiNewEntry();\n\tuiEntrySetText(textEntry, \"afford afire aflight\");\n\tuiEntryOnChanged(textEntry, onTextChanged, NULL);\n\tuiGridAppend(grid, uiControl(textEntry),\n\t\t1, 0, 1, 1,\n\t\t// TODO are these Y values correct too?\n\t\t// TODO add a baseline align? or a form align?\n\t\t1, uiAlignFill, 0, uiAlignCenter);\n\n\tvbox = uiNewVerticalBox();\n\tuiBoxSetPadded(vbox, 1);\n\tuiGridAppend(grid, uiControl(vbox),\n\t\t0, 1, 1, 1,\n\t\t0, uiAlignFill, 1, uiAlignFill);\n\n\tnullFeatures = uiNewCheckbox(\"NULL uiOpenTypeFeatures\");\n\tuiCheckboxOnToggled(nullFeatures, onNULLToggled, NULL);\n\tuiBoxAppend(vbox, uiControl(nullFeatures), 0);\n\n\t// TODO separator (if other stuff isn't a tab)\n\n\t// TODO needed for this to be testable on os x without rewriting everything again\n\t{\n\t\tint x;\n\n\t\tfor (x = 0; x < 10; x++)\n\t\t\tuiBoxAppend(vbox, uiControl(uiNewEntry()), 0);\n\t}\n\n\t// TODO other stuff\n\n\tarea = uiNewArea(&handler);\n\tuiGridAppend(grid, uiControl(area),\n\t\t1, 1, 1, 1,\n\t\t1, uiAlignFill, 1, uiAlignFill);\n\n\t// and set up the initial draw\n\tremakeAttrStr();\n\n\tuiControlShow(uiControl(mainwin));\n\tuiMain();\n\n\tuiControlDestroy(uiControl(mainwin));\n\tuiFreeAttributedString(attrstr);\n\tuiUninit();\n\treturn 0;\n}\n"
  },
  {
    "path": "_wip/rules.darwin",
    "content": "every rule in ui_darwin.h\nSetParent must be followed by SetSuperview and SyncEnableState\n\tTODO can child cache it?\nadding a child must be followed by a call to SyncEnableState\nSyncEnableState() must call ShouldStopSyncEnableState() first thing\nEnable() and Disable() must call SyncEnableState() AFTER CHANGING WHAT Enabled() WILL RETURN\n"
  },
  {
    "path": "_wip/rules.unix",
    "content": "every rule in ui_unix.h\nSetParent must be followed by SetContainer\n\tTODO can child cache it?\n"
  },
  {
    "path": "_wip/sv/normal",
    "content": "2016-05-26 22:38:12.877 svtest[81790:544681]       backgroundColor       NSNamedColorSpace System controlColor\n2016-05-26 22:38:12.877 svtest[81790:544681]       drawsBackground       1\n2016-05-26 22:38:12.877 svtest[81790:544681]       borderType            2\n2016-05-26 22:38:12.877 svtest[81790:544681]       documentCursor        (null)\n2016-05-26 22:38:12.877 svtest[81790:544681]       hasHorizontalScroller 1\n2016-05-26 22:38:12.877 svtest[81790:544681]       hasVerticalScroller   1\n2016-05-26 22:38:12.877 svtest[81790:544681]       autohidesScrollers    0\n2016-05-26 22:38:12.877 svtest[81790:544681]       hasHorizontalRuler    0\n2016-05-26 22:38:12.878 svtest[81790:544681]       hasVerticalRuler      0\n2016-05-26 22:38:12.878 svtest[81790:544681]       rulersVisible         0\n2016-05-26 22:38:12.878 svtest[81790:544681] 10.10 autoAdjContentInsets  1\n2016-05-26 22:38:12.878 svtest[81790:544681]       scrollerKnobStyle     0\n2016-05-26 22:38:12.878 svtest[81790:544681]       scrollerStyle         1\n2016-05-26 22:38:12.878 svtest[81790:544681]       horizontalLineScroll  10\n2016-05-26 22:38:12.878 svtest[81790:544681]       verticalLineScroll    10\n2016-05-26 22:38:12.886 svtest[81790:544681]       horizontalPageScroll  10\n2016-05-26 22:38:12.886 svtest[81790:544681]       verticalPageScroll    10\n2016-05-26 22:38:12.886 svtest[81790:544681]       scrollsDynamically    1\n2016-05-26 22:38:12.886 svtest[81790:544681]       findBarPosition       1\n2016-05-26 22:38:12.886 svtest[81790:544681]       usesPredomAxisScroll  0\n2016-05-26 22:38:12.886 svtest[81790:544681]       horizontalElasticity  0\n2016-05-26 22:38:12.886 svtest[81790:544681]       verticalElasticity    0\n2016-05-26 22:38:12.887 svtest[81790:544681] 10.8  allowsMagnification   0\n2016-05-26 22:38:12.887 svtest[81790:544681] 10.8  maxMagnification      4\n2016-05-26 22:38:12.887 svtest[81790:544681] 10.8  minMagnification      0.25\n"
  },
  {
    "path": "_wip/sv/normal.nots",
    "content": "         backgroundColor       NSNamedColorSpace System controlColor\n         drawsBackground       1\n         borderType            2\n         documentCursor        (null)\n         hasHorizontalScroller 1\n         hasVerticalScroller   1\n         autohidesScrollers    0\n         hasHorizontalRuler    0\n         hasVerticalRuler      0\n         rulersVisible         0\n   10.10 autoAdjContentInsets  1\n         scrollerKnobStyle     0\n         scrollerStyle         1\n         horizontalLineScroll  10\n         verticalLineScroll    10\n         horizontalPageScroll  10\n         verticalPageScroll    10\n         scrollsDynamically    1\n         findBarPosition       1\n         usesPredomAxisScroll  0\n         horizontalElasticity  0\n         verticalElasticity    0\n   10.8  allowsMagnification   0\n   10.8  maxMagnification      4\n   10.8  minMagnification      0.25\n"
  },
  {
    "path": "_wip/sv/outlineview",
    "content": "2016-05-26 22:42:16.208 svtest[82103:547159]       backgroundColor       NSNamedColorSpace System controlBackgroundColor\n2016-05-26 22:42:16.208 svtest[82103:547159]       drawsBackground       1\n2016-05-26 22:42:16.208 svtest[82103:547159]       borderType            2\n2016-05-26 22:42:16.208 svtest[82103:547159]       documentCursor        (null)\n2016-05-26 22:42:16.209 svtest[82103:547159]       hasHorizontalScroller 1\n2016-05-26 22:42:16.209 svtest[82103:547159]       hasVerticalScroller   1\n2016-05-26 22:42:16.209 svtest[82103:547159]       autohidesScrollers    1\n2016-05-26 22:42:16.209 svtest[82103:547159]       hasHorizontalRuler    0\n2016-05-26 22:42:16.209 svtest[82103:547159]       hasVerticalRuler      0\n2016-05-26 22:42:16.209 svtest[82103:547159]       rulersVisible         0\n2016-05-26 22:42:16.209 svtest[82103:547159] 10.10 autoAdjContentInsets  1\n2016-05-26 22:42:16.209 svtest[82103:547159]       scrollerKnobStyle     0\n2016-05-26 22:42:16.209 svtest[82103:547159]       scrollerStyle         1\n2016-05-26 22:42:16.209 svtest[82103:547159]       horizontalLineScroll  19\n2016-05-26 22:42:16.209 svtest[82103:547159]       verticalLineScroll    19\n2016-05-26 22:42:16.217 svtest[82103:547159]       horizontalPageScroll  10\n2016-05-26 22:42:16.218 svtest[82103:547159]       verticalPageScroll    10\n2016-05-26 22:42:16.218 svtest[82103:547159]       scrollsDynamically    1\n2016-05-26 22:42:16.218 svtest[82103:547159]       findBarPosition       1\n2016-05-26 22:42:16.218 svtest[82103:547159]       usesPredomAxisScroll  0\n2016-05-26 22:42:16.218 svtest[82103:547159]       horizontalElasticity  0\n2016-05-26 22:42:16.218 svtest[82103:547159]       verticalElasticity    0\n2016-05-26 22:42:16.218 svtest[82103:547159] 10.8  allowsMagnification   0\n2016-05-26 22:42:16.218 svtest[82103:547159] 10.8  maxMagnification      4\n2016-05-26 22:42:16.218 svtest[82103:547159] 10.8  minMagnification      0.25\n"
  },
  {
    "path": "_wip/sv/outlineview.nots",
    "content": "         backgroundColor       NSNamedColorSpace System controlBackgroundColor\n         drawsBackground       1\n         borderType            2\n         documentCursor        (null)\n         hasHorizontalScroller 1\n         hasVerticalScroller   1\n         autohidesScrollers    1\n         hasHorizontalRuler    0\n         hasVerticalRuler      0\n         rulersVisible         0\n   10.10 autoAdjContentInsets  1\n         scrollerKnobStyle     0\n         scrollerStyle         1\n         horizontalLineScroll  19\n         verticalLineScroll    19\n         horizontalPageScroll  10\n         verticalPageScroll    10\n         scrollsDynamically    1\n         findBarPosition       1\n         usesPredomAxisScroll  0\n         horizontalElasticity  0\n         verticalElasticity    0\n   10.8  allowsMagnification   0\n   10.8  maxMagnification      4\n   10.8  minMagnification      0.25\n"
  },
  {
    "path": "_wip/sv/sourcelist",
    "content": "2016-05-26 22:43:58.600 svtest[82237:548359]       backgroundColor       (null)\n2016-05-26 22:43:58.600 svtest[82237:548359]       drawsBackground       0\n2016-05-26 22:43:58.600 svtest[82237:548359]       borderType            2\n2016-05-26 22:43:58.600 svtest[82237:548359]       documentCursor        (null)\n2016-05-26 22:43:58.600 svtest[82237:548359]       hasHorizontalScroller 1\n2016-05-26 22:43:58.600 svtest[82237:548359]       hasVerticalScroller   1\n2016-05-26 22:43:58.600 svtest[82237:548359]       autohidesScrollers    1\n2016-05-26 22:43:58.600 svtest[82237:548359]       hasHorizontalRuler    0\n2016-05-26 22:43:58.600 svtest[82237:548359]       hasVerticalRuler      0\n2016-05-26 22:43:58.600 svtest[82237:548359]       rulersVisible         0\n2016-05-26 22:43:58.601 svtest[82237:548359] 10.10 autoAdjContentInsets  1\n2016-05-26 22:43:58.601 svtest[82237:548359]       scrollerKnobStyle     0\n2016-05-26 22:43:58.601 svtest[82237:548359]       scrollerStyle         1\n2016-05-26 22:43:58.601 svtest[82237:548359]       horizontalLineScroll  19\n2016-05-26 22:43:58.601 svtest[82237:548359]       verticalLineScroll    19\n2016-05-26 22:43:58.645 svtest[82237:548359]       horizontalPageScroll  10\n2016-05-26 22:43:58.645 svtest[82237:548359]       verticalPageScroll    10\n2016-05-26 22:43:58.645 svtest[82237:548359]       scrollsDynamically    1\n2016-05-26 22:43:58.645 svtest[82237:548359]       findBarPosition       1\n2016-05-26 22:43:58.645 svtest[82237:548359]       usesPredomAxisScroll  0\n2016-05-26 22:43:58.645 svtest[82237:548359]       horizontalElasticity  0\n2016-05-26 22:43:58.646 svtest[82237:548359]       verticalElasticity    0\n2016-05-26 22:43:58.646 svtest[82237:548359] 10.8  allowsMagnification   0\n2016-05-26 22:43:58.646 svtest[82237:548359] 10.8  maxMagnification      4\n2016-05-26 22:43:58.646 svtest[82237:548359] 10.8  minMagnification      0.25\n"
  },
  {
    "path": "_wip/sv/sourcelist.nots",
    "content": "         backgroundColor       (null)\n         drawsBackground       0\n         borderType            2\n         documentCursor        (null)\n         hasHorizontalScroller 1\n         hasVerticalScroller   1\n         autohidesScrollers    1\n         hasHorizontalRuler    0\n         hasVerticalRuler      0\n         rulersVisible         0\n   10.10 autoAdjContentInsets  1\n         scrollerKnobStyle     0\n         scrollerStyle         1\n         horizontalLineScroll  19\n         verticalLineScroll    19\n         horizontalPageScroll  10\n         verticalPageScroll    10\n         scrollsDynamically    1\n         findBarPosition       1\n         usesPredomAxisScroll  0\n         horizontalElasticity  0\n         verticalElasticity    0\n   10.8  allowsMagnification   0\n   10.8  maxMagnification      4\n   10.8  minMagnification      0.25\n"
  },
  {
    "path": "_wip/sv/tableview",
    "content": "2016-05-26 22:41:26.514 svtest[82032:546554]       backgroundColor       NSNamedColorSpace System controlBackgroundColor\n2016-05-26 22:41:26.514 svtest[82032:546554]       drawsBackground       1\n2016-05-26 22:41:26.514 svtest[82032:546554]       borderType            2\n2016-05-26 22:41:26.514 svtest[82032:546554]       documentCursor        (null)\n2016-05-26 22:41:26.515 svtest[82032:546554]       hasHorizontalScroller 1\n2016-05-26 22:41:26.515 svtest[82032:546554]       hasVerticalScroller   1\n2016-05-26 22:41:26.515 svtest[82032:546554]       autohidesScrollers    1\n2016-05-26 22:41:26.515 svtest[82032:546554]       hasHorizontalRuler    0\n2016-05-26 22:41:26.516 svtest[82032:546554]       hasVerticalRuler      0\n2016-05-26 22:41:26.516 svtest[82032:546554]       rulersVisible         0\n2016-05-26 22:41:26.516 svtest[82032:546554] 10.10 autoAdjContentInsets  1\n2016-05-26 22:41:26.516 svtest[82032:546554]       scrollerKnobStyle     0\n2016-05-26 22:41:26.516 svtest[82032:546554]       scrollerStyle         1\n2016-05-26 22:41:26.516 svtest[82032:546554]       horizontalLineScroll  19\n2016-05-26 22:41:26.516 svtest[82032:546554]       verticalLineScroll    19\n2016-05-26 22:41:26.528 svtest[82032:546554]       horizontalPageScroll  10\n2016-05-26 22:41:26.528 svtest[82032:546554]       verticalPageScroll    10\n2016-05-26 22:41:26.528 svtest[82032:546554]       scrollsDynamically    1\n2016-05-26 22:41:26.528 svtest[82032:546554]       findBarPosition       1\n2016-05-26 22:41:26.528 svtest[82032:546554]       usesPredomAxisScroll  0\n2016-05-26 22:41:26.528 svtest[82032:546554]       horizontalElasticity  0\n2016-05-26 22:41:26.528 svtest[82032:546554]       verticalElasticity    0\n2016-05-26 22:41:26.528 svtest[82032:546554] 10.8  allowsMagnification   0\n2016-05-26 22:41:26.528 svtest[82032:546554] 10.8  maxMagnification      4\n2016-05-26 22:41:26.528 svtest[82032:546554] 10.8  minMagnification      0.25\n"
  },
  {
    "path": "_wip/sv/tableview.nots",
    "content": "         backgroundColor       NSNamedColorSpace System controlBackgroundColor\n         drawsBackground       1\n         borderType            2\n         documentCursor        (null)\n         hasHorizontalScroller 1\n         hasVerticalScroller   1\n         autohidesScrollers    1\n         hasHorizontalRuler    0\n         hasVerticalRuler      0\n         rulersVisible         0\n   10.10 autoAdjContentInsets  1\n         scrollerKnobStyle     0\n         scrollerStyle         1\n         horizontalLineScroll  19\n         verticalLineScroll    19\n         horizontalPageScroll  10\n         verticalPageScroll    10\n         scrollsDynamically    1\n         findBarPosition       1\n         usesPredomAxisScroll  0\n         horizontalElasticity  0\n         verticalElasticity    0\n   10.8  allowsMagnification   0\n   10.8  maxMagnification      4\n   10.8  minMagnification      0.25\n"
  },
  {
    "path": "_wip/sv/textview",
    "content": "2016-05-26 22:40:02.050 svtest[81927:545793]       backgroundColor       NSCalibratedWhiteColorSpace 1 1\n2016-05-26 22:40:02.050 svtest[81927:545793]       drawsBackground       1\n2016-05-26 22:40:02.051 svtest[81927:545793]       borderType            2\n2016-05-26 22:40:02.052 svtest[81927:545793]       documentCursor        <NSCursor: 0x608000041f20>\n2016-05-26 22:40:02.052 svtest[81927:545793]       hasHorizontalScroller 0\n2016-05-26 22:40:02.052 svtest[81927:545793]       hasVerticalScroller   1\n2016-05-26 22:40:02.052 svtest[81927:545793]       autohidesScrollers    0\n2016-05-26 22:40:02.052 svtest[81927:545793]       hasHorizontalRuler    0\n2016-05-26 22:40:02.052 svtest[81927:545793]       hasVerticalRuler      0\n2016-05-26 22:40:02.052 svtest[81927:545793]       rulersVisible         0\n2016-05-26 22:40:02.052 svtest[81927:545793] 10.10 autoAdjContentInsets  1\n2016-05-26 22:40:02.052 svtest[81927:545793]       scrollerKnobStyle     0\n2016-05-26 22:40:02.052 svtest[81927:545793]       scrollerStyle         1\n2016-05-26 22:40:02.054 svtest[81927:545793]       horizontalLineScroll  10\n2016-05-26 22:40:02.055 svtest[81927:545793]       verticalLineScroll    10\n2016-05-26 22:40:02.062 svtest[81927:545793]       horizontalPageScroll  10\n2016-05-26 22:40:02.062 svtest[81927:545793]       verticalPageScroll    10\n2016-05-26 22:40:02.062 svtest[81927:545793]       scrollsDynamically    1\n2016-05-26 22:40:02.062 svtest[81927:545793]       findBarPosition       1\n2016-05-26 22:40:02.062 svtest[81927:545793]       usesPredomAxisScroll  0\n2016-05-26 22:40:02.062 svtest[81927:545793]       horizontalElasticity  0\n2016-05-26 22:40:02.062 svtest[81927:545793]       verticalElasticity    0\n2016-05-26 22:40:02.062 svtest[81927:545793] 10.8  allowsMagnification   0\n2016-05-26 22:40:02.062 svtest[81927:545793] 10.8  maxMagnification      4\n2016-05-26 22:40:02.063 svtest[81927:545793] 10.8  minMagnification      0.25\n"
  },
  {
    "path": "_wip/sv/textview.nots",
    "content": "         backgroundColor       NSCalibratedWhiteColorSpace 1 1\n         drawsBackground       1\n         borderType            2\n         documentCursor        <NSCursor: 0x608000041f20>\n         hasHorizontalScroller 0\n         hasVerticalScroller   1\n         autohidesScrollers    0\n         hasHorizontalRuler    0\n         hasVerticalRuler      0\n         rulersVisible         0\n   10.10 autoAdjContentInsets  1\n         scrollerKnobStyle     0\n         scrollerStyle         1\n         horizontalLineScroll  10\n         verticalLineScroll    10\n         horizontalPageScroll  10\n         verticalPageScroll    10\n         scrollsDynamically    1\n         findBarPosition       1\n         usesPredomAxisScroll  0\n         horizontalElasticity  0\n         verticalElasticity    0\n   10.8  allowsMagnification   0\n   10.8  maxMagnification      4\n   10.8  minMagnification      0.25\n"
  },
  {
    "path": "_wip/table/test_page9.c",
    "content": "// 18 october 2015\n#include \"test.h\"\n\n// TODO manage the memory of the uiTableModel\n\nstatic intmax_t nColumns = 4;\nstatic uiTableColumnType coltypes[] = {\n\tuiTableColumnText,\n\tuiTableColumnText,\n\tuiTableColumnCheckbox,\n\tuiTableColumnCheckbox,\n};\n\nstatic intmax_t nRows = 6;\n\nstatic intmax_t modelNumRows(uiTableModel *m, void *mData)\n{\n\treturn nRows;\n}\n\nvoid *modelCellValue(uiTableModel *m, void *mData, intmax_t row, intmax_t column)\n{\n\tchar line[20];\n\n\tline[0] = 'R';\n\tline[1] = 'o';\n\tline[2] = 'w';\n\tline[3] = ' ';\n\tline[4] = row + '0';\n\tline[5] = '\\0';\n\tswitch (column) {\n\tcase 0:\n\tcase 1:\n\t\treturn uiTableModelFromString(line);\n\tcase 2:\n\t\treturn uiTableModelFromBool(row % 2 == 0);\n\tcase 3:\n\t\treturn uiTableModelFromBool(row % 3 == 0);\n\t}\n\t// TODO\n\treturn NULL;\n}\n\n// TODO make this not need to be static\nuiTableModelSpec spec;\n\nvoid modelSetCellValue(uiTableModel *m, void *mData, intmax_t row, intmax_t column, void *value)\n{\n\t// TODO\n}\n\nuiBox *makePage9(void)\n{\n\tuiBox *page9;\n\tuiTable *table;\n\tuiTableModel *model;\n\tuiTableColumnParams p;\n\tintmax_t i;\n\n\tpage9 = newVerticalBox();\n\n\ttable = uiNewTable();\n\tuiBoxAppend(page9, uiControl(table), 1);\n\n\tspec.NumRows = modelNumRows;\n\tspec.CellValue = modelCellValue;\n\tspec.SetCellValue = modelSetCellValue;\n\tmodel = uiNewTableModel(nColumns, coltypes, &spec, NULL);\n\tuiTableSetModel(table, model);\n\n\tfor (i = 0; i < nColumns; i++) {\n\t\tp.Name = \"Column\";\n\t\tp.Type = coltypes[i];\n\t\tp.Mutable = i % 2 == 1;\n\t\tp.ValueColumn = i;\n\t\tuiTableAppendColumn(table, &p);\n\t}\n\n\treturn page9;\n}\n"
  },
  {
    "path": "_wip/table/ui.h",
    "content": "typedef struct uiTable uiTable;\ntypedef struct uiTableModel uiTableModel;\ntypedef struct uiTableModelSpec uiTableModelSpec;\ntypedef struct uiTableColumnParams uiTableColumnParams;\ntypedef enum uiTableColumnType uiTableColumnType;\ntypedef enum uiTableNotification uiTableNotification;\n\n_UI_EXTERN uintmax_t uiTableType(void);\n#define uiTable(this) ((uiTable *) uiIsA((this), uiTableType(), 1))\n_UI_EXTERN void uiTableSetModel(uiTable *t, uiTableModel *m);\n_UI_EXTERN void uiTableAppendColumn(uiTable *t, uiTableColumnParams *p);\n_UI_EXTERN uiTable *uiNewTable(void);\n\nenum uiTableColumnType {\n\tuiTableColumnText,\n//TODO\tuiTableColumnImage,\n\tuiTableColumnCheckbox,\n};\n\nstruct uiTableModelSpec {\n\tintmax_t (*NumRows)(uiTableModel *m, void *mData);\n\tvoid *(*CellValue)(uiTableModel *m, void *mData, intmax_t row, intmax_t column);\n\tvoid (*SetCellValue)(uiTableModel *m, void *mData, intmax_t row, intmax_t column, void *value);\n};\n\nenum uiTableNotification {\n\tuiTableRowInserted,\n\tuiTableRowDeleted,\n\tuiTableCellChanged,\n};\n\n_UI_EXTERN uiTableModel *uiNewTableModel(uintmax_t nCols, uiTableColumnType *types, uiTableModelSpec *spec, void *mData);\n_UI_EXTERN void uiFreeTableModel(uiTableModel *m);\n_UI_EXTERN void uiTableModelNotify(uiTableModel *m, uiTableNotification notification, intmax_t row, intmax_t column);\n\n#define uiTableModelFromBool(b) ((void *) ((intptr_t) (b)))\n_UI_EXTERN void *uiTableModelFromString(const char *str);\n\nstruct uiTableColumnParams {\n\tconst char *Name;\n\t// TODO make this unnecessary\n\tuiTableColumnType Type;\n\tint Mutable;\t\t\t// TODO move to the model?\n\tintmax_t ValueColumn;\n\t// TODO background color\n};\n"
  },
  {
    "path": "_wip/table/unix_table.c",
    "content": "// 18 october 2015\n#include \"uipriv_unix.h\"\n\nstruct uiTable {\n\tuiUnixControl c;\n\tGtkWidget *widget;\n\tGtkContainer *scontainer;\n\tGtkScrolledWindow *sw;\n\tGtkWidget *treeWidget;\n\tGtkTreeView *treeview;\n\tGtkTreeSelection *selection;\n\tuiTableModel *model;\n};\n\nuiUnixDefineControl(\n\tuiTable\t\t\t\t\t\t\t\t// type name\n)\n\nvoid uiTableSetModel(uiTable *t, uiTableModel *m)\n{\n\tt->model = m;\n\tgtk_tree_view_set_model(t->treeview, GTK_TREE_MODEL(t->model));\n}\n\nvoid uiTableAppendColumn(uiTable *t, uiTableColumnParams *p)\n{\n\tGtkTreeViewColumn *col;\n\tGtkCellRenderer *r;\n\tconst char *attribute;\n\tconst char *mutableAttr;\n\tgboolean mutable;\n\n\tswitch (p->Type) {\n\tcase uiTableColumnText:\n\t\tr = gtk_cell_renderer_text_new();\n\t\tattribute = \"text\";\n\t\tmutableAttr = \"editable\";\n\t\tbreak;\n//TODO\tcase uiTableColumnImage:\n\t\t// TODO\n\tcase uiTableColumnCheckbox:\n\t\tr = gtk_cell_renderer_toggle_new();\n\t\tattribute = \"active\";\n\t\tmutableAttr = \"activatable\";\n\t\tbreak;\n\tdefault:\n\t\tcomplain(\"unknown table column type %d in uiTableAppendColumn()\", p->Type);\n\t}\n\tmutable = FALSE;\n\tif (p->Mutable)\n\t\tmutable = TRUE;\n\tg_object_set(r,\n\t\tmutableAttr, mutable,\n\t\tNULL);\n\tcol = gtk_tree_view_column_new_with_attributes(p->Name, r,\n\t\tattribute, p->ValueColumn,\n\t\tNULL);\n\t// allow columns to be resized\n\tgtk_tree_view_column_set_resizable(col, TRUE);\n\tgtk_tree_view_append_column(t->treeview, col);\n}\n\nuiTable *uiNewTable(void)\n{\n\tuiTable *t;\n\n\tt = (uiTable *) uiNewControl(uiTableType());\n\n\tt->widget = gtk_scrolled_window_new(NULL, NULL);\n\tt->scontainer = GTK_CONTAINER(t->widget);\n\tt->sw = GTK_SCROLLED_WINDOW(t->widget);\n\n\tt->treeWidget = gtk_tree_view_new();\n\tt->treeview = GTK_TREE_VIEW(t->treeWidget);\n\n\tt->selection = gtk_tree_view_get_selection(t->treeview);\n\n\t// give a border and add the table\n\tgtk_scrolled_window_set_shadow_type(t->sw, GTK_SHADOW_IN);\n\tgtk_container_add(t->scontainer, t->treeWidget);\n\t// and make the table visible; only the scrolled window's visibility is controlled by libui\n\tgtk_widget_show(t->treeWidget);\n\n\tuiUnixFinishNewControl(t, uiTable);\n\n\treturn t;\n}\n"
  },
  {
    "path": "_wip/table/unix_tablemodel.c",
    "content": "// 18 october 2015\n#include \"uipriv_unix.h\"\n\n// On GTK+, uiTableModel is a GtkTreeModel.\n\n#define uiTableModelType (uiTableModel_get_type())\n#define uiTableModel(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), uiTableModelType, uiTableModel))\n#define isAreaWidget(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), uiTableModelType))\n#define uiTableModelClass(class) (G_TYPE_CHECK_CLASS_CAST((class), uiTableModelType, uiTableModelClass))\n#define isAreaWidgetClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), uiTableModel))\n#define getAreaWidgetClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), uiTableModelType, uiTableModelClass))\n\ntypedef struct uiTableModel uiTableModel;\ntypedef struct uiTableModelClass uiTableModelClass;\n\nstruct uiTableModel {\n\tGObject parent_instance;\n\tuiTableModelSpec *spec;\n\tvoid *mData;\n\tintmax_t nColumns;\n\tGType *coltypes;\n};\n\nstruct uiTableModelClass {\n\tGObjectClass parent_class;\n};\n\nstatic void uiTableModel_treeModel_init(GtkTreeModelIface *);\n\nG_DEFINE_TYPE_WITH_CODE(uiTableModel, uiTableModel, G_TYPE_OBJECT,\n\tG_IMPLEMENT_INTERFACE(GTK_TYPE_TREE_MODEL, uiTableModel_treeModel_init))\n\nstatic void uiTableModel_init(uiTableModel *m)\n{\n\t// do nothing\n}\n\nstatic void uiTableModel_dispose(GObject *obj)\n{\n\tG_OBJECT_CLASS(uiTableModel_parent_class)->dispose(obj);\n}\n\nstatic void uiTableModel_finalize(GObject *obj)\n{\n\tuiTableModel *m = uiTableModel(obj);\n\n\tuiFree(m->coltypes);\n\tG_OBJECT_CLASS(uiTableModel_parent_class)->finalize(obj);\n}\n\nstatic GtkTreeModelFlags uiTableModel_get_flags(GtkTreeModel *mb)\n{\n\treturn GTK_TREE_MODEL_LIST_ONLY;\n}\n\nstatic gint uiTableModel_get_n_columns(GtkTreeModel *mb)\n{\n\tuiTableModel *m = uiTableModel(mb);\n\n\treturn m->nColumns;\n}\n\nstatic GType uiTableModel_get_column_type(GtkTreeModel *mb, gint index)\n{\n\tuiTableModel *m = uiTableModel(mb);\n\n\treturn m->coltypes[index];\n}\n\n/*\nhow our GtkTreeIters are stored:\n\tstamp: either GOOD_STAMP or BAD_STAMP\n\tuser_data: row index\nThanks to Company in irc.gimp.net/#gtk+ for suggesting the GSIZE_TO_POINTER() t\nrick.\n*/\n#define GOOD_STAMP 0x1234\n#define BAD_STAMP 0x5678\n#define FROM(x) ((gint) GPOINTER_TO_SIZE((x)))\n#define TO(x) GSIZE_TO_POINTER((gsize) (x))\n\n#define numRows(m) ((*((m)->spec->NumRows))((m), (m)->mData))\n#define cellValue(m, row, col) ((*((m)->spec->CellValue))((m), (m)->mData, row, column))\n\nstatic gboolean uiTableModel_get_iter(GtkTreeModel *mb, GtkTreeIter *iter, GtkTreePath *path)\n{\n\tuiTableModel *m = uiTableModel(mb);\n\tgint index;\n\n\tif (gtk_tree_path_get_depth(path) != 1)\n\t\tgoto bad;\n\tindex = gtk_tree_path_get_indices(path)[0];\n\tif (index < 0)\n\t\tgoto bad;\n\tif (index >= numRows(m))\n\t\tgoto bad;\n\titer->stamp = GOOD_STAMP;\n\titer->user_data = TO(index);\n\treturn TRUE;\nbad:\n\titer->stamp = BAD_STAMP;\n\treturn FALSE;\n}\n\nstatic GtkTreePath *uiTableModel_get_path(GtkTreeModel *mb, GtkTreeIter *iter)\n{\n\t// note: from this point forward, the GOOD_STAMP checks ensure that the index stored in iter is nonnegative\n\tif (iter->stamp != GOOD_STAMP)\n\t\treturn NULL;\t\t\t// this is what both GtkListStore and GtkTreeStore do\n\treturn gtk_tree_path_new_from_indices(FROM(iter->user_data), -1);\n}\n\nvoid *uiTableModelFromString(const char *str)\n{\n\treturn g_strdup(str);\n}\n\n#define toBool(v) ((int) ((intptr_t) (v)))\n#define toStr(v) ((char *) (v))\n\nstatic void uiTableModel_get_value(GtkTreeModel *mb, GtkTreeIter *iter, gint column, GValue *value)\n{\n\tuiTableModel *m = uiTableModel(mb);\n\tvoid *v;\n\tGType type;\n\n\tif (iter->stamp != GOOD_STAMP)\n\t\treturn;\t\t\t\t// this is what both GtkListStore and GtkTreeStore do\n\tv = cellValue(m, FROM(iter->user_data), column);\n\ttype = m->coltypes[column];\n\tg_value_init(value, type);\n\tif (type == G_TYPE_STRING)\n\t\tg_value_take_string(value, toStr(v));\n\t\t// the GValue now manages the memory of the string that was g_strdup()'d before\n\t// TODO image\n\telse if (type == G_TYPE_BOOLEAN)\n\t\tg_value_set_boolean(value, toBool(v));\n\telse\n\t\tcomplain(\"unknown GType in uiTableModel_get_value()\");\n}\n\nstatic gboolean uiTableModel_iter_next(GtkTreeModel *mb, GtkTreeIter *iter)\n{\n\tuiTableModel *m = uiTableModel(mb);\n\tgint index;\n\n\tif (iter->stamp != GOOD_STAMP)\n\t\treturn FALSE;\t\t\t\t// this is what both GtkListStore and GtkTreeStore do\n\tindex = FROM(iter->user_data);\n\tindex++;\n\tif (index >= numRows(m)) {\n\t\titer->stamp = BAD_STAMP;\n\t\treturn FALSE;\n\t}\n\titer->user_data = TO(index);\n\treturn TRUE;\n}\n\nstatic gboolean uiTableModel_iter_previous(GtkTreeModel *mb, GtkTreeIter *iter)\n{\n\tuiTableModel *m = uiTableModel(mb);\n\tgint index;\n\n\tif (iter->stamp != GOOD_STAMP)\n\t\treturn FALSE;\t\t\t// this is what both GtkListStore and GtkTreeStore do\n\tindex = FROM(iter->user_data);\n\tif (index <= 0) {\n\t\titer->stamp = BAD_STAMP;\n\t\treturn FALSE;\n\t}\n\tindex--;\n\titer->user_data = TO(index);\n\treturn TRUE;\n}\n\nstatic gboolean uiTableModel_iter_children(GtkTreeModel *mb, GtkTreeIter *iter, GtkTreeIter *parent)\n{\n\tuiTableModel *m = uiTableModel(mb);\n\n\tif (parent == NULL && numRows(m) > 0) {\n\t\titer->stamp = GOOD_STAMP;\n\t\titer->user_data = 0;\n\t\treturn TRUE;\n\t}\n\titer->stamp = BAD_STAMP;\n\treturn FALSE;\n}\n\nstatic gboolean uiTableModel_iter_has_child(GtkTreeModel *mb, GtkTreeIter *iter)\n{\n\treturn FALSE;\n}\n\nstatic gint uiTableModel_iter_n_children(GtkTreeModel *mb, GtkTreeIter *iter)\n{\n\tuiTableModel *m = uiTableModel(mb);\n\n\tif (iter == NULL)\n\t\treturn numRows(m);\n\treturn 0;\n}\n\nstatic gboolean uiTableModel_iter_nth_child(GtkTreeModel *mb, GtkTreeIter *iter, GtkTreeIter *parent, gint n)\n{\n\tuiTableModel *m = uiTableModel(mb);\n\n\tif (parent == NULL && n >= 0 && n < numRows(m)) {\n\t\titer->stamp = GOOD_STAMP;\n\t\titer->user_data = TO(n);\n\t\treturn TRUE;\n\t}\n\titer->stamp = BAD_STAMP;\n\treturn FALSE;\n}\n\nstatic gboolean uiTableModel_iter_parent(GtkTreeModel *mb, GtkTreeIter *iter, GtkTreeIter *child)\n{\n\titer->stamp = BAD_STAMP;\n\treturn FALSE;\n}\n\nstatic void uiTableModel_class_init(uiTableModelClass *class)\n{\n\tG_OBJECT_CLASS(class)->dispose = uiTableModel_dispose;\n\tG_OBJECT_CLASS(class)->finalize = uiTableModel_finalize;\n}\n\nstatic void uiTableModel_treeModel_init(GtkTreeModelIface *iface)\n{\n\tiface->get_flags = uiTableModel_get_flags;\n\tiface->get_n_columns = uiTableModel_get_n_columns;\n\tiface->get_column_type = uiTableModel_get_column_type;\n\tiface->get_iter = uiTableModel_get_iter;\n\tiface->get_path = uiTableModel_get_path;\n\tiface->get_value = uiTableModel_get_value;\n\tiface->iter_next = uiTableModel_iter_next;\n\tiface->iter_previous = uiTableModel_iter_previous;\n\tiface->iter_children = uiTableModel_iter_children;\n\tiface->iter_has_child = uiTableModel_iter_has_child;\n\tiface->iter_n_children = uiTableModel_iter_n_children;\n\tiface->iter_nth_child = uiTableModel_iter_nth_child;\n\tiface->iter_parent = uiTableModel_iter_parent;\n\t// no need for ref_node or unref_node\n}\n\nuiTableModel *uiNewTableModel(uintmax_t nCols, uiTableColumnType *types, uiTableModelSpec *spec, void *mData)\n{\n\tuiTableModel *m;\n\tintmax_t i;\n\n\tm = uiTableModel(g_object_new(uiTableModelType, NULL));\n\tm->spec = spec;\n\tm->mData = mData;\n\tm->nColumns = nCols;\n\tm->coltypes = (GType *) uiAlloc(m->nColumns * sizeof (GType), \"GType[]\");\n\tfor (i = 0; i < m->nColumns; i++)\n\t\tswitch (types[i]) {\n\t\tcase uiTableColumnText:\n\t\t\tm->coltypes[i] = G_TYPE_STRING;\n\t\t\tbreak;\n//TODO\t\tcase uiTableColumnImage:\n\t\t\t// TODO\n\t\tcase uiTableColumnCheckbox:\n\t\t\tm->coltypes[i] = G_TYPE_BOOLEAN;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tcomplain(\"unknown column type %d in uiNewTableModel()\", types[i]);\n\t\t}\n\treturn m;\n}\n\n// TODO ensure no tables are subscribed\nvoid uiFreeTableModel(uiTableModel *m)\n{\n\tg_object_unref(m);\n}\n\nvoid uiTableModelNotify(uiTableModel *m, uiTableNotification notification, intmax_t row, intmax_t column)\n{\n\tGtkTreeModel *model = GTK_TREE_MODEL(m);\n\tGtkTreePath *path;\n\tGtkTreeIter iter;\n\n\tpath = gtk_tree_path_new_from_indices(row, -1);\n\tswitch (notification) {\n\tcase uiTableRowInserted:\n\t\tif (gtk_tree_model_get_iter(model, &iter, path) == FALSE)\n\t\t\tcomplain(\"invalid row given to row inserted in uiTableModelNotify()\");\n\t\tgtk_tree_model_row_inserted(model, path, &iter);\n\t\tbreak;\n\tcase uiTableRowDeleted:\n\t\tgtk_tree_model_row_deleted(model, path);\n\t\tbreak;\n\tcase uiTableCellChanged:\n\t\tif (gtk_tree_model_get_iter(model, &iter, path) == FALSE)\n\t\t\tcomplain(\"invalid row given to row changed in uiTableModelNotify()\");\n\t\tgtk_tree_model_row_changed(model, path, &iter);\n\t\tbreak;\n\tdefault:\n\t\tcomplain(\"unknown uiTable notification %d in uiTableModelNotify()\", notification);\n\t}\n\tgtk_tree_path_free(path);\n}\n"
  },
  {
    "path": "azure-pipelines/TODOMatrix",
    "content": "# 31 march 2019\n\nvariables:\n  releaseExamples: 'controlgallery cpp-multithread datetime drawtext histogram tester timer'\n\nstrategy:\n  matrix:\n#    targetname:\n#      os: 'fill this in'\n#      arch: 'fill this in'\n#      toolchain: 'fill this in'\n#      libtype: 'fill this in'\n#      vmImage: 'fill this in'\n#      python3Template: 'fill filename'\n#      depsAndNinjaTemplate: 'fill filename'\n#      beforeConfigure: 'fill this in'\n#      beforeBuild: 'fill this in'\n#      afterBuild: 'fill this in'\n#      artifactTemplate: 'fill filename'\n#      libfiles: 'fill this in'\n#      osHeader: 'fill this in'\n    linux_386_shared:\n      os: 'linux'\n      arch: '386'\n      libtype: 'shared'\n      vmImage: 'ubuntu-16.04'\n      python3Template: 'azure-pipelines/setup-python3.yml'\n      depsAndNinjaTemplate: 'azure-pipelines/linux-386-install-gtk-dev.yml'\n      beforeConfigure: 'export CFLAGS=-m32 CXXFLAGS=-m32 PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig'\n      artifactTemplate: 'azure-pipelines/artifacts.yml'\n      libfiles: 'libui.so.0'\n      osHeader: 'ui_unix.h'\n\npool:\n  vmImage: $(vmImage)\n\nworkspace:\n  clean: all\n\nsteps:\n- template: $(variables.python3Template)\n- template: azure-pipelines/install-latest-meson-ninja.yml\n- template: $(variables.depsAndNinjaTemplate)\n- template: azure-pipelines/configure.yml\n  parameters:\n    beforeConfigure: $(beforeConfigure)\n    defaultLibrary: $(libtype)\n- template: azure-pipelines/build.yml\n  parameters:\n    beforeBuild: $(beforeBuild)\n    afterBuild: $(afterBuild)\n- template: $(variables.artifactTemplate)\n  parameters:\n    os: $(os)\n    arch: $(arch)\n    toolchain: $(toolchain)\n    libtype: $(libtype)\n    libfiles: $(libfiles)\n    osHeader: $(osHeader)\n\n## linux {\n#- job: linux_386_static\n#  displayName: 'Linux 386 Static'\n#  pool:\n#    vmImage: 'ubuntu-16.04'\n#  workspace:\n#    clean: all\n#  steps:\n#  - template: azure-pipelines/setup-python3.yml\n#  - template: azure-pipelines/install-latest-meson-ninja.yml\n#  - template: azure-pipelines/linux-386-install-gtk-dev.yml\n#  - template: azure-pipelines/configure.yml\n#    parameters:\n#      beforeConfigure: export CFLAGS=-m32 CXXFLAGS=-m32 PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig\n#      defaultLibrary: static\n#  - template: azure-pipelines/build.yml\n#  - template: azure-pipelines/artifacts.yml\n#    parameters:\n#      os: linux\n#      arch: 386\n#      libtype: static\n#      libfiles: libui.a\n#      osHeader: ui_unix.h\n#\n#- job: linux_amd64_shared\n#  displayName: 'Linux amd64 Shared'\n#  pool:\n#    vmImage: 'ubuntu-16.04'\n#  workspace:\n#    clean: all\n#  steps:\n#  - template: azure-pipelines/setup-python3.yml\n#  - template: azure-pipelines/install-latest-meson-ninja.yml\n#  - template: azure-pipelines/linux-install-gtk-dev.yml\n#  - template: azure-pipelines/configure.yml\n#    parameters:\n#      defaultLibrary: shared\n#  - template: azure-pipelines/build.yml\n#  - template: azure-pipelines/artifacts.yml\n#    parameters:\n#      os: linux\n#      arch: amd64\n#      libtype: shared\n#      libfiles: libui.so.0\n#      osHeader: ui_unix.h\n#\n#- job: linux_amd64_static\n#  displayName: 'Linux amd64 Static'\n#  pool:\n#    vmImage: 'ubuntu-16.04'\n#  workspace:\n#    clean: all\n#  steps:\n#  - template: azure-pipelines/setup-python3.yml\n#  - template: azure-pipelines/install-latest-meson-ninja.yml\n#  - template: azure-pipelines/linux-install-gtk-dev.yml\n#  - template: azure-pipelines/configure.yml\n#    parameters:\n#      defaultLibrary: static\n#  - template: azure-pipelines/build.yml\n#  - template: azure-pipelines/artifacts.yml\n#    parameters:\n#      os: linux\n#      arch: amd64\n#      libtype: static\n#      libfiles: libui.a\n#      osHeader: ui_unix.h\n#\n## }\n#\n## windows vs2015 {\n#\n#- job: windows_386_msvc2015_shared\n#  displayName: 'Windows 386 MSVC2015 Shared'\n#  pool:\n#    vmImage: 'vs2015-win2012r2'\n#  workspace:\n#    clean: all\n#  steps:\n#  - template: azure-pipelines/vs2015-install-python3.yml\n#  - template: azure-pipelines/install-latest-meson-ninja.yml\n#  - template: azure-pipelines/windows-install-ninja.yml\n#  - template: azure-pipelines/configure.yml\n#    parameters:\n#      beforeConfigure: call \"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" x86\n#      defaultLibrary: shared\n#  - template: azure-pipelines/build.yml\n#    parameters:\n#      beforeBuild: call \"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" x86\n#  - template: azure-pipelines/windows-artifacts.yml\n#    parameters:\n#      os: windows\n#      arch: 386\n#      toolchain: msvc2015\n#      libtype: shared\n#      libfiles: libui.dll libui.exp libui.lib\n#      osHeader: ui_windows.h\n#\n#- job: windows_386_msvc2015_static\n#  displayName: 'Windows 386 MSVC2015 Static'\n#  pool:\n#    vmImage: 'vs2015-win2012r2'\n#  workspace:\n#    clean: all\n#  steps:\n#  - template: azure-pipelines/vs2015-install-python3.yml\n#  - template: azure-pipelines/install-latest-meson-ninja.yml\n#  - template: azure-pipelines/windows-install-ninja.yml\n#  - template: azure-pipelines/configure.yml\n#    parameters:\n#      beforeConfigure: call \"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" x86\n#      defaultLibrary: static\n#  - template: azure-pipelines/build.yml\n#    parameters:\n#      beforeBuild: call \"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" x86\n#      afterBuild: ren build\\meson-out\\libui.a libui.lib\n#  - template: azure-pipelines/windows-artifacts.yml\n#    parameters:\n#      os: windows\n#      arch: 386\n#      toolchain: msvc2015\n#      libtype: static\n#      libfiles: libui.lib\n#      osHeader: ui_windows.h\n#\n#- job: windows_amd64_msvc2015_shared\n#  displayName: 'Windows amd64 MSVC2015 Shared'\n#  pool:\n#    vmImage: 'vs2015-win2012r2'\n#  workspace:\n#    clean: all\n#  steps:\n#  - template: azure-pipelines/vs2015-install-python3.yml\n#  - template: azure-pipelines/install-latest-meson-ninja.yml\n#  - template: azure-pipelines/windows-install-ninja.yml\n#  - template: azure-pipelines/configure.yml\n#    parameters:\n#      beforeConfigure: call \"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" amd64\n#      defaultLibrary: shared\n#  - template: azure-pipelines/build.yml\n#    parameters:\n#      beforeBuild: call \"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" amd64\n#  - template: azure-pipelines/windows-artifacts.yml\n#    parameters:\n#      os: windows\n#      arch: amd64\n#      toolchain: msvc2015\n#      libtype: shared\n#      libfiles: libui.dll libui.exp libui.lib\n#      osHeader: ui_windows.h\n#\n#- job: windows_amd64_msvc2015_static\n#  displayName: 'Windows amd64 MSVC2015 Static'\n#  pool:\n#    vmImage: 'vs2015-win2012r2'\n#  workspace:\n#    clean: all\n#  steps:\n#  - template: azure-pipelines/vs2015-install-python3.yml\n#  - template: azure-pipelines/install-latest-meson-ninja.yml\n#  - template: azure-pipelines/windows-install-ninja.yml\n#  - template: azure-pipelines/configure.yml\n#    parameters:\n#      beforeConfigure: call \"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" amd64\n#      defaultLibrary: static\n#  - template: azure-pipelines/build.yml\n#    parameters:\n#      beforeBuild: call \"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" amd64\n#      afterBuild: ren build\\meson-out\\libui.a libui.lib\n#  - template: azure-pipelines/windows-artifacts.yml\n#    parameters:\n#      os: windows\n#      arch: amd64\n#      toolchain: msvc2015\n#      libtype: static\n#      libfiles: libui.lib\n#      osHeader: ui_windows.h\n#\n## }\n#\n## windows vs2017 {\n#\n#- job: windows_386_msvc2017_shared\n#  displayName: 'Windows 386 MSVC2017 Shared'\n#  pool:\n#    vmImage: 'vs2017-win2016'\n#  workspace:\n#    clean: all\n#  steps:\n#  - template: azure-pipelines/setup-python3.yml\n#  - template: azure-pipelines/install-latest-meson-ninja.yml\n#  - template: azure-pipelines/windows-install-ninja.yml\n#  - template: azure-pipelines/configure.yml\n#    parameters:\n#      beforeConfigure: call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat\" x86\n#      defaultLibrary: shared\n#  - template: azure-pipelines/build.yml\n#    parameters:\n#      beforeBuild: call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat\" x86\n#  - template: azure-pipelines/windows-artifacts.yml\n#    parameters:\n#      os: windows\n#      arch: 386\n#      toolchain: msvc2017\n#      libtype: shared\n#      libfiles: libui.dll libui.exp libui.lib\n#      osHeader: ui_windows.h\n#\n#- job: windows_386_msvc2017_static\n#  displayName: 'Windows 386 MSVC2017 Static'\n#  pool:\n#    vmImage: 'vs2017-win2016'\n#  workspace:\n#    clean: all\n#  steps:\n#  - template: azure-pipelines/setup-python3.yml\n#  - template: azure-pipelines/install-latest-meson-ninja.yml\n#  - template: azure-pipelines/windows-install-ninja.yml\n#  - template: azure-pipelines/configure.yml\n#    parameters:\n#      beforeConfigure: call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat\" x86\n#      defaultLibrary: static\n#  - template: azure-pipelines/build.yml\n#    parameters:\n#      beforeBuild: call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat\" x86\n#      afterBuild: ren build\\meson-out\\libui.a libui.lib\n#  - template: azure-pipelines/windows-artifacts.yml\n#    parameters:\n#      os: windows\n#      arch: 386\n#      toolchain: msvc2017\n#      libtype: static\n#      libfiles: libui.lib\n#      osHeader: ui_windows.h\n#\n#- job: windows_amd64_msvc2017_shared\n#  displayName: 'Windows amd64 MSVC2017 Shared'\n#  pool:\n#    vmImage: 'vs2017-win2016'\n#  workspace:\n#    clean: all\n#  steps:\n#  - template: azure-pipelines/setup-python3.yml\n#  - template: azure-pipelines/install-latest-meson-ninja.yml\n#  - template: azure-pipelines/windows-install-ninja.yml\n#  - template: azure-pipelines/configure.yml\n#    parameters:\n#      beforeConfigure: call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat\" amd64\n#      defaultLibrary: shared\n#  - template: azure-pipelines/build.yml\n#    parameters:\n#      beforeBuild: call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat\" amd64\n#  - template: azure-pipelines/windows-artifacts.yml\n#    parameters:\n#      os: windows\n#      arch: amd64\n#      toolchain: msvc2017\n#      libtype: shared\n#      libfiles: libui.dll libui.exp libui.lib\n#      osHeader: ui_windows.h\n#\n#- job: windows_amd64_msvc2017_static\n#  displayName: 'Windows amd64 MSVC2017 Static'\n#  pool:\n#    vmImage: 'vs2017-win2016'\n#  workspace:\n#    clean: all\n#  steps:\n#  - template: azure-pipelines/setup-python3.yml\n#  - template: azure-pipelines/install-latest-meson-ninja.yml\n#  - template: azure-pipelines/windows-install-ninja.yml\n#  - template: azure-pipelines/configure.yml\n#    parameters:\n#      beforeConfigure: call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat\" amd64\n#      defaultLibrary: static\n#  - template: azure-pipelines/build.yml\n#    parameters:\n#      beforeBuild: call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat\" amd64\n#      afterBuild: ren build\\meson-out\\libui.a libui.lib\n#  - template: azure-pipelines/windows-artifacts.yml\n#    parameters:\n#      os: windows\n#      arch: amd64\n#      toolchain: msvc2017\n#      libtype: static\n#      libfiles: libui.lib\n#      osHeader: ui_windows.h\n#\n## }\n#\n## mac {\n#\n## TODO beforeConfigure/beforeBuild: export SDKROOT=$(xcodebuild -version -sdk macosx10.13 Path)?\n#\n#- job: darwin_amd64_shared\n#  displayName: 'Darwin amd64 Shared'\n#  pool:\n#    vmImage: 'macos-10.13'\n#  workspace:\n#    clean: all\n#  steps:\n#  - template: azure-pipelines/setup-python3.yml\n#  - template: azure-pipelines/install-latest-meson-ninja.yml\n#  - template: azure-pipelines/darwin-install-ninja.yml\n#  - template: azure-pipelines/configure.yml\n#    parameters:\n#      defaultLibrary: shared\n#  - template: azure-pipelines/build.yml\n#  - template: azure-pipelines/artifacts.yml\n#    parameters:\n#      os: darwin\n#      arch: amd64\n#      libtype: shared\n#      libfiles: libui.A.dylib\n#      osHeader: ui_darwin.h\n#\n#- job: darwin_amd64_static\n#  displayName: 'Darwin amd64 Static'\n#  pool:\n#    vmImage: 'macos-10.13'\n#  workspace:\n#    clean: all\n#  steps:\n#  - template: azure-pipelines/setup-python3.yml\n#  - template: azure-pipelines/install-latest-meson-ninja.yml\n#  - template: azure-pipelines/darwin-install-ninja.yml\n#  - template: azure-pipelines/configure.yml\n#    parameters:\n#      defaultLibrary: static\n#  - template: azure-pipelines/build.yml\n#  - template: azure-pipelines/artifacts.yml\n#    parameters:\n#      os: darwin\n#      arch: amd64\n#      libtype: static\n#      libfiles: libui.a\n#      osHeader: ui_darwin.h\n#\n## }\n"
  },
  {
    "path": "azure-pipelines/artifacts.yml",
    "content": "# 6 april 2019\n\nparameters:\n  os: ''\n  arch: ''\n  libtype: ''\n  libfiles: ''\n  osHeader: ''\n\nsteps:\n- script: |\n    set -x\n    pushd build/meson-out\n    cp ../../ui.h ../../${{ parameters.osHeader }} .\n    tar czf $(Build.ArtifactStagingDirectory)/libui-$(Build.SourceBranchName)-${{ parameters.os }}-${{ parameters.arch }}-${{ parameters.libtype }}.tgz ${{ parameters.libfiles }} ui.h ${{ parameters.osHeader}}\n    tar czf $(Build.ArtifactStagingDirectory)/examples-$(Build.SourceBranchName)-${{ parameters.os }}-${{ parameters.arch }}-${{ parameters.libtype }}.tgz $(releaseExamples)\n    rm ui.h ${{ parameters.osHeader }}\n    popd\n  displayName: 'Create Artifacts'\n- task: GitHubRelease@0\n  condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/')\n  inputs:\n    gitHubConnection: andlabs\n    repositoryName: andlabs/libui\n    action: 'edit'\n    addChangelog: false\n    assets: '$(Build.ArtifactStagingDirectory)/*'\n    assetUploadMode: 'replace'\n"
  },
  {
    "path": "azure-pipelines/build.yml",
    "content": "# 5 april 2019\n\nparameters:\n  beforeBuild: ''\n  afterBuild: ''\n\nsteps:\n- script: |\n    ${{ parameters.beforeBuild }}\n    ninja -C build -v\n    ${{ parameters.afterBuild }}\n  displayName: 'Build'\n"
  },
  {
    "path": "azure-pipelines/collapse.awk",
    "content": "# 7 april 2019\n\nBEGIN {\n\tRS = \"\"\n\tFS = \"\\n +- \"\n}\n\n/^- job:/ {\n\tfor (i = 1; i <= NF; i++) {\n\t\tif (!(i in nextindex)) {\n\t\t\t# fast path for first occurrence\n\t\t\tlines[i, 0] = $i\n\t\t\tnextindex[i] = 1\n\t\t\tif (maxIndex < i)\n\t\t\t\tmaxIndex = i\n\t\t\tcontinue\n\t\t}\n\t\tfound = 0\n\t\tfor (j = 0; j < nextindex[i]; j++)\n\t\t\tif (lines[i, j] == $i) {\n\t\t\t\tfound = 1\n\t\t\t\tbreak\n\t\t\t}\n\t\tif (!found) {\n\t\t\tlines[i, nextindex[i]] = $i\n\t\t\tnextindex[i]++\n\t\t}\n\t}\n}\n\nEND {\n\tfor (i = 1; i <= maxIndex; i++) {\n\t\tif (nextindex[i] == 1) {\n\t\t\t# only one entry here, just print it\n\t\t\tprint \"- \" lines[i, 0]\n\t\t\tcontinue\n\t\t}\n\t\tprint \"{\"\n\t\tfor (j = 0; j < nextindex[i]; j++)\n\t\t\tprint \"- \" lines[i, j]\n\t\tprint \"}\"\n\t}\n}\n"
  },
  {
    "path": "azure-pipelines/collapsed",
    "content": "{\n- - job: linux_386_shared\n  displayName: 'Linux 386 Shared'\n  pool:\n    vmImage: 'ubuntu-16.04'\n  workspace:\n    clean: all\n  steps:\n- - job: linux_386_static\n  displayName: 'Linux 386 Static'\n  pool:\n    vmImage: 'ubuntu-16.04'\n  workspace:\n    clean: all\n  steps:\n- - job: linux_amd64_shared\n  displayName: 'Linux amd64 Shared'\n  pool:\n    vmImage: 'ubuntu-16.04'\n  workspace:\n    clean: all\n  steps:\n- - job: linux_amd64_static\n  displayName: 'Linux amd64 Static'\n  pool:\n    vmImage: 'ubuntu-16.04'\n  workspace:\n    clean: all\n  steps:\n- - job: windows_386_msvc2015_shared\n  displayName: 'Windows 386 MSVC2015 Shared'\n  pool:\n    vmImage: 'vs2015-win2012r2'\n  workspace:\n    clean: all\n  steps:\n- - job: windows_386_msvc2015_static\n  displayName: 'Windows 386 MSVC2015 Static'\n  pool:\n    vmImage: 'vs2015-win2012r2'\n  workspace:\n    clean: all\n  steps:\n- - job: windows_amd64_msvc2015_shared\n  displayName: 'Windows amd64 MSVC2015 Shared'\n  pool:\n    vmImage: 'vs2015-win2012r2'\n  workspace:\n    clean: all\n  steps:\n- - job: windows_amd64_msvc2015_static\n  displayName: 'Windows amd64 MSVC2015 Static'\n  pool:\n    vmImage: 'vs2015-win2012r2'\n  workspace:\n    clean: all\n  steps:\n- - job: windows_386_msvc2017_shared\n  displayName: 'Windows 386 MSVC2017 Shared'\n  pool:\n    vmImage: 'vs2017-win2016'\n  workspace:\n    clean: all\n  steps:\n- - job: windows_386_msvc2017_static\n  displayName: 'Windows 386 MSVC2017 Static'\n  pool:\n    vmImage: 'vs2017-win2016'\n  workspace:\n    clean: all\n  steps:\n- - job: windows_amd64_msvc2017_shared\n  displayName: 'Windows amd64 MSVC2017 Shared'\n  pool:\n    vmImage: 'vs2017-win2016'\n  workspace:\n    clean: all\n  steps:\n- - job: windows_amd64_msvc2017_static\n  displayName: 'Windows amd64 MSVC2017 Static'\n  pool:\n    vmImage: 'vs2017-win2016'\n  workspace:\n    clean: all\n  steps:\n- - job: darwin_amd64_shared\n  displayName: 'Darwin amd64 Shared'\n  pool:\n    vmImage: 'macos-10.13'\n  workspace:\n    clean: all\n  steps:\n- - job: darwin_amd64_static\n  displayName: 'Darwin amd64 Static'\n  pool:\n    vmImage: 'macos-10.13'\n  workspace:\n    clean: all\n  steps:\n}\n{\n- template: azure-pipelines/setup-python3.yml\n- template: azure-pipelines/vs2015-install-python3.yml\n}\n- template: azure-pipelines/install-latest-meson-ninja.yml\n{\n- template: azure-pipelines/linux-386-install-gtk-dev.yml\n- template: azure-pipelines/linux-install-gtk-dev.yml\n- template: azure-pipelines/windows-install-ninja.yml\n- template: azure-pipelines/darwin-install-ninja.yml\n}\n{\n- template: azure-pipelines/configure.yml\n    parameters:\n      beforeConfigure: export CFLAGS=-m32 CXXFLAGS=-m32 PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig\n      defaultLibrary: shared\n- template: azure-pipelines/configure.yml\n    parameters:\n      beforeConfigure: export CFLAGS=-m32 CXXFLAGS=-m32 PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig\n      defaultLibrary: static\n- template: azure-pipelines/configure.yml\n    parameters:\n      defaultLibrary: shared\n- template: azure-pipelines/configure.yml\n    parameters:\n      defaultLibrary: static\n- template: azure-pipelines/configure.yml\n    parameters:\n      beforeConfigure: call \"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" x86\n      defaultLibrary: shared\n- template: azure-pipelines/configure.yml\n    parameters:\n      beforeConfigure: call \"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" x86\n      defaultLibrary: static\n- template: azure-pipelines/configure.yml\n    parameters:\n      beforeConfigure: call \"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" amd64\n      defaultLibrary: shared\n- template: azure-pipelines/configure.yml\n    parameters:\n      beforeConfigure: call \"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" amd64\n      defaultLibrary: static\n- template: azure-pipelines/configure.yml\n    parameters:\n      beforeConfigure: call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat\" x86\n      defaultLibrary: shared\n- template: azure-pipelines/configure.yml\n    parameters:\n      beforeConfigure: call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat\" x86\n      defaultLibrary: static\n- template: azure-pipelines/configure.yml\n    parameters:\n      beforeConfigure: call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat\" amd64\n      defaultLibrary: shared\n- template: azure-pipelines/configure.yml\n    parameters:\n      beforeConfigure: call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat\" amd64\n      defaultLibrary: static\n}\n{\n- template: azure-pipelines/build.yml\n- template: azure-pipelines/build.yml\n    parameters:\n      beforeBuild: call \"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" x86\n- template: azure-pipelines/build.yml\n    parameters:\n      beforeBuild: call \"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" x86\n      afterBuild: ren build\\meson-out\\libui.a libui.lib\n- template: azure-pipelines/build.yml\n    parameters:\n      beforeBuild: call \"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" amd64\n- template: azure-pipelines/build.yml\n    parameters:\n      beforeBuild: call \"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" amd64\n      afterBuild: ren build\\meson-out\\libui.a libui.lib\n- template: azure-pipelines/build.yml\n    parameters:\n      beforeBuild: call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat\" x86\n- template: azure-pipelines/build.yml\n    parameters:\n      beforeBuild: call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat\" x86\n      afterBuild: ren build\\meson-out\\libui.a libui.lib\n- template: azure-pipelines/build.yml\n    parameters:\n      beforeBuild: call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat\" amd64\n- template: azure-pipelines/build.yml\n    parameters:\n      beforeBuild: call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat\" amd64\n      afterBuild: ren build\\meson-out\\libui.a libui.lib\n}\n{\n- template: azure-pipelines/artifacts.yml\n    parameters:\n      os: linux\n      arch: 386\n      libtype: shared\n      libfiles: libui.so.0\n      osHeader: ui_unix.h\n- template: azure-pipelines/artifacts.yml\n    parameters:\n      os: linux\n      arch: 386\n      libtype: static\n      libfiles: libui.a\n      osHeader: ui_unix.h\n- template: azure-pipelines/artifacts.yml\n    parameters:\n      os: linux\n      arch: amd64\n      libtype: shared\n      libfiles: libui.so.0\n      osHeader: ui_unix.h\n- template: azure-pipelines/artifacts.yml\n    parameters:\n      os: linux\n      arch: amd64\n      libtype: static\n      libfiles: libui.a\n      osHeader: ui_unix.h\n- template: azure-pipelines/windows-artifacts.yml\n    parameters:\n      os: windows\n      arch: 386\n      toolchain: msvc2015\n      libtype: shared\n      libfiles: libui.dll libui.exp libui.lib\n      osHeader: ui_windows.h\n- template: azure-pipelines/windows-artifacts.yml\n    parameters:\n      os: windows\n      arch: 386\n      toolchain: msvc2015\n      libtype: static\n      libfiles: libui.lib\n      osHeader: ui_windows.h\n- template: azure-pipelines/windows-artifacts.yml\n    parameters:\n      os: windows\n      arch: amd64\n      toolchain: msvc2015\n      libtype: shared\n      libfiles: libui.dll libui.exp libui.lib\n      osHeader: ui_windows.h\n- template: azure-pipelines/windows-artifacts.yml\n    parameters:\n      os: windows\n      arch: amd64\n      toolchain: msvc2015\n      libtype: static\n      libfiles: libui.lib\n      osHeader: ui_windows.h\n- template: azure-pipelines/windows-artifacts.yml\n    parameters:\n      os: windows\n      arch: 386\n      toolchain: msvc2017\n      libtype: shared\n      libfiles: libui.dll libui.exp libui.lib\n      osHeader: ui_windows.h\n- template: azure-pipelines/windows-artifacts.yml\n    parameters:\n      os: windows\n      arch: 386\n      toolchain: msvc2017\n      libtype: static\n      libfiles: libui.lib\n      osHeader: ui_windows.h\n- template: azure-pipelines/windows-artifacts.yml\n    parameters:\n      os: windows\n      arch: amd64\n      toolchain: msvc2017\n      libtype: shared\n      libfiles: libui.dll libui.exp libui.lib\n      osHeader: ui_windows.h\n- template: azure-pipelines/windows-artifacts.yml\n    parameters:\n      os: windows\n      arch: amd64\n      toolchain: msvc2017\n      libtype: static\n      libfiles: libui.lib\n      osHeader: ui_windows.h\n- template: azure-pipelines/artifacts.yml\n    parameters:\n      os: darwin\n      arch: amd64\n      libtype: shared\n      libfiles: libui.A.dylib\n      osHeader: ui_darwin.h\n- template: azure-pipelines/artifacts.yml\n    parameters:\n      os: darwin\n      arch: amd64\n      libtype: static\n      libfiles: libui.a\n      osHeader: ui_darwin.h\n}\n"
  },
  {
    "path": "azure-pipelines/configure.yml",
    "content": "# 5 april 2019\n\nparameters:\n  beforeConfigure: ''\n  defaultLibrary: 'must-be-set'\n\nsteps:\n- script: |\n    ${{ parameters.beforeConfigure }}\n    meson setup build --buildtype=release --default-library=${{ parameters.defaultLibrary }}\n  displayName: 'Configure'\n"
  },
  {
    "path": "azure-pipelines/darwin-install-ninja.yml",
    "content": "# 5 april 2019\n# because brew install is also slow (it runs an update task first)\n\nsteps:\n- script: |\n    sudo mkdir -p /opt/ninja\n    pushd /opt/ninja\n    sudo wget https://github.com/ninja-build/ninja/releases/download/v1.9.0/ninja-mac.zip\n    sudo unzip ninja-mac.zip\n    sudo chmod a+rx ninja\n    popd\n    echo '##vso[task.prependpath]/opt/ninja'\n  displayName: 'Install Ninja'\n"
  },
  {
    "path": "azure-pipelines/install-latest-meson-ninja.yml",
    "content": "# 4 april 2019\n\n# TODO remove ninja installation from non-Linux OSs and make the same ninja via pip change in the AppVeyor script\n\nsteps:\n- script: |\n    python -m pip install --upgrade pip setuptools wheel\n    pip install meson ninja\n  displayName: 'Install Latest Meson and Ninja'\n"
  },
  {
    "path": "azure-pipelines/linux-386-install-gtk-dev.yml",
    "content": "# 7 april 2019\n\n# TODO figure out how to get meson to recognize the compiler is producing 32-bit output\n\nsteps:\n- script: |\n    # Azure Pipelines ships with a patched version of this and that patch is only available on 64-bit systems, so trying to install the 32-bit versions will remove the 64-bit versions outright\n    # This is a dependency of Mesa, so we'll have to downgrade to the stock distro ones :/\n    llvmPackages=\n    for i in libllvm6.0 clang-6.0 libclang-common-6.0-dev liblldb-6.0 liblldb-6.0-dev lld-6.0 lldb-6.0 llvm-6.0-dev python-lldb-6.0 libclang1-6.0 llvm-6.0 llvm-6.0-runtime; do llvmPackages=\"$llvmPackages $i=1:6.0-1ubuntu2~16.04.1\"; done\n    sudo dpkg --add-architecture i386\n    sudo apt-get update\n    sudo apt-get install --allow-downgrades \\\n      gcc-multilib g++-multilib \\\n      $llvmPackages \\\n      libgtk-3-dev:i386\n  displayName: 'Install GTK+ Dev Files'\n"
  },
  {
    "path": "azure-pipelines/linux-install-gtk-dev.yml",
    "content": "# 7 april 2019\n\nsteps:\n- script: |\n    sudo apt-get install libgtk-3-dev\n  displayName: 'Install GTK+ Dev Files'\n"
  },
  {
    "path": "azure-pipelines/setup-python3.yml",
    "content": "# 4 april 2019\n\nsteps:\n- task: UsePythonVersion@0\n  inputs:\n    versionSpec: '3.6'\n    architecture: 'x64'\n"
  },
  {
    "path": "azure-pipelines/vs2015-install-python3.yml",
    "content": "# 4 april 2019\n# see https://github.com/Microsoft/azure-pipelines-image-generation/issues/374 for context and source\n\nsteps:\n- script: |\n    powershell -Command \"Invoke-WebRequest https://www.python.org/ftp/python/3.7.1/python-3.7.1-amd64-webinstall.exe -OutFile C:\\py3-setup.exe\"\n    C:\\py3-setup.exe /quiet PrependPath=0 InstallAllUsers=0 Include_launcher=0 InstallLauncherAllUsers=0 Include_test=0 Include_doc=0 Include_dev=0 Include_debug=0 Include_tcltk=0 TargetDir=C:\\Python37\n    @echo ##vso[task.prependpath]C:\\Python37\n    @echo ##vso[task.prependpath]C:\\Python37\\Scripts\n  displayName: 'Install Python 3'\n"
  },
  {
    "path": "azure-pipelines/windows-artifacts.yml",
    "content": "# 6 april 2019\n\nparameters:\n  os: ''\n  arch: ''\n  toolchain: ''\n  libtype: ''\n  libfiles: ''\n  osHeader: ''\n\nsteps:\n- powershell: |\n    pushd build\\meson-out\n    Copy-Item @(\"..\\..\\ui.h\",\"..\\..\\${{ parameters.osHeader }}\") -Destination .\n    Compress-Archive -Destination $(Build.ArtifactStagingDirectory)\\libui-$(Build.SourceBranchName)-${{ parameters.os }}-${{ parameters.arch }}-${{ parameters.toolchain }}-${{ parameters.libtype }}.zip -Path @(\"${{ parameters.libfiles }}\".Split(\" \") + @(\"ui.h\",\"${{ parameters.osHeader}}\"))\n    Compress-Archive -Destination $(Build.ArtifactStagingDirectory)\\examples-$(Build.SourceBranchName)-${{ parameters.os }}-${{ parameters.arch }}-${{ parameters.libtype }}.zip -Path @(\"$(releaseExamples)\".Split(\" \") | % {$_ + \".exe\"})\n    Remove-Item @(\"ui.h\",\"${{ parameters.osHeader }}\")\n    popd\n  displayName: 'Create Artifacts'\n- task: GitHubRelease@0\n  condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/')\n  inputs:\n    gitHubConnection: andlabs\n    repositoryName: andlabs/libui\n    action: 'edit'\n    addChangelog: false\n    assets: '$(Build.ArtifactStagingDirectory)/*'\n    assetUploadMode: 'replace'\n"
  },
  {
    "path": "azure-pipelines/windows-install-ninja.yml",
    "content": "# 4 april 2019\n# why this? because choco isn't available on the VS2015 image and is extremely slow on the VS2017 one (it should not take 2.5 minutes to install just ninja!)\n\nsteps:\n- script: |\n    powershell -Command \"Invoke-WebRequest https://github.com/ninja-build/ninja/releases/download/v1.9.0/ninja-win.zip -OutFile C:\\ninja-win.zip\"\n    mkdir C:\\ninja\n    powershell -Command \"Expand-Archive -LiteralPath C:\\ninja-win.zip -DestinationPath C:\\ninja\"\n    @echo ##vso[task.prependpath]C:\\ninja\n  displayName: 'Install Ninja'\n"
  },
  {
    "path": "azure-pipelines.yml",
    "content": "# 31 march 2019\n\ntrigger:\n  branches:\n    include:\n    - '*'\n  tags:\n    include:\n    - '*'\n\nvariables:\n  releaseExamples: 'controlgallery cpp-multithread datetime drawtext histogram tester timer'\n\njobs:\n\n# linux {\n\n- job: linux_386_shared\n  displayName: 'Linux 386 Shared'\n  pool:\n    vmImage: 'ubuntu-16.04'\n  workspace:\n    clean: all\n  steps:\n  - template: azure-pipelines/setup-python3.yml\n  - template: azure-pipelines/install-latest-meson-ninja.yml\n  - template: azure-pipelines/linux-386-install-gtk-dev.yml\n  - template: azure-pipelines/configure.yml\n    parameters:\n      beforeConfigure: export CFLAGS=-m32 CXXFLAGS=-m32 PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig\n      defaultLibrary: shared\n  - template: azure-pipelines/build.yml\n  - template: azure-pipelines/artifacts.yml\n    parameters:\n      os: linux\n      arch: 386\n      libtype: shared\n      libfiles: libui.so.0\n      osHeader: ui_unix.h\n\n- job: linux_386_static\n  displayName: 'Linux 386 Static'\n  pool:\n    vmImage: 'ubuntu-16.04'\n  workspace:\n    clean: all\n  steps:\n  - template: azure-pipelines/setup-python3.yml\n  - template: azure-pipelines/install-latest-meson-ninja.yml\n  - template: azure-pipelines/linux-386-install-gtk-dev.yml\n  - template: azure-pipelines/configure.yml\n    parameters:\n      beforeConfigure: export CFLAGS=-m32 CXXFLAGS=-m32 PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig\n      defaultLibrary: static\n  - template: azure-pipelines/build.yml\n  - template: azure-pipelines/artifacts.yml\n    parameters:\n      os: linux\n      arch: 386\n      libtype: static\n      libfiles: libui.a\n      osHeader: ui_unix.h\n\n- job: linux_amd64_shared\n  displayName: 'Linux amd64 Shared'\n  pool:\n    vmImage: 'ubuntu-16.04'\n  workspace:\n    clean: all\n  steps:\n  - template: azure-pipelines/setup-python3.yml\n  - template: azure-pipelines/install-latest-meson-ninja.yml\n  - template: azure-pipelines/linux-install-gtk-dev.yml\n  - template: azure-pipelines/configure.yml\n    parameters:\n      defaultLibrary: shared\n  - template: azure-pipelines/build.yml\n  - template: azure-pipelines/artifacts.yml\n    parameters:\n      os: linux\n      arch: amd64\n      libtype: shared\n      libfiles: libui.so.0\n      osHeader: ui_unix.h\n\n- job: linux_amd64_static\n  displayName: 'Linux amd64 Static'\n  pool:\n    vmImage: 'ubuntu-16.04'\n  workspace:\n    clean: all\n  steps:\n  - template: azure-pipelines/setup-python3.yml\n  - template: azure-pipelines/install-latest-meson-ninja.yml\n  - template: azure-pipelines/linux-install-gtk-dev.yml\n  - template: azure-pipelines/configure.yml\n    parameters:\n      defaultLibrary: static\n  - template: azure-pipelines/build.yml\n  - template: azure-pipelines/artifacts.yml\n    parameters:\n      os: linux\n      arch: amd64\n      libtype: static\n      libfiles: libui.a\n      osHeader: ui_unix.h\n\n# }\n\n# windows vs2015 {\n\n- job: windows_386_msvc2015_shared\n  displayName: 'Windows 386 MSVC2015 Shared'\n  pool:\n    vmImage: 'vs2015-win2012r2'\n  workspace:\n    clean: all\n  steps:\n  - template: azure-pipelines/vs2015-install-python3.yml\n  - template: azure-pipelines/install-latest-meson-ninja.yml\n  - template: azure-pipelines/windows-install-ninja.yml\n  - template: azure-pipelines/configure.yml\n    parameters:\n      beforeConfigure: call \"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" x86\n      defaultLibrary: shared\n  - template: azure-pipelines/build.yml\n    parameters:\n      beforeBuild: call \"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" x86\n  - template: azure-pipelines/windows-artifacts.yml\n    parameters:\n      os: windows\n      arch: 386\n      toolchain: msvc2015\n      libtype: shared\n      libfiles: libui.dll libui.exp libui.lib\n      osHeader: ui_windows.h\n\n- job: windows_386_msvc2015_static\n  displayName: 'Windows 386 MSVC2015 Static'\n  pool:\n    vmImage: 'vs2015-win2012r2'\n  workspace:\n    clean: all\n  steps:\n  - template: azure-pipelines/vs2015-install-python3.yml\n  - template: azure-pipelines/install-latest-meson-ninja.yml\n  - template: azure-pipelines/windows-install-ninja.yml\n  - template: azure-pipelines/configure.yml\n    parameters:\n      beforeConfigure: call \"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" x86\n      defaultLibrary: static\n  - template: azure-pipelines/build.yml\n    parameters:\n      beforeBuild: call \"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" x86\n      afterBuild: ren build\\meson-out\\libui.a libui.lib\n  - template: azure-pipelines/windows-artifacts.yml\n    parameters:\n      os: windows\n      arch: 386\n      toolchain: msvc2015\n      libtype: static\n      libfiles: libui.lib\n      osHeader: ui_windows.h\n\n- job: windows_amd64_msvc2015_shared\n  displayName: 'Windows amd64 MSVC2015 Shared'\n  pool:\n    vmImage: 'vs2015-win2012r2'\n  workspace:\n    clean: all\n  steps:\n  - template: azure-pipelines/vs2015-install-python3.yml\n  - template: azure-pipelines/install-latest-meson-ninja.yml\n  - template: azure-pipelines/windows-install-ninja.yml\n  - template: azure-pipelines/configure.yml\n    parameters:\n      beforeConfigure: call \"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" amd64\n      defaultLibrary: shared\n  - template: azure-pipelines/build.yml\n    parameters:\n      beforeBuild: call \"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" amd64\n  - template: azure-pipelines/windows-artifacts.yml\n    parameters:\n      os: windows\n      arch: amd64\n      toolchain: msvc2015\n      libtype: shared\n      libfiles: libui.dll libui.exp libui.lib\n      osHeader: ui_windows.h\n\n- job: windows_amd64_msvc2015_static\n  displayName: 'Windows amd64 MSVC2015 Static'\n  pool:\n    vmImage: 'vs2015-win2012r2'\n  workspace:\n    clean: all\n  steps:\n  - template: azure-pipelines/vs2015-install-python3.yml\n  - template: azure-pipelines/install-latest-meson-ninja.yml\n  - template: azure-pipelines/windows-install-ninja.yml\n  - template: azure-pipelines/configure.yml\n    parameters:\n      beforeConfigure: call \"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" amd64\n      defaultLibrary: static\n  - template: azure-pipelines/build.yml\n    parameters:\n      beforeBuild: call \"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" amd64\n      afterBuild: ren build\\meson-out\\libui.a libui.lib\n  - template: azure-pipelines/windows-artifacts.yml\n    parameters:\n      os: windows\n      arch: amd64\n      toolchain: msvc2015\n      libtype: static\n      libfiles: libui.lib\n      osHeader: ui_windows.h\n\n# }\n\n# windows vs2017 {\n\n- job: windows_386_msvc2017_shared\n  displayName: 'Windows 386 MSVC2017 Shared'\n  pool:\n    vmImage: 'vs2017-win2016'\n  workspace:\n    clean: all\n  steps:\n  - template: azure-pipelines/setup-python3.yml\n  - template: azure-pipelines/install-latest-meson-ninja.yml\n  - template: azure-pipelines/windows-install-ninja.yml\n  - template: azure-pipelines/configure.yml\n    parameters:\n      beforeConfigure: call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat\" x86\n      defaultLibrary: shared\n  - template: azure-pipelines/build.yml\n    parameters:\n      beforeBuild: call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat\" x86\n  - template: azure-pipelines/windows-artifacts.yml\n    parameters:\n      os: windows\n      arch: 386\n      toolchain: msvc2017\n      libtype: shared\n      libfiles: libui.dll libui.exp libui.lib\n      osHeader: ui_windows.h\n\n- job: windows_386_msvc2017_static\n  displayName: 'Windows 386 MSVC2017 Static'\n  pool:\n    vmImage: 'vs2017-win2016'\n  workspace:\n    clean: all\n  steps:\n  - template: azure-pipelines/setup-python3.yml\n  - template: azure-pipelines/install-latest-meson-ninja.yml\n  - template: azure-pipelines/windows-install-ninja.yml\n  - template: azure-pipelines/configure.yml\n    parameters:\n      beforeConfigure: call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat\" x86\n      defaultLibrary: static\n  - template: azure-pipelines/build.yml\n    parameters:\n      beforeBuild: call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat\" x86\n      afterBuild: ren build\\meson-out\\libui.a libui.lib\n  - template: azure-pipelines/windows-artifacts.yml\n    parameters:\n      os: windows\n      arch: 386\n      toolchain: msvc2017\n      libtype: static\n      libfiles: libui.lib\n      osHeader: ui_windows.h\n\n- job: windows_amd64_msvc2017_shared\n  displayName: 'Windows amd64 MSVC2017 Shared'\n  pool:\n    vmImage: 'vs2017-win2016'\n  workspace:\n    clean: all\n  steps:\n  - template: azure-pipelines/setup-python3.yml\n  - template: azure-pipelines/install-latest-meson-ninja.yml\n  - template: azure-pipelines/windows-install-ninja.yml\n  - template: azure-pipelines/configure.yml\n    parameters:\n      beforeConfigure: call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat\" amd64\n      defaultLibrary: shared\n  - template: azure-pipelines/build.yml\n    parameters:\n      beforeBuild: call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat\" amd64\n  - template: azure-pipelines/windows-artifacts.yml\n    parameters:\n      os: windows\n      arch: amd64\n      toolchain: msvc2017\n      libtype: shared\n      libfiles: libui.dll libui.exp libui.lib\n      osHeader: ui_windows.h\n\n- job: windows_amd64_msvc2017_static\n  displayName: 'Windows amd64 MSVC2017 Static'\n  pool:\n    vmImage: 'vs2017-win2016'\n  workspace:\n    clean: all\n  steps:\n  - template: azure-pipelines/setup-python3.yml\n  - template: azure-pipelines/install-latest-meson-ninja.yml\n  - template: azure-pipelines/windows-install-ninja.yml\n  - template: azure-pipelines/configure.yml\n    parameters:\n      beforeConfigure: call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat\" amd64\n      defaultLibrary: static\n  - template: azure-pipelines/build.yml\n    parameters:\n      beforeBuild: call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat\" amd64\n      afterBuild: ren build\\meson-out\\libui.a libui.lib\n  - template: azure-pipelines/windows-artifacts.yml\n    parameters:\n      os: windows\n      arch: amd64\n      toolchain: msvc2017\n      libtype: static\n      libfiles: libui.lib\n      osHeader: ui_windows.h\n\n# }\n\n# mac {\n\n# TODO beforeConfigure/beforeBuild: export SDKROOT=$(xcodebuild -version -sdk macosx10.13 Path)?\n\n- job: darwin_amd64_shared\n  displayName: 'Darwin amd64 Shared'\n  pool:\n    vmImage: 'macos-10.13'\n  workspace:\n    clean: all\n  steps:\n  - template: azure-pipelines/setup-python3.yml\n  - template: azure-pipelines/install-latest-meson-ninja.yml\n  - template: azure-pipelines/darwin-install-ninja.yml\n  - template: azure-pipelines/configure.yml\n    parameters:\n      defaultLibrary: shared\n  - template: azure-pipelines/build.yml\n  - template: azure-pipelines/artifacts.yml\n    parameters:\n      os: darwin\n      arch: amd64\n      libtype: shared\n      libfiles: libui.A.dylib\n      osHeader: ui_darwin.h\n\n- job: darwin_amd64_static\n  displayName: 'Darwin amd64 Static'\n  pool:\n    vmImage: 'macos-10.13'\n  workspace:\n    clean: all\n  steps:\n  - template: azure-pipelines/setup-python3.yml\n  - template: azure-pipelines/install-latest-meson-ninja.yml\n  - template: azure-pipelines/darwin-install-ninja.yml\n  - template: azure-pipelines/configure.yml\n    parameters:\n      defaultLibrary: static\n  - template: azure-pipelines/build.yml\n  - template: azure-pipelines/artifacts.yml\n    parameters:\n      os: darwin\n      arch: amd64\n      libtype: static\n      libfiles: libui.a\n      osHeader: ui_darwin.h\n\n# }\n"
  },
  {
    "path": "common/OLD_table.c",
    "content": "// 21 june 2016\n#include \"../ui.h\"\n#include \"uipriv.h\"\n\nvoid *uiTableModelGiveInt(int i)\n{\n\treturn (void *) ((intptr_t) i);\n}\n\nint uiTableModelTakeInt(void *v)\n{\n\treturn (int) ((intptr_t) v);\n}\n\nuiTableColumn *uiTableAppendTextColumn(uiTable *t, const char *name, int modelColumn)\n{\n\tuiTableColumn *tc;\n\n\ttc = uiTableAppendColumn(t, name);\n\tuiTableColumnAppendTextPart(tc, modelColumn, 1);\n\treturn tc;\n}\n"
  },
  {
    "path": "common/areaevents.c",
    "content": "// 29 march 2014\n#include \"../ui.h\"\n#include \"uipriv.h\"\n\n/*\nWindows and GTK+ have a limit of 2 and 3 clicks, respectively, natively supported. Fortunately, we can simulate the double/triple-click behavior to build higher-order clicks. We can use the same algorithm Windows uses on both:\n\thttp://blogs.msdn.com/b/oldnewthing/archive/2004/10/18/243925.aspx\nFor GTK+, we pull the double-click time and double-click distance, which work the same as the equivalents on Windows (so the distance is in all directions), from the GtkSettings system.\n\nOn 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.\n\nThanks to mclasen, garnacho_, halfline, and tristan in irc.gimp.net/#gtk+.\n\nTODO note the bits about asymmetry and g_rcClick initial value not mattering in the oldnewthing article\n*/\n\n// x, y, xdist, ydist, and c.rect must have the same units\n// so must time, maxTime, and c.prevTime\nint uiprivClickCounterClick(uiprivClickCounter *c, int button, int x, int y, uintptr_t time, uintptr_t maxTime, int32_t xdist, int32_t ydist)\n{\n\t// different button than before? if so, don't count\n\tif (button != c->curButton)\n\t\tc->count = 0;\n\n\t// (x, y) in the allowed region for a double-click? if not, don't count\n\tif (x < c->rectX0)\n\t\tc->count = 0;\n\tif (y < c->rectY0)\n\t\tc->count = 0;\n\tif (x >= c->rectX1)\n\t\tc->count = 0;\n\tif (y >= c->rectY1)\n\t\tc->count = 0;\n\n\t// too slow? if so, don't count\n\t// note the below expression; time > (c.prevTime + maxTime) can overflow!\n\tif ((time - c->prevTime) > maxTime)\t// too slow; don't count\n\t\tc->count = 0;\n\n\tc->count++;\t\t// if either of the above ifs happened, this will make the click count 1; otherwise it will make the click count 2, 3, 4, 5, ...\n\n\t// now we need to update the internal structures for the next test\n\tc->curButton = button;\n\tc->prevTime = time;\n\tc->rectX0 = x - xdist;\n\tc->rectY0 = y - ydist;\n\tc->rectX1 = x + xdist;\n\tc->rectY1 = y + ydist;\n\n\treturn c->count;\n}\n\nvoid uiprivClickCounterReset(uiprivClickCounter *c)\n{\n\tc->curButton = 0;\n\tc->rectX0 = 0;\n\tc->rectY0 = 0;\n\tc->rectX1 = 0;\n\tc->rectY1 = 0;\n\tc->prevTime = 0;\n\tc->count = 0;\n}\n\n/*\nFor position independence across international keyboard layouts, typewriter keys are read using scancodes (which are always set 1).\nWindows provides the scancodes directly in the LPARAM.\nGTK+ provides the scancodes directly from the underlying window system via GdkEventKey.hardware_keycode.\nOn X11, this is scancode + 8 (because X11 keyboard codes have a range of [8,255]).\nWayland is guaranteed to give the same result (thanks ebassi in irc.gimp.net/#gtk+).\nOn Linux, where evdev is used instead of polling scancodes directly from the keyboard, evdev's typewriter section key code constants are the same as scancodes anyway, so the rules above apply.\nTypewriter section scancodes are the same across international keyboards with some exceptions that have been accounted for (see KeyEvent's documentation); see http://www.quadibloc.com/comp/scan.htm for details.\nNon-typewriter keys can be handled safely using constants provided by the respective backend API.\n\nBecause GTK+ keysyms may or may not obey Num Lock, we also handle the 0-9 and . keys on the numeric keypad with scancodes (they match too).\n*/\n\n// use uintptr_t to be safe; the size of the scancode/hardware key code field on each platform is different\nstatic const struct {\n\tuintptr_t scancode;\n\tchar equiv;\n} scancodeKeys[] = {\n\t{ 0x02, '1' },\n\t{ 0x03, '2' },\n\t{ 0x04, '3' },\n\t{ 0x05, '4' },\n\t{ 0x06, '5' },\n\t{ 0x07, '6' },\n\t{ 0x08, '7' },\n\t{ 0x09, '8' },\n\t{ 0x0A, '9' },\n\t{ 0x0B, '0' },\n\t{ 0x0C, '-' },\n\t{ 0x0D, '=' },\n\t{ 0x0E, '\\b' },\n\t{ 0x0F, '\\t' },\n\t{ 0x10, 'q' },\n\t{ 0x11, 'w' },\n\t{ 0x12, 'e' },\n\t{ 0x13, 'r' },\n\t{ 0x14, 't' },\n\t{ 0x15, 'y' },\n\t{ 0x16, 'u' },\n\t{ 0x17, 'i' },\n\t{ 0x18, 'o' },\n\t{ 0x19, 'p' },\n\t{ 0x1A, '[' },\n\t{ 0x1B, ']' },\n\t{ 0x1C, '\\n' },\n\t{ 0x1E, 'a' },\n\t{ 0x1F, 's' },\n\t{ 0x20, 'd' },\n\t{ 0x21, 'f' },\n\t{ 0x22, 'g' },\n\t{ 0x23, 'h' },\n\t{ 0x24, 'j' },\n\t{ 0x25, 'k' },\n\t{ 0x26, 'l' },\n\t{ 0x27, ';' },\n\t{ 0x28, '\\'' },\n\t{ 0x29, '`' },\n\t{ 0x2B, '\\\\' },\n\t{ 0x2C, 'z' },\n\t{ 0x2D, 'x' },\n\t{ 0x2E, 'c' },\n\t{ 0x2F, 'v' },\n\t{ 0x30, 'b' },\n\t{ 0x31, 'n' },\n\t{ 0x32, 'm' },\n\t{ 0x33, ',' },\n\t{ 0x34, '.' },\n\t{ 0x35, '/' },\n\t{ 0x39, ' ' },\n\t{ 0xFFFF, 0 },\n};\n\nstatic const struct {\n\tuintptr_t scancode;\n\tuiExtKey equiv;\n} scancodeExtKeys[] = {\n\t{ 0x47, uiExtKeyN7 },\n\t{ 0x48, uiExtKeyN8 },\n\t{ 0x49, uiExtKeyN9 },\n\t{ 0x4B, uiExtKeyN4 },\n\t{ 0x4C, uiExtKeyN5 },\n\t{ 0x4D, uiExtKeyN6 },\n\t{ 0x4F, uiExtKeyN1 },\n\t{ 0x50, uiExtKeyN2 },\n\t{ 0x51, uiExtKeyN3 },\n\t{ 0x52, uiExtKeyN0 },\n\t{ 0x53, uiExtKeyNDot },\n\t{ 0xFFFF, 0 },\n};\n\nint uiprivFromScancode(uintptr_t scancode, uiAreaKeyEvent *ke)\n{\n\tint i;\n\n\tfor (i = 0; scancodeKeys[i].scancode != 0xFFFF; i++)\n\t\tif (scancodeKeys[i].scancode == scancode) {\n\t\t\tke->Key = scancodeKeys[i].equiv;\n\t\t\treturn 1;\n\t\t}\n\tfor (i = 0; scancodeExtKeys[i].scancode != 0xFFFF; i++)\n\t\tif (scancodeExtKeys[i].scancode == scancode) {\n\t\t\tke->ExtKey = scancodeExtKeys[i].equiv;\n\t\t\treturn 1;\n\t\t}\n\treturn 0;\n}\n"
  },
  {
    "path": "common/attribute.c",
    "content": "// 19 february 2018\n#include \"../ui.h\"\n#include \"uipriv.h\"\n#include \"attrstr.h\"\n\nstruct uiAttribute {\n\tint ownedByUser;\n\tsize_t refcount;\n\tuiAttributeType type;\n\tunion {\n\t\tchar *family;\n\t\tdouble size;\n\t\tuiTextWeight weight;\n\t\tuiTextItalic italic;\n\t\tuiTextStretch stretch;\n\t\tstruct {\n\t\t\tdouble r;\n\t\t\tdouble g;\n\t\t\tdouble b;\n\t\t\tdouble a;\n\t\t\t// put this here so we can reuse this structure\n\t\t\tuiUnderlineColor underlineColor;\n\t\t} color;\n\t\tuiUnderline underline;\n\t\tuiOpenTypeFeatures *features;\n\t} u;\n};\n\nstatic uiAttribute *newAttribute(uiAttributeType type)\n{\n\tuiAttribute *a;\n\n\ta = uiprivNew(uiAttribute);\n\ta->ownedByUser = 1;\n\ta->refcount = 0;\n\ta->type = type;\n\treturn a;\n}\n\n// returns a to allow expressions like b = uiprivAttributeRetain(a)\n// TODO would this allow us to copy attributes between strings in a foreach func, and if so, should that be allowed?\nuiAttribute *uiprivAttributeRetain(uiAttribute *a)\n{\n\ta->ownedByUser = 0;\n\ta->refcount++;\n\treturn a;\n}\n\nstatic void destroy(uiAttribute *a)\n{\n\tswitch (a->type) {\n\tcase uiAttributeTypeFamily:\n\t\tuiprivFree(a->u.family);\n\t\tbreak;\n\tcase uiAttributeTypeFeatures:\n\t\tuiFreeOpenTypeFeatures(a->u.features);\n\t\tbreak;\n\t}\n\tuiprivFree(a);\n}\n\nvoid uiprivAttributeRelease(uiAttribute *a)\n{\n\tif (a->ownedByUser)\n\t\t/* TODO implementation bug: we can't release an attribute we don't own */;\n\ta->refcount--;\n\tif (a->refcount == 0)\n\t\tdestroy(a);\n}\n\nvoid uiFreeAttribute(uiAttribute *a)\n{\n\tif (!a->ownedByUser)\n\t\t/* TODO user bug: you can't free an attribute you don't own */;\n\tdestroy(a);\n}\n\nuiAttributeType uiAttributeGetType(const uiAttribute *a)\n{\n\treturn a->type;\n}\n\nuiAttribute *uiNewFamilyAttribute(const char *family)\n{\n\tuiAttribute *a;\n\n\ta = newAttribute(uiAttributeTypeFamily);\n\ta->u.family = (char *) uiprivAlloc((strlen(family) + 1) * sizeof (char), \"char[] (uiAttribute)\");\n\tstrcpy(a->u.family, family);\n\treturn a;\n}\n\nconst char *uiAttributeFamily(const uiAttribute *a)\n{\n\treturn a->u.family;\n}\n\nuiAttribute *uiNewSizeAttribute(double size)\n{\n\tuiAttribute *a;\n\n\ta = newAttribute(uiAttributeTypeSize);\n\ta->u.size = size;\n\treturn a;\n}\n\ndouble uiAttributeSize(const uiAttribute *a)\n{\n\treturn a->u.size;\n}\n\nuiAttribute *uiNewWeightAttribute(uiTextWeight weight)\n{\n\tuiAttribute *a;\n\n\ta = newAttribute(uiAttributeTypeWeight);\n\ta->u.weight = weight;\n\treturn a;\n}\n\nuiTextWeight uiAttributeWeight(const uiAttribute *a)\n{\n\treturn a->u.weight;\n}\n\nuiAttribute *uiNewItalicAttribute(uiTextItalic italic)\n{\n\tuiAttribute *a;\n\n\ta = newAttribute(uiAttributeTypeItalic);\n\ta->u.italic = italic;\n\treturn a;\n}\n\nuiTextItalic uiAttributeItalic(const uiAttribute *a)\n{\n\treturn a->u.italic;\n}\n\nuiAttribute *uiNewStretchAttribute(uiTextStretch stretch)\n{\n\tuiAttribute *a;\n\n\ta = newAttribute(uiAttributeTypeStretch);\n\ta->u.stretch = stretch;\n\treturn a;\n}\n\nuiTextStretch uiAttributeStretch(const uiAttribute *a)\n{\n\treturn a->u.stretch;\n}\n\nuiAttribute *uiNewColorAttribute(double r, double g, double b, double a)\n{\n\tuiAttribute *at;\n\n\tat = newAttribute(uiAttributeTypeColor);\n\tat->u.color.r = r;\n\tat->u.color.g = g;\n\tat->u.color.b = b;\n\tat->u.color.a = a;\n\treturn at;\n}\n\nvoid uiAttributeColor(const uiAttribute *a, double *r, double *g, double *b, double *alpha)\n{\n\t*r = a->u.color.r;\n\t*g = a->u.color.g;\n\t*b = a->u.color.b;\n\t*alpha = a->u.color.a;\n}\n\nuiAttribute *uiNewBackgroundAttribute(double r, double g, double b, double a)\n{\n\tuiAttribute *at;\n\n\tat = newAttribute(uiAttributeTypeBackground);\n\tat->u.color.r = r;\n\tat->u.color.g = g;\n\tat->u.color.b = b;\n\tat->u.color.a = a;\n\treturn at;\n}\n\nuiAttribute *uiNewUnderlineAttribute(uiUnderline u)\n{\n\tuiAttribute *a;\n\n\ta = newAttribute(uiAttributeTypeUnderline);\n\ta->u.underline = u;\n\treturn a;\n}\n\nuiUnderline uiAttributeUnderline(const uiAttribute *a)\n{\n\treturn a->u.underline;\n}\n\nuiAttribute *uiNewUnderlineColorAttribute(uiUnderlineColor u, double r, double g, double b, double a)\n{\n\tuiAttribute *at;\n\n\tat = uiNewColorAttribute(r, g, b, a);\n\tat->type = uiAttributeTypeUnderlineColor;\n\tat->u.color.underlineColor = u;\n\treturn at;\n}\n\nvoid uiAttributeUnderlineColor(const uiAttribute *a, uiUnderlineColor *u, double *r, double *g, double *b, double *alpha)\n{\n\t*u = a->u.color.underlineColor;\n\tuiAttributeColor(a, r, g, b, alpha);\n}\n\nuiAttribute *uiNewFeaturesAttribute(const uiOpenTypeFeatures *otf)\n{\n\tuiAttribute *a;\n\n\ta = newAttribute(uiAttributeTypeFeatures);\n\ta->u.features = uiOpenTypeFeaturesClone(otf);\n\treturn a;\n}\n\nconst uiOpenTypeFeatures *uiAttributeFeatures(const uiAttribute *a)\n{\n\treturn a->u.features;\n}\n\nint uiprivAttributeEqual(const uiAttribute *a, const uiAttribute *b)\n{\n\tif (a == b)\n\t\treturn 1;\n\tif (a->type != b->type)\n\t\treturn 0;\n\tswitch (a->type) {\n\tcase uiAttributeTypeFamily:\n\t\treturn uiprivStricmp(a->u.family, b->u.family);\n\tcase uiAttributeTypeSize:\n\t\t// TODO is the use of == correct?\n\t\treturn a->u.size == b->u.size;\n\tcase uiAttributeTypeWeight:\n\t\treturn a->u.weight == b->u.weight;\n\tcase uiAttributeTypeItalic:\n\t\treturn a->u.italic == b->u.italic;\n\tcase uiAttributeTypeStretch:\n\t\treturn a->u.stretch == b->u.stretch;\n\tcase uiAttributeTypeUnderline:\n\t\treturn a->u.underline == b->u.underline;\n\tcase uiAttributeTypeUnderlineColor:\n\t\tif (a->u.color.underlineColor != b->u.color.underlineColor)\n\t\t\treturn 0;\n\t\t// fall through\n\tcase uiAttributeTypeColor:\n\tcase uiAttributeTypeBackground:\n\t\t// TODO is the use of == correct?\n\t\treturn (a->u.color.r == b->u.color.r) &&\n\t\t\t(a->u.color.g == b->u.color.g) &&\n\t\t\t(a->u.color.b == b->u.color.b) &&\n\t\t\t(a->u.color.a == b->u.color.a);\n\tcase uiAttributeTypeFeatures:\n\t\treturn uiprivOpenTypeFeaturesEqual(a->u.features, b->u.features);\n\t}\n\t// TODO should not be reached\n\treturn 0;\n}\n"
  },
  {
    "path": "common/attrlist.c",
    "content": "// 16 december 2016\n#include \"../ui.h\"\n#include \"uipriv.h\"\n#include \"attrstr.h\"\n\n/*\nAn attribute list is a doubly linked list of attributes.\nAttribute start positions are inclusive and attribute end positions are exclusive (or in other words, [start, end)).\nThe 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.\nOverlapping attributes are not allowed; if an attribute is added that conflicts with an existing one, the existing one is removed.\nIn 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?)\nThe linked list is not a ring; alist->fist->prev == NULL and alist->last->next == NULL.\nTODO verify that this disallows attributes of length zero\n*/\n\nstruct attr {\n\tuiAttribute *val;\n\tsize_t start;\n\tsize_t end;\n\tstruct attr *prev;\n\tstruct attr *next;\n};\n\nstruct uiprivAttrList {\n\tstruct attr *first;\n\tstruct attr *last;\n};\n\n// if before is NULL, add to the end of the list\nstatic void attrInsertBefore(uiprivAttrList *alist, struct attr *a, struct attr *before)\n{\n\t// if the list is empty, this is the first item\n\tif (alist->first == NULL) {\n\t\talist->first = a;\n\t\talist->last = a;\n\t\treturn;\n\t}\n\n\t// add to the end\n\tif (before == NULL) {\n\t\tstruct attr *oldlast;\n\n\t\toldlast = alist->last;\n\t\talist->last = a;\n\t\ta->prev = oldlast;\n\t\toldlast->next = a;\n\t\treturn;\n\t}\n\n\t// add to the beginning\n\tif (before == alist->first) {\n\t\tstruct attr *oldfirst;\n\n\t\toldfirst = alist->first;\n\t\talist->first = a;\n\t\toldfirst->prev = a;\n\t\ta->next = oldfirst;\n\t\treturn;\n\t}\n\n\t// add to the middle\n\ta->prev = before->prev;\n\ta->next = before;\n\tbefore->prev = a;\n\ta->prev->next = a;\n}\n\nstatic int attrHasPos(struct attr *a, size_t pos)\n{\n\tif (pos < a->start)\n\t\treturn 0;\n\treturn pos < a->end;\n}\n\n// returns 1 if there was an intersection and 0 otherwise\nstatic int attrRangeIntersect(struct attr *a, size_t *start, size_t *end)\n{\n\t// is the range outside a entirely?\n\tif (*start >= a->end)\n\t\treturn 0;\n\tif (*end < a->start)\n\t\treturn 0;\n\n\t// okay, so there is an overlap\n\t// compute the intersection\n\tif (*start < a->start)\n\t\t*start = a->start;\n\tif (*end > a->end)\n\t\t*end = a->end;\n\treturn 1;\n}\n\n// returns the old a->next, for forward iteration\nstatic struct attr *attrUnlink(uiprivAttrList *alist, struct attr *a)\n{\n\tstruct attr *p, *n;\n\n\tp = a->prev;\n\tn = a->next;\n\ta->prev = NULL;\n\ta->next = NULL;\n\n\t// only item in list?\n\tif (p == NULL && n == NULL) {\n\t\talist->first = NULL;\n\t\talist->last = NULL;\n\t\treturn NULL;\n\t}\n\t// start of list?\n\tif (p == NULL) {\n\t\tn->prev = NULL;\n\t\talist->first = n;\n\t\treturn n;\n\t}\n\t// end of list?\n\tif (n == NULL) {\n\t\tp->next = NULL;\n\t\talist->last = p;\n\t\treturn NULL;\n\t}\n\t// middle of list\n\tp->next = n;\n\tn->prev = p;\n\treturn n;\n}\n\n// returns the old a->next, for forward iteration\nstatic struct attr *attrDelete(uiprivAttrList *alist, struct attr *a)\n{\n\tstruct attr *next;\n\n\tnext = attrUnlink(alist, a);\n\tuiprivAttributeRelease(a->val);\n\tuiprivFree(a);\n\treturn next;\n}\n\n// attrDropRange() removes attributes without deleting characters.\n// \n// If the attribute needs no change, then nothing is done.\n// \n// If the attribute needs to be deleted, it is deleted.\n// \n// If the attribute only needs to be resized at the end, it is adjusted.\n// \n// If the attribute only needs to be resized at the start, it is adjusted, unlinked, and returned in tail.\n// \n// 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.\n// \n// In all cases, the return value is the next attribute to look at in a forward sequential loop.\nstatic struct attr *attrDropRange(uiprivAttrList *alist, struct attr *a, size_t start, size_t end, struct attr **tail)\n{\n\tstruct attr *b;\n\n\t// always pre-initialize tail to NULL\n\t*tail = NULL;\n\n\tif (!attrRangeIntersect(a, &start, &end))\n\t\t// out of range; nothing to do\n\t\treturn a->next;\n\n\t// just outright delete the attribute?\n\t// the inequalities handle attributes entirely inside the range\n\t// if both are equal, the attribute's range is equal to the range\n\tif (a->start >= start && a->end <= end)\n\t\treturn attrDelete(alist, a);\n\n\t// only chop off the start or end?\n\tif (a->start == start) {\t\t\t// chop off the start\n\t\t// we are dropping the left half, so set a->start and unlink\n\t\ta->start = end;\n\t\t*tail = a;\n\t\treturn attrUnlink(alist, a);\n\t}\n\tif (a->end == end) {\t\t\t\t// chop off the end\n\t\t// we are dropping the right half, so just set a->end\n\t\ta->end = start;\n\t\treturn a->next;\n\t}\n\n\t// we'll need to split the attribute into two\n\tb = uiprivNew(struct attr);\n\tb->val = uiprivAttributeRetain(a->val);\n\tb->start = end;\n\tb->end = a->end;\n\t*tail = b;\n\n\ta->end = start;\n\treturn a->next;\n}\n\nstatic void attrGrow(uiprivAttrList *alist, struct attr *a, size_t start, size_t end)\n{\n\tstruct attr *before;\n\n\t// adjusting the end is simple: if it ends before our new end, just set the new end\n\tif (a->end < end)\n\t\ta->end = end;\n\n\t// adjusting the start is harder\n\t// if the start is before our new start, we are done\n\t// otherwise, we have to move the start back AND reposition the attribute to keep the sorted order\n\tif (a->start <= start)\n\t\treturn;\n\ta->start = start;\n\tattrUnlink(alist, a);\n\tfor (before = alist->first; before != NULL; before = before->next)\n\t\tif (before->start > a->start)\n\t\t\tbreak;\n\tattrInsertBefore(alist, a, before);\n}\n\n// returns the right side of the split, which is unlinked, or NULL if no split was done\nstatic struct attr *attrSplitAt(uiprivAttrList *alist, struct attr *a, size_t at)\n{\n\tstruct attr *b;\n\n\t// no splittng needed?\n\t// note the equality: we don't need to split at start or end\n\t// 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\n\tif (at <= a->start)\n\t\treturn NULL;\n\tif (at >= a->end)\n\t\treturn NULL;\n\n\tb = uiprivNew(struct attr);\n\tb->val = uiprivAttributeRetain(a->val);\n\tb->start = at;\n\tb->end = a->end;\n\n\ta->end = at;\n\treturn b;\n}\n\n// attrDeleteRange() removes attributes while deleting characters.\n// \n// If the attribute does not include the deleted range, then nothing is done (though the start and end are adjusted as necessary).\n// \n// If the attribute needs to be deleted, it is deleted.\n// \n// Otherwise, the attribute only needs the start or end deleted, and it is adjusted.\n// \n// In all cases, the return value is the next attribute to look at in a forward sequential loop.\n// TODO rewrite this comment\nstatic struct attr *attrDeleteRange(uiprivAttrList *alist, struct attr *a, size_t start, size_t end)\n{\n\tsize_t ostart, oend;\n\tsize_t count;\n\n\tostart = start;\n\toend = end;\n\tcount = oend - ostart;\n\n\tif (!attrRangeIntersect(a, &start, &end)) {\n\t\t// out of range\n\t\t// adjust if necessary\n\t\tif (a->start >= ostart)\n\t\t\ta->start -= count;\n\t\tif (a->end >= oend)\n\t\t\ta->end -= count;\n\t\treturn a->next;\n\t}\n\n\t// just outright delete the attribute?\n\t// the inequalities handle attributes entirely inside the range\n\t// if both are equal, the attribute's range is equal to the range\n\tif (a->start >= start && a->end <= end)\n\t\treturn attrDelete(alist, a);\n\n\t// only chop off the start or end?\n\tif (a->start == start) {\t\t\t// chop off the start\n\t\t// if we weren't adjusting positions this would just be setting a->start to end\n\t\t// but since this is deleting from the start, we need to adjust both by count\n\t\ta->start = end - count;\n\t\ta->end -= count;\n\t\treturn a->next;\n\t}\n\tif (a->end == end) {\t\t\t\t// chop off the end\n\t\t// a->start is already good\n\t\ta->end = start;\n\t\treturn a->next;\n\t}\n\n\t// in this case, the deleted range is inside the attribute\n\t// we can clear it by just removing count from a->end\n\ta->end -= count;\n\treturn a->next;\n}\n\nuiprivAttrList *uiprivNewAttrList(void)\n{\n\treturn uiprivNew(uiprivAttrList);\n}\n\nvoid uiprivFreeAttrList(uiprivAttrList *alist)\n{\n\tstruct attr *a, *next;\n\n\ta = alist->first;\n\twhile (a != NULL) {\n\t\tnext = a->next;\n\t\tuiprivAttributeRelease(a->val);\n\t\tuiprivFree(a);\n\t\ta = next;\n\t}\n\tuiprivFree(alist);\n}\n\nvoid uiprivAttrListInsertAttribute(uiprivAttrList *alist, uiAttribute *val, size_t start, size_t end)\n{\n\tstruct attr *a;\n\tstruct attr *before;\n\tstruct attr *tail = NULL;\n\tint split = 0;\n\tuiAttributeType valtype;\n\n\t// first, figure out where in the list this should go\n\t// in addition, if this attribute overrides one that already exists, split that one apart so this one can take over\n\tbefore = alist->first;\n\tvaltype = uiAttributeGetType(val);\n\twhile (before != NULL) {\n\t\tsize_t lstart, lend;\n\n\t\t// once we get to the first point after start, we know where to insert\n\t\tif (before->start > start)\n\t\t\tbreak;\n\n\t\t// if we have already split a prior instance of this attribute, don't bother doing it again\n\t\tif (split)\n\t\t\tgoto next;\n\n\t\t// should we split this attribute?\n\t\tif (uiAttributeGetType(before->val) != valtype)\n\t\t\tgoto next;\n\t\tlstart = start;\n\t\tlend = end;\n\t\tif (!attrRangeIntersect(before, &lstart, &lend))\n\t\t\tgoto next;\n\n\t\t// 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\n\t\t// 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?\n\t\tif (uiprivAttributeEqual(before->val, val)) {\n\t\t\tattrGrow(alist, before, start, end);\n\t\t\treturn;\n\t\t}\n\t\t// okay the values are different; we need to split apart\n\t\tbefore = attrDropRange(alist, before, start, end, &tail);\n\t\tsplit = 1;\n\t\tcontinue;\n\n\tnext:\n\t\tbefore = before->next;\n\t}\n\n\t// if we got here, we know we have to add the attribute before before\n\ta = uiprivNew(struct attr);\n\ta->val = uiprivAttributeRetain(val);\n\ta->start = start;\n\ta->end = end;\n\tattrInsertBefore(alist, a, before);\n\n\t// and finally, if we split, insert the remainder\n\tif (tail == NULL)\n\t\treturn;\n\t// note we start at before; it won't be inserted before that by the sheer nature of how the code above works\n\tfor (; before != NULL; before = before->next)\n\t\tif (before->start > tail->start)\n\t\t\tbreak;\n\tattrInsertBefore(alist, tail, before);\n}\n\nvoid uiprivAttrListInsertCharactersUnattributed(uiprivAttrList *alist, size_t start, size_t count)\n{\n\tstruct attr *a;\n\tstruct attr *tails = NULL;\n\n\t// every attribute before the insertion point can either cross into the insertion point or not\n\t// 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\n\tfor (a = alist->first; a != NULL; a = a->next) {\n\t\tstruct attr *tail;\n\n\t\t// stop once we get to the insertion point\n\t\tif (a->start >= start)\n\t\t\tbreak;\n\t\t// only do something if overlapping\n\t\tif (!attrHasPos(a, start))\n\t\t\tcontinue;\n\n\t\ttail = attrSplitAt(alist, a, start);\n\t\t// adjust the new tail for the insertion\n\t\ttail->start += count;\n\t\ttail->end += count;\n\t\t// and queue it for re-adding later\n\t\t// 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\n\t\t// 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...\n\t\ttail->next = tails;\n\t\ttails = tail;\n\t}\n\n\t// at this point in the attribute list, we are at or after the insertion point\n\t// all the split-apart attributes will be at the insertion point\n\t// therefore, we can just add them all back now, and the list will still be sorted correctly\n\twhile (tails != NULL) {\n\t\tstruct attr *next;\n\n\t\t// make all the links NULL before insertion, just to be safe\n\t\tnext = tails->next;\n\t\ttails->next = NULL;\n\t\tattrInsertBefore(alist, tails, a);\n\t\ttails = next;\n\t}\n\n\t// every remaining attribute will be either at or after the insertion point\n\t// we just need to move them ahead\n\tfor (; a != NULL; a = a->next) {\n\t\ta->start += count;\n\t\ta->end += count;\n\t}\n}\n\n// The attributes are those of character start - 1.\n// If start == 0, the attributes are those of character 0.\n/*\nThis is an obtuse function. Here's some diagrams to help.\n\nGiven the input string\n\tabcdefghi (positions: 012345678 9)\nand attribute set\n\tred start 0 end 3\n\tbold start 2 end 6\n\tunderline start 5 end 8\nor visually:\n\t012345678 9\n\trrr------\n\t--bbbb---\n\t-----uuu-\nIf we insert qwe to result in positions 0123456789AB C:\n\nbefore 0, 1, 2 (grow the red part, move everything else down)\n\tred -> start 0 (no change) end 3+3=6\n\tbold -> start 2+3=5 end 6+3=9\n\tunderline -> start 5+3=8 end 8+3=B\nbefore 3 (grow red and bold, move underline down)\n\tred -> start 0 (no change) end 3+3=6\n\tbold -> start 2 (no change) end 6+3=9\n\tunderline -> start 5+3=8 end 8+3=B\nbefore 4, 5 (keep red, grow bold, move underline down)\n\tred -> start 0 (no change) end 3 (no change)\n\tbold -> start 2 (no change) end 6+3=9\n\tunderline -> start 5+3=8 end 8+3=B\nbefore 6 (keep red, grow bold and underline)\n\tred -> start 0 (no change) end 3 (no change)\n\tbold -> start 2 (no change) end 6+3=9\n\tunderline -> start 5 (no change) end 8+3=B\nbefore 7, 8 (keep red and bold, grow underline)\n\tred -> start 0 (no change) end 3 (no change)\n\tbold -> start 2 (no change) end 6 (no change)\n\tunderline -> start 5 (no change) end 8+3=B\nbefore 9 (keep all three)\n\tred -> start 0 (no change) end 3 (no change)\n\tbold -> start 2 (no change) end 6 (no change)\n\tunderline -> start 5 (no change) end 8 (no change)\n\nresult:\n          0 1 2 3 4 5 6 7 8 9\n      red E E E e n n n n n n\n     bold s s S E E E e n n n\nunderline s s s s s S E E e n\nN = none\nE = end only\nS = start and end\nuppercase = in original range, lowercase = not\n\nwhich results in our algorithm:\n\tfor each attribute\n\t\tif start < insertion point\n\t\t\tmove start up\n\t\telse if start == insertion point\n\t\t\tif start != 0\n\t\t\t\tmove start up\n\t\tif end <= insertion point\n\t\t\tmove end up\n*/\n// TODO does this ensure the list remains sorted?\nvoid uiprivAttrListInsertCharactersExtendingAttributes(uiprivAttrList *alist, size_t start, size_t count)\n{\n\tstruct attr *a;\n\n\tfor (a = alist->first; a != NULL; a = a->next) {\n\t\tif (a->start < start)\n\t\t\ta->start += count;\n\t\telse if (a->start == start && start != 0)\n\t\t\ta->start += count;\n\t\tif (a->end <= start)\n\t\t\ta->end += count;\n\t}\n}\n\n// TODO replace at point with — replaces with first character's attributes\n\nvoid uiprivAttrListRemoveAttribute(uiprivAttrList *alist, uiAttributeType type, size_t start, size_t end)\n{\n\tstruct attr *a;\n\tstruct attr *tails = NULL;\t\t// see uiprivAttrListInsertCharactersUnattributed() above\n\tstruct attr *tailsAt = NULL;\n\n\ta = alist->first;\n\twhile (a != NULL) {\n\t\tsize_t lstart, lend;\n\t\tstruct attr *tail;\n\n\t\t// this defines where to re-attach the tails\n\t\t// (all the tails will have their start at end, so we can just insert them all before tailsAt)\n\t\tif (a->start >= end) {\n\t\t\ttailsAt = a;\n\t\t\t// and at this point we're done, so\n\t\t\tbreak;\n\t\t}\n\t\tif (uiAttributeGetType(a->val) != type)\n\t\t\tgoto next;\n\t\tlstart = start;\n\t\tlend = end;\n\t\tif (!attrRangeIntersect(a, &lstart, &lend))\n\t\t\tgoto next;\n\t\ta = attrDropRange(alist, a, start, end, &tail);\n\t\tif (tail != NULL) {\n\t\t\ttail->next = tails;\n\t\t\ttails = tail;\n\t\t}\n\t\tcontinue;\n\n\tnext:\n\t\ta = a->next;\n\t}\n\n\twhile (tails != NULL) {\n\t\tstruct attr *next;\n\n\t\t// make all the links NULL before insertion, just to be safe\n\t\tnext = tails->next;\n\t\ttails->next = NULL;\n\t\tattrInsertBefore(alist, tails, a);\n\t\ttails = next;\n\t}\n}\n\n// TODO merge this with the above\nvoid uiprivAttrListRemoveAttributes(uiprivAttrList *alist, size_t start, size_t end)\n{\n\tstruct attr *a;\n\tstruct attr *tails = NULL;\t\t// see uiprivAttrListInsertCharactersUnattributed() above\n\tstruct attr *tailsAt = NULL;\n\n\ta = alist->first;\n\twhile (a != NULL) {\n\t\tsize_t lstart, lend;\n\t\tstruct attr *tail;\n\n\t\t// this defines where to re-attach the tails\n\t\t// (all the tails will have their start at end, so we can just insert them all before tailsAt)\n\t\tif (a->start >= end) {\n\t\t\ttailsAt = a;\n\t\t\t// and at this point we're done, so\n\t\t\tbreak;\n\t\t}\n\t\tlstart = start;\n\t\tlend = end;\n\t\tif (!attrRangeIntersect(a, &lstart, &lend))\n\t\t\tgoto next;\n\t\ta = attrDropRange(alist, a, start, end, &tail);\n\t\tif (tail != NULL) {\n\t\t\ttail->next = tails;\n\t\t\ttails = tail;\n\t\t}\n\t\tcontinue;\n\n\tnext:\n\t\ta = a->next;\n\t}\n\n\twhile (tails != NULL) {\n\t\tstruct attr *next;\n\n\t\t// make all the links NULL before insertion, just to be safe\n\t\tnext = tails->next;\n\t\ttails->next = NULL;\n\t\tattrInsertBefore(alist, tails, a);\n\t\ttails = next;\n\t}\n}\n\nvoid uiprivAttrListRemoveCharacters(uiprivAttrList *alist, size_t start, size_t end)\n{\n\tstruct attr *a;\n\n\ta = alist->first;\n\twhile (a != NULL)\n\t\ta = attrDeleteRange(alist, a, start, end);\n}\n\nvoid uiprivAttrListForEach(const uiprivAttrList *alist, const uiAttributedString *s, uiAttributedStringForEachAttributeFunc f, void *data)\n{\n\tstruct attr *a;\n\tuiForEach ret;\n\n\tfor (a = alist->first; a != NULL; a = a->next) {\n\t\tret = (*f)(s, a->val, a->start, a->end, data);\n\t\tif (ret == uiForEachStop)\n\t\t\t// TODO for all: break or return?\n\t\t\tbreak;\n\t}\n}\n"
  },
  {
    "path": "common/attrstr.c",
    "content": "// 3 december 2016\n#include \"../ui.h\"\n#include \"uipriv.h\"\n#include \"attrstr.h\"\n\nstruct uiAttributedString {\n\tchar *s;\n\tsize_t len;\n\n\tuiprivAttrList *attrs;\n\n\t// indiscriminately keep a UTF-16 copy of the string on all platforms so we can hand this off to the grapheme calculator\n\t// this ensures no one platform has a speed advantage (sorry GTK+)\n\tuint16_t *u16;\n\tsize_t u16len;\n\n\tsize_t *u8tou16;\n\tsize_t *u16tou8;\n\n\t// this is lazily created to keep things from getting *too* slow\n\tuiprivGraphemes *graphemes;\n};\n\nstatic void resize(uiAttributedString *s, size_t u8, size_t u16)\n{\n\ts->len = u8;\n\ts->s = (char *) uiprivRealloc(s->s, (s->len + 1) * sizeof (char), \"char[] (uiAttributedString)\");\n\ts->u8tou16 = (size_t *) uiprivRealloc(s->u8tou16, (s->len + 1) * sizeof (size_t), \"size_t[] (uiAttributedString)\");\n\ts->u16len = u16;\n\ts->u16 = (uint16_t *) uiprivRealloc(s->u16, (s->u16len + 1) * sizeof (uint16_t), \"uint16_t[] (uiAttributedString)\");\n\ts->u16tou8 = (size_t *) uiprivRealloc(s->u16tou8, (s->u16len + 1) * sizeof (size_t), \"size_t[] (uiAttributedString)\");\n}\n\nuiAttributedString *uiNewAttributedString(const char *initialString)\n{\n\tuiAttributedString *s;\n\n\ts = uiprivNew(uiAttributedString);\n\ts->attrs = uiprivNewAttrList();\n\tuiAttributedStringAppendUnattributed(s, initialString);\n\treturn s;\n}\n\n// TODO make sure that all implementations of uiprivNewGraphemes() work fine with empty strings; in particular, the Windows one might not\nstatic void recomputeGraphemes(uiAttributedString *s)\n{\n\tif (s->graphemes != NULL)\n\t\treturn;\n\tif (uiprivGraphemesTakesUTF16()) {\n\t\ts->graphemes = uiprivNewGraphemes(s->u16, s->u16len);\n\t\treturn;\n\t}\n\ts->graphemes = uiprivNewGraphemes(s->s, s->len);\n}\n\nstatic void invalidateGraphemes(uiAttributedString *s)\n{\n\tif (s->graphemes == NULL)\n\t\treturn;\n\tuiprivFree(s->graphemes->pointsToGraphemes);\n\tuiprivFree(s->graphemes->graphemesToPoints);\n\tuiprivFree(s->graphemes);\n\ts->graphemes = NULL;\n}\n\nvoid uiFreeAttributedString(uiAttributedString *s)\n{\n\tuiprivFreeAttrList(s->attrs);\n\tinvalidateGraphemes(s);\n\tuiprivFree(s->u16tou8);\n\tuiprivFree(s->u8tou16);\n\tuiprivFree(s->u16);\n\tuiprivFree(s->s);\n\tuiprivFree(s);\n}\n\nconst char *uiAttributedStringString(const uiAttributedString *s)\n{\n\treturn s->s;\n}\n\nsize_t uiAttributedStringLen(const uiAttributedString *s)\n{\n\treturn s->len;\n}\n\nstatic void u8u16len(const char *str, size_t *n8, size_t *n16)\n{\n\tuint32_t rune;\n\tchar buf[4];\n\tuint16_t buf16[2];\n\n\t*n8 = 0;\n\t*n16 = 0;\n\twhile (*str) {\n\t\tstr = uiprivUTF8DecodeRune(str, 0, &rune);\n\t\t// TODO document the use of the function vs a pointer subtract here\n\t\t// TODO also we need to consider namespace collision with utf.h...\n\t\t*n8 += uiprivUTF8EncodeRune(rune, buf);\n\t\t*n16 += uiprivUTF16EncodeRune(rune, buf16);\n\t}\n}\n\nvoid uiAttributedStringAppendUnattributed(uiAttributedString *s, const char *str)\n{\n\tuiAttributedStringInsertAtUnattributed(s, str, s->len);\n}\n\n// 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\nstatic int onCodepointBoundary(uiAttributedString *s, size_t at)\n{\n\tuint8_t c;\n\n\t// for uiNewAttributedString()\n\tif (s->s == NULL && at == 0)\n\t\treturn 1;\n\tc = (uint8_t) (s->s[at]);\n\treturn c < 0x80 || c >= 0xC0;\n}\n\n// TODO note that at must be on a codeoint boundary\nvoid uiAttributedStringInsertAtUnattributed(uiAttributedString *s, const char *str, size_t at)\n{\n\tuint32_t rune;\n\tchar buf[4];\n\tuint16_t buf16[2];\n\tsize_t n8, n16;\t\t// TODO make loop-local? to avoid using them in the wrong place again\n\tsize_t old, old16;\n\tsize_t oldn8, oldn16;\n\tsize_t oldlen, old16len;\n\tsize_t at16;\n\tsize_t i;\n\n\tif (!onCodepointBoundary(s, at)) {\n\t\t// TODO\n\t}\n\n\tat16 = 0;\n\tif (s->u8tou16 != NULL)\n\t\tat16 = s->u8tou16[at];\n\n\t// do this first to reclaim memory\n\tinvalidateGraphemes(s);\n\n\t// first figure out how much we need to grow by\n\t// this includes post-validated UTF-8\n\tu8u16len(str, &n8, &n16);\n\n\t// and resize\n\told = at;\n\told16 = at16;\n\toldlen = s->len;\n\told16len = s->u16len;\n\tresize(s, s->len + n8, s->u16len + n16);\n\n\t// move existing characters out of the way\n\t// note the use of memmove(): https://twitter.com/rob_pike/status/737797688217894912\n\tmemmove(\n\t\ts->s + at + n8,\n\t\ts->s + at,\n\t\t(oldlen - at) * sizeof (char));\n\tmemmove(\n\t\ts->u16 + at16 + n16,\n\t\ts->u16 + at16,\n\t\t(old16len - at16) * sizeof (uint16_t));\n\t// note the + 1 for these; we want to copy the terminating null too\n\tmemmove(\n\t\ts->u8tou16 + at + n8,\n\t\ts->u8tou16 + at,\n\t\t(oldlen - at + 1) * sizeof (size_t));\n\tmemmove(\n\t\ts->u16tou8 + at16 + n16,\n\t\ts->u16tou8 + at16,\n\t\t(old16len - at16 + 1) * sizeof (size_t));\n\toldn8 = n8;\n\toldn16 = n16;\n\n\t// and copy\n\twhile (*str) {\n\t\tsize_t n;\n\n\t\tstr = uiprivUTF8DecodeRune(str, 0, &rune);\n\t\tn = uiprivUTF8EncodeRune(rune, buf);\n\t\tn16 = uiprivUTF16EncodeRune(rune, buf16);\n\t\ts->s[old] = buf[0];\n\t\ts->u8tou16[old] = old16;\n\t\tif (n > 1) {\n\t\t\ts->s[old + 1] = buf[1];\n\t\t\ts->u8tou16[old + 1] = old16;\n\t\t}\n\t\tif (n > 2) {\n\t\t\ts->s[old + 2] = buf[2];\n\t\t\ts->u8tou16[old + 2] = old16;\n\t\t}\n\t\tif (n > 3) {\n\t\t\ts->s[old + 3] = buf[3];\n\t\t\ts->u8tou16[old + 3] = old16;\n\t\t}\n\t\ts->u16[old16] = buf16[0];\n\t\ts->u16tou8[old16] = old;\n\t\tif (n16 > 1) {\n\t\t\ts->u16[old16 + 1] = buf16[1];\n\t\t\ts->u16tou8[old16 + 1] = old;\n\t\t}\n\t\told += n;\n\t\told16 += n16;\n\t}\n\t// and have an index for the end of the string\n\t// TODO is this done by the below?\n//TODO\ts->u8tou16[old] = old16;\n//TODO\ts->u16tou8[old16] = old;\n\n\t// and adjust the prior values in the conversion tables\n\t// use <= so the terminating 0 gets updated too\n\tfor (i = 0; i <= oldlen - at; i++)\n\t\ts->u8tou16[at + oldn8 + i] += s->u16len - old16len;\n\tfor (i = 0; i <= old16len - at16; i++)\n\t\ts->u16tou8[at16 + oldn16 + i] += s->len - oldlen;\n\n\t// and finally do the attributes\n\tuiprivAttrListInsertCharactersUnattributed(s->attrs, at, n8);\n}\n\n// TODO document that end is the first index that will be maintained\nvoid uiAttributedStringDelete(uiAttributedString *s, size_t start, size_t end)\n{\n\tsize_t start16, end16;\n\tsize_t count, count16;\n\tsize_t i;\n\n\tif (!onCodepointBoundary(s, start)) {\n\t\t// TODO\n\t}\n\tif (!onCodepointBoundary(s, end)) {\n\t\t// TODO\n\t}\n\n\tcount = end - start;\n\tstart16 = s->u8tou16[start];\n\tend16 = s->u8tou16[end];\n\tcount16 = end16 - start16;\n\n\tinvalidateGraphemes(s);\n\n\t// overwrite old characters\n\tmemmove(\n\t\ts->s + start,\n\t\ts->s + end,\n\t\t(s->len - end) * sizeof (char));\n\tmemmove(\n\t\ts->u16 + start16,\n\t\ts->u16 + end16,\n\t\t(s->u16len - end16) * sizeof (uint16_t));\n\t// note the + 1 for these; we want to copy the terminating null too\n\tmemmove(\n\t\ts->u8tou16 + start,\n\t\ts->u8tou16 + end,\n\t\t(s->len - end + 1) * sizeof (size_t));\n\tmemmove(\n\t\ts->u16tou8 + start16,\n\t\ts->u16tou8 + end16,\n\t\t(s->u16len - end16 + 1) * sizeof (size_t));\n\n\t// update the conversion tables\n\t// note the use of <= to include the null terminator\n\tfor (i = 0; i <= (s->len - end); i++)\n\t\ts->u8tou16[start + i] -= count16;\n\tfor (i = 0; i <= (s->u16len - end16); i++)\n\t\ts->u16tou8[start16 + i] -= count;\n\n\t// null-terminate the string\n\ts->s[start + (s->len - end)] = 0;\n\ts->u16[start16 + (s->u16len - end16)] = 0;\n\n\t// fix up attributes\n\tuiprivAttrListRemoveCharacters(s->attrs, start, end);\n\n\t// and finally resize\n\tresize(s, s->len - count, s->u16len - count16);\n}\n\nvoid uiAttributedStringSetAttribute(uiAttributedString *s, uiAttribute *a, size_t start, size_t end)\n{\n\tuiprivAttrListInsertAttribute(s->attrs, a, start, end);\n}\n\n// LONGTERM introduce an iterator object instead?\nvoid uiAttributedStringForEachAttribute(const uiAttributedString *s, uiAttributedStringForEachAttributeFunc f, void *data)\n{\n\tuiprivAttrListForEach(s->attrs, s, f, data);\n}\n\n// TODO figure out if we should count the grapheme past the end\nsize_t uiAttributedStringNumGraphemes(uiAttributedString *s)\n{\n\trecomputeGraphemes(s);\n\treturn s->graphemes->len;\n}\n\nsize_t uiAttributedStringByteIndexToGrapheme(uiAttributedString *s, size_t pos)\n{\n\trecomputeGraphemes(s);\n\tif (uiprivGraphemesTakesUTF16())\n\t\tpos = s->u8tou16[pos];\n\treturn s->graphemes->pointsToGraphemes[pos];\n}\n\nsize_t uiAttributedStringGraphemeToByteIndex(uiAttributedString *s, size_t pos)\n{\n\trecomputeGraphemes(s);\n\tpos = s->graphemes->graphemesToPoints[pos];\n\tif (uiprivGraphemesTakesUTF16())\n\t\tpos = s->u16tou8[pos];\n\treturn pos;\n}\n\n// helpers for platform-specific code\n\nconst uint16_t *uiprivAttributedStringUTF16String(const uiAttributedString *s)\n{\n\treturn s->u16;\n}\n\nsize_t uiprivAttributedStringUTF16Len(const uiAttributedString *s)\n{\n\treturn s->u16len;\n}\n\n// TODO is this still needed given the below?\nsize_t uiprivAttributedStringUTF8ToUTF16(const uiAttributedString *s, size_t n)\n{\n\treturn s->u8tou16[n];\n}\n\nsize_t *uiprivAttributedStringCopyUTF8ToUTF16Table(const uiAttributedString *s, size_t *n)\n{\n\tsize_t *out;\n\tsize_t nbytes;\n\n\tnbytes = (s->len + 1) * sizeof (size_t);\n\t*n = s->len;\n\tout = (size_t *) uiprivAlloc(nbytes, \"size_t[] (uiAttributedString)\");\n\tmemmove(out, s->u8tou16, nbytes);\n\treturn out;\n}\n\nsize_t *uiprivAttributedStringCopyUTF16ToUTF8Table(const uiAttributedString *s, size_t *n)\n{\n\tsize_t *out;\n\tsize_t nbytes;\n\n\tnbytes = (s->u16len + 1) * sizeof (size_t);\n\t*n = s->u16len;\n\tout = (size_t *) uiprivAlloc(nbytes, \"size_t[] (uiAttributedString)\");\n\tmemmove(out, s->u16tou8, nbytes);\n\treturn out;\n}\n"
  },
  {
    "path": "common/attrstr.h",
    "content": "// 19 february 2018\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n// attribute.c\nextern uiAttribute *uiprivAttributeRetain(uiAttribute *a);\nextern void uiprivAttributeRelease(uiAttribute *a);\nextern int uiprivAttributeEqual(const uiAttribute *a, const uiAttribute *b);\n\n// opentype.c\nextern int uiprivOpenTypeFeaturesEqual(const uiOpenTypeFeatures *a, const uiOpenTypeFeatures *b);\n\n// attrlist.c\ntypedef struct uiprivAttrList uiprivAttrList;\nextern uiprivAttrList *uiprivNewAttrList(void);\nextern void uiprivFreeAttrList(uiprivAttrList *alist);\nextern void uiprivAttrListInsertAttribute(uiprivAttrList *alist, uiAttribute *val, size_t start, size_t end);\nextern void uiprivAttrListInsertCharactersUnattributed(uiprivAttrList *alist, size_t start, size_t count);\nextern void uiprivAttrListInsertCharactersExtendingAttributes(uiprivAttrList *alist, size_t start, size_t count);\nextern void uiprivAttrListRemoveAttribute(uiprivAttrList *alist, uiAttributeType type, size_t start, size_t end);\nextern void uiprivAttrListRemoveAttributes(uiprivAttrList *alist, size_t start, size_t end);\nextern void uiprivAttrListRemoveCharacters(uiprivAttrList *alist, size_t start, size_t end);\nextern void uiprivAttrListForEach(const uiprivAttrList *alist, const uiAttributedString *s, uiAttributedStringForEachAttributeFunc f, void *data);\n\n// attrstr.c\nextern const uint16_t *uiprivAttributedStringUTF16String(const uiAttributedString *s);\nextern size_t uiprivAttributedStringUTF16Len(const uiAttributedString *s);\nextern size_t uiprivAttributedStringUTF8ToUTF16(const uiAttributedString *s, size_t n);\nextern size_t *uiprivAttributedStringCopyUTF8ToUTF16Table(const uiAttributedString *s, size_t *n);\nextern size_t *uiprivAttributedStringCopyUTF16ToUTF8Table(const uiAttributedString *s, size_t *n);\n\n// per-OS graphemes.c/graphemes.cpp/graphemes.m/etc.\ntypedef struct uiprivGraphemes uiprivGraphemes;\nstruct uiprivGraphemes {\n\tsize_t len;\n\tsize_t *pointsToGraphemes;\n\tsize_t *graphemesToPoints;\n};\nextern int uiprivGraphemesTakesUTF16(void);\nextern uiprivGraphemes *uiprivNewGraphemes(void *s, size_t len);\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "common/control.c",
    "content": "// 26 may 2015\n#include \"../ui.h\"\n#include \"uipriv.h\"\n\nvoid uiControlDestroy(uiControl *c)\n{\n\t(*(c->Destroy))(c);\n}\n\nuintptr_t uiControlHandle(uiControl *c)\n{\n\treturn (*(c->Handle))(c);\n}\n\nuiControl *uiControlParent(uiControl *c)\n{\n\treturn (*(c->Parent))(c);\n}\n\nvoid uiControlSetParent(uiControl *c, uiControl *parent)\n{\n\t(*(c->SetParent))(c, parent);\n}\n\nint uiControlToplevel(uiControl *c)\n{\n\treturn (*(c->Toplevel))(c);\n}\n\nint uiControlVisible(uiControl *c)\n{\n\treturn (*(c->Visible))(c);\n}\n\nvoid uiControlShow(uiControl *c)\n{\n\t(*(c->Show))(c);\n}\n\nvoid uiControlHide(uiControl *c)\n{\n\t(*(c->Hide))(c);\n}\n\nint uiControlEnabled(uiControl *c)\n{\n\treturn (*(c->Enabled))(c);\n}\n\nvoid uiControlEnable(uiControl *c)\n{\n\t(*(c->Enable))(c);\n}\n\nvoid uiControlDisable(uiControl *c)\n{\n\t(*(c->Disable))(c);\n}\n\n#define uiprivControlSignature 0x7569436F\n\nuiControl *uiAllocControl(size_t size, uint32_t OSsig, uint32_t typesig, const char *typenamestr)\n{\n\tuiControl *c;\n\n\tc = (uiControl *) uiprivAlloc(size, typenamestr);\n\tc->Signature = uiprivControlSignature;\n\tc->OSSignature = OSsig;\n\tc->TypeSignature = typesig;\n\treturn c;\n}\n\nvoid uiFreeControl(uiControl *c)\n{\n\tif (uiControlParent(c) != NULL)\n\t\tuiprivUserBug(\"You cannot destroy a uiControl while it still has a parent. (control: %p)\", c);\n\tuiprivFree(c);\n}\n\nvoid uiControlVerifySetParent(uiControl *c, uiControl *parent)\n{\n\tuiControl *curParent;\n\n\tif (uiControlToplevel(c))\n\t\tuiprivUserBug(\"You cannot give a toplevel uiControl a parent. (control: %p)\", c);\n\tcurParent = uiControlParent(c);\n\tif (parent != NULL && curParent != NULL)\n\t\tuiprivUserBug(\"You cannot give a uiControl a parent while it already has one. (control: %p; current parent: %p; new parent: %p)\", c, curParent, parent);\n\tif (parent == NULL && curParent == NULL)\n\t\tuiprivImplBug(\"attempt to double unparent uiControl %p\", c);\n}\n\nint uiControlEnabledToUser(uiControl *c)\n{\n\twhile (c != NULL) {\n\t\tif (!uiControlEnabled(c))\n\t\t\treturn 0;\n\t\tc = uiControlParent(c);\n\t}\n\treturn 1;\n}\n"
  },
  {
    "path": "common/controlsigs.h",
    "content": "// 24 april 2016\n\n// LONGTERM if I don't decide to remove these outright, should they be renamed uiprivTypeNameSignature? these aren't real symbols, so...\n\n#define uiAreaSignature 0x41726561\n#define uiBoxSignature 0x426F784C\n#define uiButtonSignature 0x42746F6E\n#define uiCheckboxSignature 0x43686B62\n#define uiColorButtonSignature 0x436F6C42\n#define uiComboboxSignature 0x436F6D62\n#define uiDateTimePickerSignature 0x44545069\n#define uiEditableComboboxSignature 0x45644362\n#define uiEntrySignature 0x456E7472\n#define uiFontButtonSignature 0x466F6E42\n#define uiFormSignature 0x466F726D\n#define uiGridSignature 0x47726964\n#define uiGroupSignature 0x47727062\n#define uiLabelSignature 0x4C61626C\n#define uiMultilineEntrySignature 0x4D6C6E45\n#define uiProgressBarSignature 0x50426172\n#define uiRadioButtonsSignature 0x5264696F\n#define uiSeparatorSignature 0x53657061\n#define uiSliderSignature 0x536C6964\n#define uiSpinboxSignature 0x5370696E\n#define uiTabSignature 0x54616273\n#define uiTableSignature 0x5461626C\n#define uiWindowSignature 0x57696E64\n"
  },
  {
    "path": "common/debug.c",
    "content": "// 13 may 2016\n#include \"../ui.h\"\n#include \"uipriv.h\"\n\nvoid uiprivDoImplBug(const char *file, const char *line, const char *func, const char *format, ...)\n{\n\tva_list ap;\n\n\tva_start(ap, format);\n\tuiprivRealBug(file, line, func, \"POSSIBLE IMPLEMENTATION BUG; CONTACT ANDLABS:\\n\", format, ap);\n\tva_end(ap);\n}\n\nvoid uiprivDoUserBug(const char *file, const char *line, const char *func, const char *format, ...)\n{\n\tva_list ap;\n\n\tva_start(ap, format);\n\tuiprivRealBug(file, line, func, \"You have a bug: \", format, ap);\n\tva_end(ap);\n}\n"
  },
  {
    "path": "common/matrix.c",
    "content": "// 11 october 2015\n#include <math.h>\n#include \"../ui.h\"\n#include \"uipriv.h\"\n\nvoid uiDrawMatrixSetIdentity(uiDrawMatrix *m)\n{\n\tm->M11 = 1;\n\tm->M12 = 0;\n\tm->M21 = 0;\n\tm->M22 = 1;\n\tm->M31 = 0;\n\tm->M32 = 0;\n}\n\n// The rest of this file provides basic utilities in case the platform doesn't provide any of its own for these tasks.\n// Keep these as minimal as possible. They should generally not call other fallbacks.\n\n// see https://msdn.microsoft.com/en-us/library/windows/desktop/ff684171%28v=vs.85%29.aspx#skew_transform\n// TODO see if there's a way we can avoid the multiplication\nvoid uiprivFallbackSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount)\n{\n\tuiDrawMatrix n;\n\n\tuiDrawMatrixSetIdentity(&n);\n\t// TODO explain this\n\tn.M12 = tan(yamount);\n\tn.M21 = tan(xamount);\n\tn.M31 = -y * tan(xamount);\n\tn.M32 = -x * tan(yamount);\n\tuiDrawMatrixMultiply(m, &n);\n}\n\nvoid uiprivScaleCenter(double xCenter, double yCenter, double *x, double *y)\n{\n\t*x = xCenter - (*x * xCenter);\n\t*y = yCenter - (*y * yCenter);\n}\n\n// the basic algorithm is from cairo\n// 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\nvoid uiprivFallbackTransformSize(uiDrawMatrix *m, double *x, double *y)\n{\n\tuiDrawMatrix m2;\n\n\tm2 = *m;\n\tm2.M31 = 0;\n\tm2.M32 = 0;\n\tuiDrawMatrixTransformPoint(&m2, x, y);\n}\n"
  },
  {
    "path": "common/meson.build",
    "content": "# 23 march 2019\n\nlibui_sources += [\n\t'common/attribute.c',\n\t'common/attrlist.c',\n\t'common/attrstr.c',\n\t'common/areaevents.c',\n\t'common/control.c',\n\t'common/debug.c',\n\t'common/matrix.c',\n\t'common/opentype.c',\n\t'common/shouldquit.c',\n\t'common/tablemodel.c',\n\t'common/tablevalue.c',\n\t'common/userbugs.c',\n\t'common/utf.c',\n]\n"
  },
  {
    "path": "common/opentype.c",
    "content": "// 25 february 2018\n#include <stdlib.h>\n#include \"../ui.h\"\n#include \"uipriv.h\"\n#include \"attrstr.h\"\n\nstruct feature {\n\tchar a;\n\tchar b;\n\tchar c;\n\tchar d;\n\tuint32_t value;\n};\n\nstruct uiOpenTypeFeatures {\n\tstruct feature *data;\n\tsize_t len;\n\tsize_t cap;\n};\n\n#define bytecount(n) ((n) * sizeof (struct feature))\n\nuiOpenTypeFeatures *uiNewOpenTypeFeatures(void)\n{\n\tuiOpenTypeFeatures *otf;\n\n\totf = uiprivNew(uiOpenTypeFeatures);\n\totf->cap = 16;\n\totf->data = (struct feature *) uiprivAlloc(bytecount(otf->cap), \"struct feature[]\");\n\totf->len = 0;\n\treturn otf;\n}\n\nvoid uiFreeOpenTypeFeatures(uiOpenTypeFeatures *otf)\n{\n\tuiprivFree(otf->data);\n\tuiprivFree(otf);\n}\n\nuiOpenTypeFeatures *uiOpenTypeFeaturesClone(const uiOpenTypeFeatures *otf)\n{\n\tuiOpenTypeFeatures *ret;\n\n\tret = uiprivNew(uiOpenTypeFeatures);\n\tret->len = otf->len;\n\tret->cap = otf->cap;\n\tret->data = (struct feature *) uiprivAlloc(bytecount(ret->cap), \"struct feature[]\");\n\tmemset(ret->data, 0, bytecount(ret->cap));\n\tmemmove(ret->data, otf->data, bytecount(ret->len));\n\treturn ret;\n}\n\n#define intdiff(a, b) (((int) (a)) - ((int) (b)))\n\nstatic int featurecmp(const void *a, const void *b)\n{\n\tconst struct feature *f = (const struct feature *) a;\n\tconst struct feature *g = (const struct feature *) b;\n\n\tif (f->a != g->a)\n\t\treturn intdiff(f->a, g->a);\n\tif (f->b != g->b)\n\t\treturn intdiff(f->b, g->b);\n\tif (f->c != g->c)\n\t\treturn intdiff(f->c, g->c);\n\treturn intdiff(f->d, g->d);\n}\n\nstatic struct feature mkkey(char a, char b, char c, char d)\n{\n\tstruct feature f;\n\n\tf.a = a;\n\tf.b = b;\n\tf.c = c;\n\tf.d = d;\n\treturn f;\n}\n\n#define find(pkey, otf) bsearch(pkey, otf->data, otf->len, sizeof (struct feature), featurecmp)\n\nvoid uiOpenTypeFeaturesAdd(uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value)\n{\n\tstruct feature *f;\n\tstruct feature key;\n\n\t// replace existing value if any\n\tkey = mkkey(a, b, c, d);\n\tf = (struct feature *) find(&key, otf);\n\tif (f != NULL) {\n\t\tf->value = value;\n\t\treturn;\n\t}\n\n\t// if we got here, the tag is new\n\tif (otf->len == otf->cap) {\n\t\totf->cap *= 2;\n\t\totf->data = (struct feature *) uiprivRealloc(otf->data, bytecount(otf->cap), \"struct feature[]\");\n\t}\n\tf = otf->data + otf->len;\n\tf->a = a;\n\tf->b = b;\n\tf->c = c;\n\tf->d = d;\n\tf->value = value;\n\t// LONGTERM qsort here is overkill\n\totf->len++;\n\tqsort(otf->data, otf->len, sizeof (struct feature), featurecmp);\n}\n\nvoid uiOpenTypeFeaturesRemove(uiOpenTypeFeatures *otf, char a, char b, char c, char d)\n{\n\tstruct feature *f;\n\tstruct feature key;\n\tptrdiff_t index;\n\tsize_t count;\n\n\tkey = mkkey(a, b, c, d);\n\tf = (struct feature *) find(&key, otf);\n\tif (f == NULL)\n\t\treturn;\n\n\tindex = f - otf->data;\n\tcount = otf->len - index - 1;\n\tmemmove(f + 1, f, bytecount(count));\n\totf->len--;\n}\n\nint uiOpenTypeFeaturesGet(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t *value)\n{\n\tconst struct feature *f;\n\tstruct feature key;\n\n\tkey = mkkey(a, b, c, d);\n\tf = (const struct feature *) find(&key, otf);\n\tif (f == NULL)\n\t\treturn 0;\n\t*value = f->value;\n\treturn 1;\n}\n\nvoid uiOpenTypeFeaturesForEach(const uiOpenTypeFeatures *otf, uiOpenTypeFeaturesForEachFunc f, void *data)\n{\n\tsize_t n;\n\tconst struct feature *p;\n\tuiForEach ret;\n\n\tp = otf->data;\n\tfor (n = 0; n < otf->len; n++) {\n\t\tret = (*f)(otf, p->a, p->b, p->c, p->d, p->value, data);\n\t\t// TODO for all: require exact match?\n\t\tif (ret == uiForEachStop)\n\t\t\treturn;\n\t\tp++;\n\t}\n}\n\nint uiprivOpenTypeFeaturesEqual(const uiOpenTypeFeatures *a, const uiOpenTypeFeatures *b)\n{\n\tif (a == b)\n\t\treturn 1;\n\tif (a->len != b->len)\n\t\treturn 0;\n\treturn memcmp(a->data, b->data, bytecount(a->len)) == 0;\n}\n"
  },
  {
    "path": "common/shouldquit.c",
    "content": "// 9 may 2015\n#include \"../ui.h\"\n#include \"uipriv.h\"\n\nstatic int defaultOnShouldQuit(void *data)\n{\n\treturn 0;\n}\n\nstatic int (*onShouldQuit)(void *) = defaultOnShouldQuit;\nstatic void *onShouldQuitData = NULL;\n\nvoid uiOnShouldQuit(int (*f)(void *), void *data)\n{\n\tonShouldQuit = f;\n\tonShouldQuitData = data;\n}\n\nint uiprivShouldQuit(void)\n{\n\treturn (*onShouldQuit)(onShouldQuitData);\n}\n"
  },
  {
    "path": "common/table.h",
    "content": "// 23 june 2018\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n// tablemodel.c\nextern uiTableModelHandler *uiprivTableModelHandler(uiTableModel *m);\nextern int uiprivTableModelNumColumns(uiTableModel *m);\nextern uiTableValueType uiprivTableModelColumnType(uiTableModel *m, int column);\nextern int uiprivTableModelNumRows(uiTableModel *m);\nextern uiTableValue *uiprivTableModelCellValue(uiTableModel *m, int row, int column);\nextern void uiprivTableModelSetCellValue(uiTableModel *m, int row, int column, const uiTableValue *value);\nextern const uiTableTextColumnOptionalParams uiprivDefaultTextColumnOptionalParams;\nextern int uiprivTableModelCellEditable(uiTableModel *m, int row, int column);\nextern int uiprivTableModelColorIfProvided(uiTableModel *m, int row, int column, double *r, double *g, double *b, double *a);\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "common/tablemodel.c",
    "content": "// 23 june 2018\n#include \"../ui.h\"\n#include \"uipriv.h\"\n#include \"table.h\"\n\nint uiprivTableModelNumColumns(uiTableModel *m)\n{\n\tuiTableModelHandler *mh;\n\n\tmh = uiprivTableModelHandler(m);\n\treturn (*(mh->NumColumns))(mh, m);\n}\n\nuiTableValueType uiprivTableModelColumnType(uiTableModel *m, int column)\n{\n\tuiTableModelHandler *mh;\n\n\tmh = uiprivTableModelHandler(m);\n\treturn (*(mh->ColumnType))(mh, m, column);\n}\n\nint uiprivTableModelNumRows(uiTableModel *m)\n{\n\tuiTableModelHandler *mh;\n\n\tmh = uiprivTableModelHandler(m);\n\treturn (*(mh->NumRows))(mh, m);\n}\n\nuiTableValue *uiprivTableModelCellValue(uiTableModel *m, int row, int column)\n{\n\tuiTableModelHandler *mh;\n\n\tmh = uiprivTableModelHandler(m);\n\treturn (*(mh->CellValue))(mh, m, row, column);\n}\n\nvoid uiprivTableModelSetCellValue(uiTableModel *m, int row, int column, const uiTableValue *value)\n{\n\tuiTableModelHandler *mh;\n\n\tmh = uiprivTableModelHandler(m);\n\t(*(mh->SetCellValue))(mh, m, row, column, value);\n}\n\nconst uiTableTextColumnOptionalParams uiprivDefaultTextColumnOptionalParams = {\n\t.ColorModelColumn = -1,\n};\n\nint uiprivTableModelCellEditable(uiTableModel *m, int row, int column)\n{\n\tuiTableValue *value;\n\tint editable;\n\n\tswitch (column) {\n\tcase uiTableModelColumnNeverEditable:\n\t\treturn 0;\n\tcase uiTableModelColumnAlwaysEditable:\n\t\treturn 1;\n\t}\n\tvalue = uiprivTableModelCellValue(m, row, column);\n\teditable = uiTableValueInt(value);\n\tuiFreeTableValue(value);\n\treturn editable;\n}\n\nint uiprivTableModelColorIfProvided(uiTableModel *m, int row, int column, double *r, double *g, double *b, double *a)\n{\n\tuiTableValue *value;\n\n\tif (column == -1)\n\t\treturn 0;\n\tvalue = uiprivTableModelCellValue(m, row, column);\n\tif (value == NULL)\n\t\treturn 0;\n\tuiTableValueColor(value, r, g, b, a);\n\tuiFreeTableValue(value);\n\treturn 1;\n}\n"
  },
  {
    "path": "common/tablevalue.c",
    "content": "// 3 june 2018\n#include \"../ui.h\"\n#include \"uipriv.h\"\n#include \"table.h\"\n\nstruct uiTableValue {\n\tuiTableValueType type;\n\tunion {\n\t\tchar *str;\n\t\tuiImage *img;\n\t\tint i;\n\t\tstruct {\n\t\t\tdouble r;\n\t\t\tdouble g;\n\t\t\tdouble b;\n\t\t\tdouble a;\n\t\t} color;\n\t} u;\n};\n\nstatic uiTableValue *newTableValue(uiTableValueType type)\n{\n\tuiTableValue *v;\n\n\tv = uiprivNew(uiTableValue);\n\tv->type = type;\n\treturn v;\n}\n\nvoid uiFreeTableValue(uiTableValue *v)\n{\n\tswitch (v->type) {\n\tcase uiTableValueTypeString:\n\t\tuiprivFree(v->u.str);\n\t\tbreak;\n\t}\n\tuiprivFree(v);\n}\n\nuiTableValueType uiTableValueGetType(const uiTableValue *v)\n{\n\treturn v->type;\n}\n\nuiTableValue *uiNewTableValueString(const char *str)\n{\n\tuiTableValue *v;\n\n\tv = newTableValue(uiTableValueTypeString);\n\tv->u.str = (char *) uiprivAlloc((strlen(str) + 1) * sizeof (char), \"char[] (uiTableValue)\");\n\tstrcpy(v->u.str, str);\n\treturn v;\n}\n\nconst char *uiTableValueString(const uiTableValue *v)\n{\n\treturn v->u.str;\n}\n\nuiTableValue *uiNewTableValueImage(uiImage *img)\n{\n\tuiTableValue *v;\n\n\tv = newTableValue(uiTableValueTypeImage);\n\tv->u.img = img;\n\treturn v;\n}\n\nuiImage *uiTableValueImage(const uiTableValue *v)\n{\n\treturn v->u.img;\n}\n\nuiTableValue *uiNewTableValueInt(int i)\n{\n\tuiTableValue *v;\n\n\tv = newTableValue(uiTableValueTypeInt);\n\tv->u.i = i;\n\treturn v;\n}\n\nint uiTableValueInt(const uiTableValue *v)\n{\n\treturn v->u.i;\n}\n\nuiTableValue *uiNewTableValueColor(double r, double g, double b, double a)\n{\n\tuiTableValue *v;\n\n\tv = newTableValue(uiTableValueTypeColor);\n\tv->u.color.r = r;\n\tv->u.color.g = g;\n\tv->u.color.b = b;\n\tv->u.color.a = a;\n\treturn v;\n}\n\nvoid uiTableValueColor(const uiTableValue *v, double *r, double *g, double *b, double *a)\n{\n\t*r = v->u.color.r;\n\t*g = v->u.color.g;\n\t*b = v->u.color.b;\n\t*a = v->u.color.a;\n}\n"
  },
  {
    "path": "common/uipriv.h",
    "content": "// 6 april 2015\n// 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\n#include <stdarg.h>\n#include <string.h>\n#include \"controlsigs.h\"\n#include \"utf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n// OS-specific init.* or main.* files\nextern uiInitOptions uiprivOptions;\n\n// OS-specific alloc.* files\nextern void *uiprivAlloc(size_t, const char *);\n#define uiprivNew(T) ((T *) uiprivAlloc(sizeof (T), #T))\nextern void *uiprivRealloc(void *, size_t, const char *);\nextern void uiprivFree(void *);\n\n// debug.c and OS-specific debug.* files\n// TODO get rid of this mess...\n// ugh, __func__ was only introduced in MSVC 2015...\n#ifdef _MSC_VER\n#define uiprivMacro__func__ __FUNCTION__\n#else\n#define uiprivMacro__func__ __func__\n#endif\nextern void uiprivRealBug(const char *file, const char *line, const char *func, const char *prefix, const char *format, va_list ap);\n#define uiprivMacro_ns2(s) #s\n#define uiprivMacro_ns(s) uiprivMacro_ns2(s)\nextern void uiprivDoImplBug(const char *file, const char *line, const char *func, const char *format, ...);\n#define uiprivImplBug(...) uiprivDoImplBug(__FILE__, uiprivMacro_ns(__LINE__), uiprivMacro__func__, __VA_ARGS__)\nextern void uiprivDoUserBug(const char *file, const char *line, const char *func, const char *format, ...);\n#define uiprivUserBug(...) uiprivDoUserBug(__FILE__, uiprivMacro_ns(__LINE__), uiprivMacro__func__, __VA_ARGS__)\n\n// shouldquit.c\nextern int uiprivShouldQuit(void);\n\n// areaevents.c\ntypedef struct uiprivClickCounter uiprivClickCounter;\n// you should call Reset() to zero-initialize a new instance\n// 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\nstruct uiprivClickCounter {\n\tint curButton;\n\tint rectX0;\n\tint rectY0;\n\tint rectX1;\n\tint rectY1;\n\tuintptr_t prevTime;\n\tint count;\n};\nextern int uiprivClickCounterClick(uiprivClickCounter *c, int button, int x, int y, uintptr_t time, uintptr_t maxTime, int32_t xdist, int32_t ydist);\nextern void uiprivClickCounterReset(uiprivClickCounter *);\nextern int uiprivFromScancode(uintptr_t, uiAreaKeyEvent *);\n\n// matrix.c\nextern void uiprivFallbackSkew(uiDrawMatrix *, double, double, double, double);\nextern void uiprivScaleCenter(double, double, double *, double *);\nextern void uiprivFallbackTransformSize(uiDrawMatrix *, double *, double *);\n\n// OS-specific text.* files\nextern int uiprivStricmp(const char *a, const char *b);\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "common/userbugs.c",
    "content": "// 22 may 2016\n#include \"../ui.h\"\n#include \"uipriv.h\"\n\nvoid uiUserBugCannotSetParentOnToplevel(const char *type)\n{\n\tuiprivUserBug(\"You cannot make a %s a child of another uiControl,\", type);\n}\n"
  },
  {
    "path": "common/utf.c",
    "content": "// utf by pietro gagliardi (andlabs) — https://github.com/andlabs/utf/\n// 10 november 2016\n// function names have been altered to avoid namespace collisions in libui static builds (see utf.h)\n#include \"utf.h\"\n\n// this code imitates Go's unicode/utf8 and unicode/utf16\n// 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)\n// it is also an imitation so we can license it under looser terms than the Go source\n#define badrune 0xFFFD\n\n// encoded must be at most 4 bytes\n// TODO clean this code up somehow\nsize_t uiprivUTF8EncodeRune(uint32_t rune, char *encoded)\n{\n\tuint8_t b, c, d, e;\n\tsize_t n;\n\n\t// not in the valid range for Unicode\n\tif (rune > 0x10FFFF)\n\t\trune = badrune;\n\t// surrogate runes cannot be encoded\n\tif (rune >= 0xD800 && rune < 0xE000)\n\t\trune = badrune;\n\n\tif (rune < 0x80) {\t\t// ASCII bytes represent themselves\n\t\tb = (uint8_t) (rune & 0xFF);\n\t\tn = 1;\n\t\tgoto done;\n\t}\n\tif (rune < 0x800) {\t\t// two-byte encoding\n\t\tc = (uint8_t) (rune & 0x3F);\n\t\tc |= 0x80;\n\t\trune >>= 6;\n\t\tb = (uint8_t) (rune & 0x1F);\n\t\tb |= 0xC0;\n\t\tn = 2;\n\t\tgoto done;\n\t}\n\tif (rune < 0x10000) {\t// three-byte encoding\n\t\td = (uint8_t) (rune & 0x3F);\n\t\td |= 0x80;\n\t\trune >>= 6;\n\t\tc = (uint8_t) (rune & 0x3F);\n\t\tc |= 0x80;\n\t\trune >>= 6;\n\t\tb = (uint8_t) (rune & 0x0F);\n\t\tb |= 0xE0;\n\t\tn = 3;\n\t\tgoto done;\n\t}\n\t// otherwise use a four-byte encoding\n\te = (uint8_t) (rune & 0x3F);\n\te |= 0x80;\n\trune >>= 6;\n\td = (uint8_t) (rune & 0x3F);\n\td |= 0x80;\n\trune >>= 6;\n\tc = (uint8_t) (rune & 0x3F);\n\tc |= 0x80;\n\trune >>= 6;\n\tb = (uint8_t) (rune & 0x07);\n\tb |= 0xF0;\n\tn = 4;\n\ndone:\n\tencoded[0] = b;\n\tif (n > 1)\n\t\tencoded[1] = c;\n\tif (n > 2)\n\t\tencoded[2] = d;\n\tif (n > 3)\n\t\tencoded[3] = e;\n\treturn n;\n}\n\nconst char *uiprivUTF8DecodeRune(const char *s, size_t nElem, uint32_t *rune)\n{\n\tuint8_t b, c;\n\tuint8_t lowestAllowed, highestAllowed;\n\tsize_t i, expected;\n\tint bad;\n\n\tb = (uint8_t) (*s);\n\tif (b < 0x80) {\t\t// ASCII bytes represent themselves\n\t\t*rune = b;\n\t\ts++;\n\t\treturn s;\n\t}\n\t// 0xC0 and 0xC1 cover 2-byte overlong equivalents\n\t// 0xF5 to 0xFD cover values > 0x10FFFF\n\t// 0xFE and 0xFF were never defined (always illegal)\n\tif (b < 0xC2 || b > 0xF4) {\t\t// invalid\n\t\t*rune = badrune;\n\t\ts++;\n\t\treturn s;\n\t}\n\n\t// this determines the range of allowed first continuation bytes\n\tlowestAllowed = 0x80;\n\thighestAllowed = 0xBF;\n\tswitch (b) {\n\tcase 0xE0:\n\t\t// disallow 3-byte overlong equivalents\n\t\tlowestAllowed = 0xA0;\n\t\tbreak;\n\tcase 0xED:\n\t\t// disallow surrogate characters\n\t\thighestAllowed = 0x9F;\n\t\tbreak;\n\tcase 0xF0:\n\t\t// disallow 4-byte overlong equivalents\n\t\tlowestAllowed = 0x90;\n\t\tbreak;\n\tcase 0xF4:\n\t\t// disallow values > 0x10FFFF\n\t\thighestAllowed = 0x8F;\n\t\tbreak;\n\t}\n\n\t// and this determines how many continuation bytes are expected\n\texpected = 1;\n\tif (b >= 0xE0)\n\t\texpected++;\n\tif (b >= 0xF0)\n\t\texpected++;\n\tif (nElem != 0) {\t\t\t\t// are there enough bytes?\n\t\tnElem--;\n\t\tif (nElem < expected) {\t// nope\n\t\t\t*rune = badrune;\n\t\t\ts++;\n\t\t\treturn s;\n\t\t}\n\t}\n\n\t// ensure that everything is correct\n\t// if not, **only** consume the initial byte\n\tbad = 0;\n\tfor (i = 0; i < expected; i++) {\n\t\tc = (uint8_t) (s[1 + i]);\n\t\tif (c < lowestAllowed || c > highestAllowed) {\n\t\t\tbad = 1;\n\t\t\tbreak;\n\t\t}\n\t\t// the old lowestAllowed and highestAllowed is only for the first continuation byte\n\t\tlowestAllowed = 0x80;\n\t\thighestAllowed = 0xBF;\n\t}\n\tif (bad) {\n\t\t*rune = badrune;\n\t\ts++;\n\t\treturn s;\n\t}\n\n\t// now do the topmost bits\n\tif (b < 0xE0)\n\t\t*rune = b & 0x1F;\n\telse if (b < 0xF0)\n\t\t*rune = b & 0x0F;\n\telse\n\t\t*rune = b & 0x07;\n\ts++;\t\t// we can finally move on\n\n\t// now do the continuation bytes\n\tfor (; expected; expected--) {\n\t\tc = (uint8_t) (*s);\n\t\ts++;\n\t\tc &= 0x3F;\t\t// strip continuation bits\n\t\t*rune <<= 6;\n\t\t*rune |= c;\n\t}\n\n\treturn s;\n}\n\n// encoded must have at most 2 elements\nsize_t uiprivUTF16EncodeRune(uint32_t rune, uint16_t *encoded)\n{\n\tuint16_t low, high;\n\n\t// not in the valid range for Unicode\n\tif (rune > 0x10FFFF)\n\t\trune = badrune;\n\t// surrogate runes cannot be encoded\n\tif (rune >= 0xD800 && rune < 0xE000)\n\t\trune = badrune;\n\n\tif (rune < 0x10000) {\n\t\tencoded[0] = (uint16_t) rune;\n\t\treturn 1;\n\t}\n\n\trune -= 0x10000;\n\tlow = (uint16_t) (rune & 0x3FF);\n\trune >>= 10;\n\thigh = (uint16_t) (rune & 0x3FF);\n\tencoded[0] = high | 0xD800;\n\tencoded[1] = low | 0xDC00;\n\treturn 2;\n}\n\n// TODO see if this can be cleaned up somehow\nconst uint16_t *uiprivUTF16DecodeRune(const uint16_t *s, size_t nElem, uint32_t *rune)\n{\n\tuint16_t high, low;\n\n\tif (*s < 0xD800 || *s >= 0xE000) {\n\t\t// self-representing character\n\t\t*rune = *s;\n\t\ts++;\n\t\treturn s;\n\t}\n\tif (*s >= 0xDC00) {\n\t\t// out-of-order surrogates\n\t\t*rune = badrune;\n\t\ts++;\n\t\treturn s;\n\t}\n\tif (nElem == 1) {\t\t// not enough elements\n\t\t*rune = badrune;\n\t\ts++;\n\t\treturn s;\n\t}\n\thigh = *s;\n\thigh &= 0x3FF;\n\tif (s[1] < 0xDC00 || s[1] >= 0xE000) {\n\t\t// bad surrogate pair\n\t\t*rune = badrune;\n\t\ts++;\n\t\treturn s;\n\t}\n\ts++;\n\tlow = *s;\n\ts++;\n\tlow &= 0x3FF;\n\t*rune = high;\n\t*rune <<= 10;\n\t*rune |= low;\n\t*rune += 0x10000;\n\treturn s;\n}\n\n// TODO find a way to reduce the code in all of these somehow\n// TODO find a way to remove u as well\nsize_t uiprivUTF8RuneCount(const char *s, size_t nElem)\n{\n\tsize_t len;\n\tuint32_t rune;\n\n\tif (nElem != 0) {\n\t\tconst char *t, *u;\n\n\t\tlen = 0;\n\t\tt = s;\n\t\twhile (nElem != 0) {\n\t\t\tu = uiprivUTF8DecodeRune(t, nElem, &rune);\n\t\t\tlen++;\n\t\t\tnElem -= u - t;\n\t\t\tt = u;\n\t\t}\n\t\treturn len;\n\t}\n\tlen = 0;\n\twhile (*s) {\n\t\ts = uiprivUTF8DecodeRune(s, nElem, &rune);\n\t\tlen++;\n\t}\n\treturn len;\n}\n\nsize_t uiprivUTF8UTF16Count(const char *s, size_t nElem)\n{\n\tsize_t len;\n\tuint32_t rune;\n\tuint16_t encoded[2];\n\n\tif (nElem != 0) {\n\t\tconst char *t, *u;\n\n\t\tlen = 0;\n\t\tt = s;\n\t\twhile (nElem != 0) {\n\t\t\tu = uiprivUTF8DecodeRune(t, nElem, &rune);\n\t\t\tlen += uiprivUTF16EncodeRune(rune, encoded);\n\t\t\tnElem -= u - t;\n\t\t\tt = u;\n\t\t}\n\t\treturn len;\n\t}\n\tlen = 0;\n\twhile (*s) {\n\t\ts = uiprivUTF8DecodeRune(s, nElem, &rune);\n\t\tlen += uiprivUTF16EncodeRune(rune, encoded);\n\t}\n\treturn len;\n}\n\nsize_t uiprivUTF16RuneCount(const uint16_t *s, size_t nElem)\n{\n\tsize_t len;\n\tuint32_t rune;\n\n\tif (nElem != 0) {\n\t\tconst uint16_t *t, *u;\n\n\t\tlen = 0;\n\t\tt = s;\n\t\twhile (nElem != 0) {\n\t\t\tu = uiprivUTF16DecodeRune(t, nElem, &rune);\n\t\t\tlen++;\n\t\t\tnElem -= u - t;\n\t\t\tt = u;\n\t\t}\n\t\treturn len;\n\t}\n\tlen = 0;\n\twhile (*s) {\n\t\ts = uiprivUTF16DecodeRune(s, nElem, &rune);\n\t\tlen++;\n\t}\n\treturn len;\n}\n\nsize_t uiprivUTF16UTF8Count(const uint16_t *s, size_t nElem)\n{\n\tsize_t len;\n\tuint32_t rune;\n\tchar encoded[4];\n\n\tif (nElem != 0) {\n\t\tconst uint16_t *t, *u;\n\n\t\tlen = 0;\n\t\tt = s;\n\t\twhile (nElem != 0) {\n\t\t\tu = uiprivUTF16DecodeRune(t, nElem, &rune);\n\t\t\tlen += uiprivUTF8EncodeRune(rune, encoded);\n\t\t\tnElem -= u - t;\n\t\t\tt = u;\n\t\t}\n\t\treturn len;\n\t}\n\tlen = 0;\n\twhile (*s) {\n\t\ts = uiprivUTF16DecodeRune(s, nElem, &rune);\n\t\tlen += uiprivUTF8EncodeRune(rune, encoded);\n\t}\n\treturn len;\n}\n"
  },
  {
    "path": "common/utf.h",
    "content": "// utf by pietro gagliardi (andlabs) — https://github.com/andlabs/utf/\n// 10 november 2016\n\n// note the overridden names with uipriv at the beginning; this avoids potential symbol clashes when building libui as a static library\n// LONGTERM find a way to encode the name overrides directly into the utf library\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n// TODO (for utf itself as well) should this go outside the extern \"C\" block or not\n#include <stddef.h>\n#include <stdint.h>\n\n// if nElem == 0, assume the buffer has no upper limit and is '\\0' terminated\n// otherwise, assume buffer is NOT '\\0' terminated but is bounded by nElem *elements*\n\nextern size_t uiprivUTF8EncodeRune(uint32_t rune, char *encoded);\nextern const char *uiprivUTF8DecodeRune(const char *s, size_t nElem, uint32_t *rune);\nextern size_t uiprivUTF16EncodeRune(uint32_t rune, uint16_t *encoded);\nextern const uint16_t *uiprivUTF16DecodeRune(const uint16_t *s, size_t nElem, uint32_t *rune);\n\nextern size_t uiprivUTF8RuneCount(const char *s, size_t nElem);\nextern size_t uiprivUTF8UTF16Count(const char *s, size_t nElem);\nextern size_t uiprivUTF16RuneCount(const uint16_t *s, size_t nElem);\nextern size_t uiprivUTF16UTF8Count(const uint16_t *s, size_t nElem);\n\n#ifdef __cplusplus\n}\n\n// TODO sync this back to upstream (need copyright clearance first)\n\n// On Windows, wchar_t is equivalent to uint16_t, but C++ requires\n// wchar_t to be a completely distinct type. These overloads allow\n// passing wchar_t pointers directly into these functions from C++\n// on Windows. Otherwise, you'd need to cast to pass a wchar_t\n// pointer, WCHAR pointer, or equivalent to these functions.\n// \n// This does not apply to MSVC because the situation there is\n// slightly more complicated; see below.\n#if defined(_WIN32) && !defined(_MSC_VER)\n\ninline size_t uiprivUTF16EncodeRune(uint32_t rune, wchar_t *encoded)\n{\n\treturn uiprivUTF16EncodeRune(rune, reinterpret_cast<uint16_t *>(encoded));\n}\n\ninline const wchar_t *uiprivUTF16DecodeRune(const wchar_t *s, size_t nElem, uint32_t *rune)\n{\n\tconst uint16_t *ret;\n\n\tret = uiprivUTF16DecodeRune(reinterpret_cast<const uint16_t *>(s), nElem, rune);\n\treturn reinterpret_cast<const wchar_t *>(ret);\n}\n\ninline size_t uiprivUTF16RuneCount(const wchar_t *s, size_t nElem)\n{\n\treturn uiprivUTF16RuneCount(reinterpret_cast<const uint16_t *>(s), nElem);\n}\n\ninline size_t uiprivUTF16UTF8Count(const wchar_t *s, size_t nElem)\n{\n\treturn uiprivUTF16UTF8Count(reinterpret_cast<const uint16_t *>(s), nElem);\n}\n\n#endif\n\n// This is the same as the above, except that with MSVC, whether\n// wchar_t is a keyword or not is controlled by a compiler option!\n// (At least with gcc, this is not the case; thanks redi in\n// irc.freenode.net/#gcc.) We use __wchar_t to be independent of\n// the option; see https://blogs.msdn.microsoft.com/oldnewthing/20161201-00/?p=94836\n// (ironically posted one day after I initially wrote this code!).\n// TODO should defined(_WIN32) be used too?\n// TODO check this under /Wall\n// TODO are C-style casts enough? or will that fail in /Wall?\n// TODO same for UniChar/unichar on Mac? if both are unsigned then we have nothing to worry about\n#if defined(_MSC_VER)\n\ninline size_t uiprivUTF16EncodeRune(uint32_t rune, __wchar_t *encoded)\n{\n\treturn uiprivUTF16EncodeRune(rune, reinterpret_cast<uint16_t *>(encoded));\n}\n\ninline const __wchar_t *uiprivUTF16DecodeRune(const __wchar_t *s, size_t nElem, uint32_t *rune)\n{\n\tconst uint16_t *ret;\n\n\tret = uiprivUTF16DecodeRune(reinterpret_cast<const uint16_t *>(s), nElem, rune);\n\treturn reinterpret_cast<const __wchar_t *>(ret);\n}\n\ninline size_t uiprivUTF16RuneCount(const __wchar_t *s, size_t nElem)\n{\n\treturn uiprivUTF16RuneCount(reinterpret_cast<const uint16_t *>(s), nElem);\n}\n\ninline size_t uiprivUTF16UTF8Count(const __wchar_t *s, size_t nElem)\n{\n\treturn uiprivUTF16UTF8Count(reinterpret_cast<const uint16_t *>(s), nElem);\n}\n\n#endif\n\n#endif\n"
  },
  {
    "path": "darwin/OLD_table.m",
    "content": "// 21 june 2016\n#import \"uipriv_darwin.h\"\n\n// TODOs\n// - header cell seems off\n// - background color shows up for a line or two below selection\n// - editable NSTextFields have no intrinsic width\n// - is the Y position of checkbox cells correct?\n\n@implementation tablePart\n\n- (NSView *)mkView:(uiTableModel *)m row:(int)row\n{\n\t// if stretchy, don't hug, otherwise hug forcibly\n\tif (self.expand)\n\t\t[view setContentHuggingPriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal];\n\telse\n\t\t[view setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal];\n}\n\n@end\n\nuiTableColumn *uiTableAppendColumn(uiTable *t, const char *name)\n{\n\tuiTableColumn *c;\n\n\tc = uiprivNew(uiTableColumn);\n\tc->c = [[tableColumn alloc] initWithIdentifier:@\"\"];\n\tc->c.libui_col = c;\n\t// via Interface Builder\n\t[c->c setResizingMask:(NSTableColumnAutoresizingMask | NSTableColumnUserResizingMask)];\n\t// 10.10 adds -[NSTableColumn setTitle:]; before then we have to do this\n\t[[c->c headerCell] setStringValue:uiprivToNSString(name)];\n\t// TODO is this sufficient?\n\t[[c->c headerCell] setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]];\n\tc->parts = [NSMutableArray new];\n\t[t->tv addTableColumn:c->c];\n\treturn c;\n}\n"
  },
  {
    "path": "darwin/aat.m",
    "content": "// 14 february 2017\n#import \"uipriv_darwin.h\"\n#import \"attrstr.h\"\n\n// TODO explain the purpose of this file\n\nstatic void boolspec(uint32_t value, uint16_t type, uint16_t ifTrue, uint16_t ifFalse, uiprivAATBlock f)\n{\n\t// TODO are values other than 1 accepted for true by OpenType itself? (same for the rest of the file)\n\tif (value != 0) {\n\t\tf(type, ifTrue);\n\t\treturn;\n\t}\n\tf(type, ifFalse);\n}\n\n// TODO remove the need for this\n// TODO remove x8tox32()\n#define x8tox32(x) ((uint32_t) (((uint8_t) (x)) & 0xFF))\n#define mkTag(a, b, c, d)\t\t\\\n\t((x8tox32(a) << 24) |\t\\\n\t(x8tox32(b) << 16) |\t\t\\\n\t(x8tox32(c) << 8) |\t\t\\\n\tx8tox32(d))\n\n// TODO double-check drawtext example to make sure all of these are used properly (I already screwed dlig up by putting clig twice instead)\nvoid uiprivOpenTypeToAAT(char a, char b, char c, char d, uint32_t value, uiprivAATBlock f)\n{\n\tswitch (mkTag(a, b, c, d)) {\n\tcase mkTag('l', 'i', 'g', 'a'):\n\t\tboolspec(value, kLigaturesType,\n\t\t\tkCommonLigaturesOnSelector,\n\t\t\tkCommonLigaturesOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('r', 'l', 'i', 'g'):\n\t\tboolspec(value, kLigaturesType,\n\t\t\tkRequiredLigaturesOnSelector,\n\t\t\tkRequiredLigaturesOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('d', 'l', 'i', 'g'):\n\t\tboolspec(value, kLigaturesType,\n\t\t\tkRareLigaturesOnSelector,\n\t\t\tkRareLigaturesOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('c', 'l', 'i', 'g'):\n\t\tboolspec(value, kLigaturesType,\n\t\t\tkContextualLigaturesOnSelector,\n\t\t\tkContextualLigaturesOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('h', 'l', 'i', 'g'):\n\t// 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\n\tcase mkTag('h', 'i', 's', 't'):\n\t\tboolspec(value, kLigaturesType,\n\t\t\tkHistoricalLigaturesOnSelector,\n\t\t\tkHistoricalLigaturesOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('u', 'n', 'i', 'c'):\n\t\t// TODO is this correct, or should we provide an else case?\n\t\tif (value != 0)\n\t\t\t// this is undocumented; it comes from Core Text's internal AAT-to-OpenType conversion table\n\t\t\tf(kLetterCaseType, 14);\n\t\tbreak;\n\n\t// TODO will the following handle all cases properly, or are elses going to be needed?\n\tcase mkTag('p', 'n', 'u', 'm'):\n\t\tif (value != 0)\n\t\t\tf(kNumberSpacingType, kProportionalNumbersSelector);\n\t\tbreak;\n\tcase mkTag('t', 'n', 'u', 'm'):\n\t\tif (value != 0)\n\t\t\tf(kNumberSpacingType, kMonospacedNumbersSelector);\n\t\tbreak;\n\n\t// TODO will the following handle all cases properly, or are elses going to be needed?\n\tcase mkTag('s', 'u', 'p', 's'):\n\t\tif (value != 0)\n\t\t\tf(kVerticalPositionType, kSuperiorsSelector);\n\t\tbreak;\n\tcase mkTag('s', 'u', 'b', 's'):\n\t\tif (value != 0)\n\t\t\tf(kVerticalPositionType, kInferiorsSelector);\n\t\tbreak;\n\tcase mkTag('o', 'r', 'd', 'n'):\n\t\tif (value != 0)\n\t\t\tf(kVerticalPositionType, kOrdinalsSelector);\n\t\tbreak;\n\tcase mkTag('s', 'i', 'n', 'f'):\n\t\tif (value != 0)\n\t\t\tf(kVerticalPositionType, kScientificInferiorsSelector);\n\t\tbreak;\n\n\t// TODO will the following handle all cases properly, or are elses going to be needed?\n\tcase mkTag('a', 'f', 'r', 'c'):\n\t\tif (value != 0)\n\t\t\tf(kFractionsType, kVerticalFractionsSelector);\n\t\tbreak;\n\tcase mkTag('f', 'r', 'a', 'c'):\n\t\tif (value != 0)\n\t\t\tf(kFractionsType, kDiagonalFractionsSelector);\n\t\tbreak;\n\n\tcase mkTag('z', 'e', 'r', 'o'):\n\t\tboolspec(value, kTypographicExtrasType,\n\t\t\tkSlashedZeroOnSelector,\n\t\t\tkSlashedZeroOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('m', 'g', 'r', 'k'):\n\t\tboolspec(value, kMathematicalExtrasType,\n\t\t\tkMathematicalGreekOnSelector,\n\t\t\tkMathematicalGreekOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('o', 'r', 'n', 'm'):\n\t\tf(kOrnamentSetsType, (uint16_t) value);\n\t\tbreak;\n\tcase mkTag('a', 'a', 'l', 't'):\n\t\tf(kCharacterAlternativesType, (uint16_t) value);\n\t\tbreak;\n\tcase mkTag('t', 'i', 't', 'l'):\n\t\t// TODO is this correct, or should we provide an else case?\n\t\tif (value != 0)\n\t\t\tf(kStyleOptionsType, kTitlingCapsSelector);\n\t\tbreak;\n\n\t// TODO will the following handle all cases properly, or are elses going to be needed?\n\tcase mkTag('t', 'r', 'a', 'd'):\n\t\tif (value != 0)\n\t\t\tf(kCharacterShapeType, kTraditionalCharactersSelector);\n\t\tbreak;\n\tcase mkTag('s', 'm', 'p', 'l'):\n\t\tif (value != 0)\n\t\t\tf(kCharacterShapeType, kSimplifiedCharactersSelector);\n\t\tbreak;\n\tcase mkTag('j', 'p', '7', '8'):\n\t\tif (value != 0)\n\t\t\tf(kCharacterShapeType, kJIS1978CharactersSelector);\n\t\tbreak;\n\tcase mkTag('j', 'p', '8', '3'):\n\t\tif (value != 0)\n\t\t\tf(kCharacterShapeType, kJIS1983CharactersSelector);\n\t\tbreak;\n\tcase mkTag('j', 'p', '9', '0'):\n\t\tif (value != 0)\n\t\t\tf(kCharacterShapeType, kJIS1990CharactersSelector);\n\t\tbreak;\n\tcase mkTag('e', 'x', 'p', 't'):\n\t\tif (value != 0)\n\t\t\tf(kCharacterShapeType, kExpertCharactersSelector);\n\t\tbreak;\n\tcase mkTag('j', 'p', '0', '4'):\n\t\tif (value != 0)\n\t\t\tf(kCharacterShapeType, kJIS2004CharactersSelector);\n\t\tbreak;\n\tcase mkTag('h', 'o', 'j', 'o'):\n\t\tif (value != 0)\n\t\t\tf(kCharacterShapeType, kHojoCharactersSelector);\n\t\tbreak;\n\tcase mkTag('n', 'l', 'c', 'k'):\n\t\tif (value != 0)\n\t\t\tf(kCharacterShapeType, kNLCCharactersSelector);\n\t\tbreak;\n\tcase mkTag('t', 'n', 'a', 'm'):\n\t\tif (value != 0)\n\t\t\tf(kCharacterShapeType, kTraditionalNamesCharactersSelector);\n\t\tbreak;\n\n\tcase mkTag('o', 'n', 'u', 'm'):\n\t// Core Text's internal AAT-to-OpenType mapping says to include this, so we include it too\n\t// TODO is it always set?\n\tcase mkTag('l', 'n', 'u', 'm'):\n\t\t// TODO is this correct, or should we provide an else case?\n\t\tif (value != 0)\n\t\t\tf(kNumberCaseType, kLowerCaseNumbersSelector);\n\t\tbreak;\n\tcase mkTag('h', 'n', 'g', 'l'):\n\t\t// TODO is this correct, or should we provide an else case?\n\t\tif (value != 0)\n\t\t\tf(kTransliterationType, kHanjaToHangulSelector);\n\t\tbreak;\n\tcase mkTag('n', 'a', 'l', 't'):\n\t\tf(kAnnotationType, (uint16_t) value);\n\t\tbreak;\n\tcase mkTag('r', 'u', 'b', 'y'):\n\t\t// include this for completeness\n\t\tboolspec(value, kRubyKanaType,\n\t\t\tkRubyKanaSelector,\n\t\t\tkNoRubyKanaSelector,\n\t\t\tf);\n\t\t// this is the current one\n\t\tboolspec(value, kRubyKanaType,\n\t\t\tkRubyKanaOnSelector,\n\t\t\tkRubyKanaOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('i', 't', 'a', 'l'):\n\t\t// include this for completeness\n\t\tboolspec(value, kItalicCJKRomanType,\n\t\t\tkCJKItalicRomanSelector,\n\t\t\tkNoCJKItalicRomanSelector,\n\t\t\tf);\n\t\t// this is the current one\n\t\tboolspec(value, kItalicCJKRomanType,\n\t\t\tkCJKItalicRomanOnSelector,\n\t\t\tkCJKItalicRomanOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('c', 'a', 's', 'e'):\n\t\tboolspec(value, kCaseSensitiveLayoutType,\n\t\t\tkCaseSensitiveLayoutOnSelector,\n\t\t\tkCaseSensitiveLayoutOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('c', 'p', 's', 'p'):\n\t\tboolspec(value, kCaseSensitiveLayoutType,\n\t\t\tkCaseSensitiveSpacingOnSelector,\n\t\t\tkCaseSensitiveSpacingOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('h', 'k', 'n', 'a'):\n\t\tboolspec(value, kAlternateKanaType,\n\t\t\tkAlternateHorizKanaOnSelector,\n\t\t\tkAlternateHorizKanaOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('v', 'k', 'n', 'a'):\n\t\tboolspec(value, kAlternateKanaType,\n\t\t\tkAlternateVertKanaOnSelector,\n\t\t\tkAlternateVertKanaOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('s', 's', '0', '1'):\n\t\tboolspec(value, kStylisticAlternativesType,\n\t\t\tkStylisticAltOneOnSelector,\n\t\t\tkStylisticAltOneOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('s', 's', '0', '2'):\n\t\tboolspec(value, kStylisticAlternativesType,\n\t\t\tkStylisticAltTwoOnSelector,\n\t\t\tkStylisticAltTwoOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('s', 's', '0', '3'):\n\t\tboolspec(value, kStylisticAlternativesType,\n\t\t\tkStylisticAltThreeOnSelector,\n\t\t\tkStylisticAltThreeOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('s', 's', '0', '4'):\n\t\tboolspec(value, kStylisticAlternativesType,\n\t\t\tkStylisticAltFourOnSelector,\n\t\t\tkStylisticAltFourOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('s', 's', '0', '5'):\n\t\tboolspec(value, kStylisticAlternativesType,\n\t\t\tkStylisticAltFiveOnSelector,\n\t\t\tkStylisticAltFiveOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('s', 's', '0', '6'):\n\t\tboolspec(value, kStylisticAlternativesType,\n\t\t\tkStylisticAltSixOnSelector,\n\t\t\tkStylisticAltSixOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('s', 's', '0', '7'):\n\t\tboolspec(value, kStylisticAlternativesType,\n\t\t\tkStylisticAltSevenOnSelector,\n\t\t\tkStylisticAltSevenOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('s', 's', '0', '8'):\n\t\tboolspec(value, kStylisticAlternativesType,\n\t\t\tkStylisticAltEightOnSelector,\n\t\t\tkStylisticAltEightOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('s', 's', '0', '9'):\n\t\tboolspec(value, kStylisticAlternativesType,\n\t\t\tkStylisticAltNineOnSelector,\n\t\t\tkStylisticAltNineOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('s', 's', '1', '0'):\n\t\tboolspec(value, kStylisticAlternativesType,\n\t\t\tkStylisticAltTenOnSelector,\n\t\t\tkStylisticAltTenOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('s', 's', '1', '1'):\n\t\tboolspec(value, kStylisticAlternativesType,\n\t\t\tkStylisticAltElevenOnSelector,\n\t\t\tkStylisticAltElevenOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('s', 's', '1', '2'):\n\t\tboolspec(value, kStylisticAlternativesType,\n\t\t\tkStylisticAltTwelveOnSelector,\n\t\t\tkStylisticAltTwelveOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('s', 's', '1', '3'):\n\t\tboolspec(value, kStylisticAlternativesType,\n\t\t\tkStylisticAltThirteenOnSelector,\n\t\t\tkStylisticAltThirteenOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('s', 's', '1', '4'):\n\t\tboolspec(value, kStylisticAlternativesType,\n\t\t\tkStylisticAltFourteenOnSelector,\n\t\t\tkStylisticAltFourteenOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('s', 's', '1', '5'):\n\t\tboolspec(value, kStylisticAlternativesType,\n\t\t\tkStylisticAltFifteenOnSelector,\n\t\t\tkStylisticAltFifteenOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('s', 's', '1', '6'):\n\t\tboolspec(value, kStylisticAlternativesType,\n\t\t\tkStylisticAltSixteenOnSelector,\n\t\t\tkStylisticAltSixteenOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('s', 's', '1', '7'):\n\t\tboolspec(value, kStylisticAlternativesType,\n\t\t\tkStylisticAltSeventeenOnSelector,\n\t\t\tkStylisticAltSeventeenOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('s', 's', '1', '8'):\n\t\tboolspec(value, kStylisticAlternativesType,\n\t\t\tkStylisticAltEighteenOnSelector,\n\t\t\tkStylisticAltEighteenOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('s', 's', '1', '9'):\n\t\tboolspec(value, kStylisticAlternativesType,\n\t\t\tkStylisticAltNineteenOnSelector,\n\t\t\tkStylisticAltNineteenOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('s', 's', '2', '0'):\n\t\tboolspec(value, kStylisticAlternativesType,\n\t\t\tkStylisticAltTwentyOnSelector,\n\t\t\tkStylisticAltTwentyOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('c', 'a', 'l', 't'):\n\t\tboolspec(value, kContextualAlternatesType,\n\t\t\tkContextualAlternatesOnSelector,\n\t\t\tkContextualAlternatesOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('s', 'w', 's', 'h'):\n\t\tboolspec(value, kContextualAlternatesType,\n\t\t\tkSwashAlternatesOnSelector,\n\t\t\tkSwashAlternatesOffSelector,\n\t\t\tf);\n\t\tbreak;\n\tcase mkTag('c', 's', 'w', 'h'):\n\t\tboolspec(value, kContextualAlternatesType,\n\t\t\tkContextualSwashAlternatesOnSelector,\n\t\t\tkContextualSwashAlternatesOffSelector,\n\t\t\tf);\n\t\tbreak;\n\n\t// TODO will the following handle all cases properly, or are elses going to be needed?\n\tcase mkTag('s', 'm', 'c', 'p'):\n\t\tif (value != 0) {\n\t\t\t// include this for compatibility (some fonts that come with OS X still use this!)\n\t\t\t// TODO make it boolean?\n\t\t\tf(kLetterCaseType, kSmallCapsSelector);\n\t\t\t// this is the current one\n\t\t\tf(kLowerCaseType, kLowerCaseSmallCapsSelector);\n\t\t}\n\t\tbreak;\n\tcase mkTag('p', 'c', 'a', 'p'):\n\t\tif (value != 0)\n\t\t\tf(kLowerCaseType, kLowerCasePetiteCapsSelector);\n\t\tbreak;\n\n\t// TODO will the following handle all cases properly, or are elses going to be needed?\n\tcase mkTag('c', '2', 's', 'c'):\n\t\tif (value != 0)\n\t\t\tf(kUpperCaseType, kUpperCaseSmallCapsSelector);\n\t\tbreak;\n\tcase mkTag('c', '2', 'p', 'c'):\n\t\tif (value != 0)\n\t\t\tf(kUpperCaseType, kUpperCasePetiteCapsSelector);\n\t\tbreak;\n\t}\n\t// TODO handle this properly\n\t// (it used to return 0 when this still returned the number of selectors produced but IDK what properly is anymore)\n}\n"
  },
  {
    "path": "darwin/alloc.m",
    "content": "// 4 december 2014\n#import <stdlib.h>\n#import \"uipriv_darwin.h\"\n\nstatic NSMutableArray *allocations;\nNSMutableArray *uiprivDelegates;\n\nvoid uiprivInitAlloc(void)\n{\n\tallocations = [NSMutableArray new];\n\tuiprivDelegates = [NSMutableArray new];\n}\n\n#define UINT8(p) ((uint8_t *) (p))\n#define PVOID(p) ((void *) (p))\n#define EXTRA (sizeof (size_t) + sizeof (const char **))\n#define DATA(p) PVOID(UINT8(p) + EXTRA)\n#define BASE(p) PVOID(UINT8(p) - EXTRA)\n#define SIZE(p) ((size_t *) (p))\n#define CCHAR(p) ((const char **) (p))\n#define TYPE(p) CCHAR(UINT8(p) + sizeof (size_t))\n\nvoid uiprivUninitAlloc(void)\n{\n\tNSMutableString *str;\n\tNSValue *v;\n\n\t[uiprivDelegates release];\n\tif ([allocations count] == 0) {\n\t\t[allocations release];\n\t\treturn;\n\t}\n\tstr = [NSMutableString new];\n\tfor (v in allocations) {\n\t\tvoid *ptr;\n\n\t\tptr = [v pointerValue];\n\t\t[str appendString:[NSString stringWithFormat:@\"%p %s\\n\", ptr, *TYPE(ptr)]];\n\t}\n\tuiprivUserBug(\"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]);\n\t[str release];\n}\n\nvoid *uiprivAlloc(size_t size, const char *type)\n{\n\tvoid *out;\n\n\tout = malloc(EXTRA + size);\n\tif (out == NULL) {\n\t\tfprintf(stderr, \"memory exhausted in uiAlloc()\\n\");\n\t\tabort();\n\t}\n\tmemset(DATA(out), 0, size);\n\t*SIZE(out) = size;\n\t*TYPE(out) = type;\n\t[allocations addObject:[NSValue valueWithPointer:out]];\n\treturn DATA(out);\n}\n\nvoid *uiprivRealloc(void *p, size_t new, const char *type)\n{\n\tvoid *out;\n\tsize_t *s;\n\n\tif (p == NULL)\n\t\treturn uiprivAlloc(new, type);\n\tp = BASE(p);\n\tout = realloc(p, EXTRA + new);\n\tif (out == NULL) {\n\t\tfprintf(stderr, \"memory exhausted in uiprivRealloc()\\n\");\n\t\tabort();\n\t}\n\ts = SIZE(out);\n\tif (new > *s)\n\t\tmemset(((uint8_t *) DATA(out)) + *s, 0, new - *s);\n\t*s = new;\n\t[allocations removeObject:[NSValue valueWithPointer:p]];\n\t[allocations addObject:[NSValue valueWithPointer:out]];\n\treturn DATA(out);\n}\n\nvoid uiprivFree(void *p)\n{\n\tif (p == NULL)\n\t\tuiprivImplBug(\"attempt to uiprivFree(NULL)\");\n\tp = BASE(p);\n\tfree(p);\n\t[allocations removeObject:[NSValue valueWithPointer:p]];\n}\n"
  },
  {
    "path": "darwin/area.m",
    "content": "// 9 september 2015\n#import \"uipriv_darwin.h\"\n\n// 10.8 fixups\n#define NSEventModifierFlags NSUInteger\n\n@interface areaView : NSView {\n\tuiArea *libui_a;\n\tNSTrackingArea *libui_ta;\n\tNSSize libui_ss;\n\tBOOL libui_enabled;\n}\n- (id)initWithFrame:(NSRect)r area:(uiArea *)a;\n- (uiModifiers)parseModifiers:(NSEvent *)e;\n- (void)doMouseEvent:(NSEvent *)e;\n- (int)sendKeyEvent:(uiAreaKeyEvent *)ke;\n- (int)doKeyDownUp:(NSEvent *)e up:(int)up;\n- (int)doKeyDown:(NSEvent *)e;\n- (int)doKeyUp:(NSEvent *)e;\n- (int)doFlagsChanged:(NSEvent *)e;\n- (void)setupNewTrackingArea;\n- (void)setScrollingSize:(NSSize)s;\n- (BOOL)isEnabled;\n- (void)setEnabled:(BOOL)e;\n@end\n\nstruct uiArea {\n\tuiDarwinControl c;\n\tNSView *view;\t\t\t// either sv or area depending on whether it is scrolling\n\tNSScrollView *sv;\n\tareaView *area;\n\tuiprivScrollViewData *d;\n\tuiAreaHandler *ah;\n\tBOOL scrolling;\n\tNSEvent *dragevent;\n};\n\n@implementation areaView\n\n- (id)initWithFrame:(NSRect)r area:(uiArea *)a\n{\n\tself = [super initWithFrame:r];\n\tif (self) {\n\t\tself->libui_a = a;\n\t\t[self setupNewTrackingArea];\n\t\tself->libui_ss = r.size;\n\t\tself->libui_enabled = YES;\n\t}\n\treturn self;\n}\n\n- (void)drawRect:(NSRect)r\n{\n\tuiArea *a = self->libui_a;\n\tCGContextRef c;\n\tuiAreaDrawParams dp;\n\n\tc = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];\n\t// see draw.m under text for why we need the height\n\tdp.Context = uiprivDrawNewContext(c, [self bounds].size.height);\n\n\tdp.AreaWidth = 0;\n\tdp.AreaHeight = 0;\n\tif (!a->scrolling) {\n\t\tdp.AreaWidth = [self frame].size.width;\n\t\tdp.AreaHeight = [self frame].size.height;\n\t}\n\n\tdp.ClipX = r.origin.x;\n\tdp.ClipY = r.origin.y;\n\tdp.ClipWidth = r.size.width;\n\tdp.ClipHeight = r.size.height;\n\n\t// no need to save or restore the graphics state to reset transformations; Cocoa creates a brand-new context each time\n\t(*(a->ah->Draw))(a->ah, a, &dp);\n\n\tuiprivDrawFreeContext(dp.Context);\n}\n\n- (BOOL)isFlipped\n{\n\treturn YES;\n}\n\n- (BOOL)acceptsFirstResponder\n{\n\treturn YES;\n}\n\n- (uiModifiers)parseModifiers:(NSEvent *)e\n{\n\tNSEventModifierFlags mods;\n\tuiModifiers m;\n\n\tm = 0;\n\tmods = [e modifierFlags];\n\tif ((mods & NSControlKeyMask) != 0)\n\t\tm |= uiModifierCtrl;\n\tif ((mods & NSAlternateKeyMask) != 0)\n\t\tm |= uiModifierAlt;\n\tif ((mods & NSShiftKeyMask) != 0)\n\t\tm |= uiModifierShift;\n\tif ((mods & NSCommandKeyMask) != 0)\n\t\tm |= uiModifierSuper;\n\treturn m;\n}\n\n- (void)setupNewTrackingArea\n{\n\tself->libui_ta = [[NSTrackingArea alloc] initWithRect:[self bounds]\n\t\toptions:(NSTrackingMouseEnteredAndExited |\n\t\t\tNSTrackingMouseMoved |\n\t\t\tNSTrackingActiveAlways |\n\t\t\tNSTrackingInVisibleRect |\n\t\t\tNSTrackingEnabledDuringMouseDrag)\n\t\towner:self\n\t\tuserInfo:nil];\n\t[self addTrackingArea:self->libui_ta];\n}\n\n- (void)updateTrackingAreas\n{\n\t[self removeTrackingArea:self->libui_ta];\n\t[self->libui_ta release];\n\t[self setupNewTrackingArea];\n}\n\n// capture on drag is done automatically on OS X\n- (void)doMouseEvent:(NSEvent *)e\n{\n\tuiArea *a = self->libui_a;\n\tuiAreaMouseEvent me;\n\tNSPoint point;\n\tint buttonNumber;\n\tNSUInteger pmb;\n\tunsigned int i, max;\n\n\t// this will convert point to drawing space\n\t// thanks swillits in irc.freenode.net/#macdev\n\tpoint = [self convertPoint:[e locationInWindow] fromView:nil];\n\tme.X = point.x;\n\tme.Y = point.y;\n\n\tme.AreaWidth = 0;\n\tme.AreaHeight = 0;\n\tif (!a->scrolling) {\n\t\tme.AreaWidth = [self frame].size.width;\n\t\tme.AreaHeight = [self frame].size.height;\n\t}\n\n\tbuttonNumber = [e buttonNumber] + 1;\n\t// swap button numbers 2 and 3 (right and middle)\n\tif (buttonNumber == 2)\n\t\tbuttonNumber = 3;\n\telse if (buttonNumber == 3)\n\t\tbuttonNumber = 2;\n\n\tme.Down = 0;\n\tme.Up = 0;\n\tme.Count = 0;\n\tswitch ([e type]) {\n\tcase NSLeftMouseDown:\n\tcase NSRightMouseDown:\n\tcase NSOtherMouseDown:\n\t\tme.Down = buttonNumber;\n\t\tme.Count = [e clickCount];\n\t\tbreak;\n\tcase NSLeftMouseUp:\n\tcase NSRightMouseUp:\n\tcase NSOtherMouseUp:\n\t\tme.Up = buttonNumber;\n\t\tbreak;\n\tcase NSLeftMouseDragged:\n\tcase NSRightMouseDragged:\n\tcase NSOtherMouseDragged:\n\t\t// we include the button that triggered the dragged event in the Held fields\n\t\tbuttonNumber = 0;\n\t\tbreak;\n\t}\n\n\tme.Modifiers = [self parseModifiers:e];\n\n\tpmb = [NSEvent pressedMouseButtons];\n\tme.Held1To64 = 0;\n\tif (buttonNumber != 1 && (pmb & 1) != 0)\n\t\tme.Held1To64 |= 1;\n\tif (buttonNumber != 2 && (pmb & 4) != 0)\n\t\tme.Held1To64 |= 2;\n\tif (buttonNumber != 3 && (pmb & 2) != 0)\n\t\tme.Held1To64 |= 4;\n\t// buttons 4..32\n\t// 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\n\tmax = 32;\n\tfor (i = 4; i <= max; i++) {\n\t\tuint64_t j;\n\n\t\tif (buttonNumber == i)\n\t\t\tcontinue;\n\t\tj = 1 << (i - 1);\n\t\tif ((pmb & j) != 0)\n\t\t\tme.Held1To64 |= j;\n\t}\n\n\tif (self->libui_enabled) {\n\t\t// and allow dragging here\n\t\ta->dragevent = e;\n\t\t(*(a->ah->MouseEvent))(a->ah, a, &me);\n\t\ta->dragevent = nil;\n\t}\n}\n\n#define mouseEvent(name) \\\n\t- (void)name:(NSEvent *)e \\\n\t{ \\\n\t\t[self doMouseEvent:e]; \\\n\t}\nmouseEvent(mouseMoved)\nmouseEvent(mouseDragged)\nmouseEvent(rightMouseDragged)\nmouseEvent(otherMouseDragged)\nmouseEvent(mouseDown)\nmouseEvent(rightMouseDown)\nmouseEvent(otherMouseDown)\nmouseEvent(mouseUp)\nmouseEvent(rightMouseUp)\nmouseEvent(otherMouseUp)\n\n- (void)mouseEntered:(NSEvent *)e\n{\n\tuiArea *a = self->libui_a;\n\n\tif (self->libui_enabled)\n\t\t(*(a->ah->MouseCrossed))(a->ah, a, 0);\n}\n\n- (void)mouseExited:(NSEvent *)e\n{\n\tuiArea *a = self->libui_a;\n\n\tif (self->libui_enabled)\n\t\t(*(a->ah->MouseCrossed))(a->ah, a, 1);\n}\n\n// note: there is no equivalent to WM_CAPTURECHANGED on Mac OS X; there literally is no way to break a grab like that\n// even if I invoke the task switcher and switch processes, the mouse grab will still be held until I let go of all buttons\n// therefore, no DragBroken()\n\n- (int)sendKeyEvent:(uiAreaKeyEvent *)ke\n{\n\tuiArea *a = self->libui_a;\n\n\treturn (*(a->ah->KeyEvent))(a->ah, a, ke);\n}\n\n- (int)doKeyDownUp:(NSEvent *)e up:(int)up\n{\n\tuiAreaKeyEvent ke;\n\n\tke.Key = 0;\n\tke.ExtKey = 0;\n\tke.Modifier = 0;\n\n\tke.Modifiers = [self parseModifiers:e];\n\n\tke.Up = up;\n\n\tif (!uiprivFromKeycode([e keyCode], &ke))\n\t\treturn 0;\n\treturn [self sendKeyEvent:&ke];\n}\n\n- (int)doKeyDown:(NSEvent *)e\n{\n\treturn [self doKeyDownUp:e up:0];\n}\n\n- (int)doKeyUp:(NSEvent *)e\n{\n\treturn [self doKeyDownUp:e up:1];\n}\n\n- (int)doFlagsChanged:(NSEvent *)e\n{\n\tuiAreaKeyEvent ke;\n\tuiModifiers whichmod;\n\n\tke.Key = 0;\n\tke.ExtKey = 0;\n\n\t// Mac OS X sends this event on both key up and key down.\n\t// 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\n\tif (!uiprivKeycodeModifier([e keyCode], &whichmod))\n\t\treturn 0;\n\tke.Modifier = whichmod;\n\tke.Modifiers = [self parseModifiers:e];\n\tke.Up = (ke.Modifiers & ke.Modifier) == 0;\n\t// and then drop the current modifier from Modifiers\n\tke.Modifiers &= ~ke.Modifier;\n\treturn [self sendKeyEvent:&ke];\n}\n\n- (void)setFrameSize:(NSSize)size\n{\n\tuiArea *a = self->libui_a;\n\n\t[super setFrameSize:size];\n\tif (!a->scrolling)\n\t\t// we must redraw everything on resize because Windows requires it\n\t\t[self setNeedsDisplay:YES];\n}\n\n- (void)setScrollingSize:(NSSize)s\n{\n\tself->libui_ss = s;\n\t[self setFrameSize:s];\n}\n\n- (NSSize)intrinsicContentSize\n{\n\tif (!self->libui_a->scrolling)\n\t\treturn [super intrinsicContentSize];\n\treturn self->libui_ss;\n}\n\n- (BOOL)becomeFirstResponder\n{\n\treturn [self isEnabled];\n}\n\n- (BOOL)isEnabled\n{\n\treturn self->libui_enabled;\n}\n\n- (void)setEnabled:(BOOL)e\n{\n\tself->libui_enabled = e;\n\tif (!self->libui_enabled && [self window] != nil)\n\t\tif ([[self window] firstResponder] == self)\n\t\t\t[[self window] makeFirstResponder:nil];\n}\n\n@end\n\nuiDarwinControlAllDefaultsExceptDestroy(uiArea, view)\n\nstatic void uiAreaDestroy(uiControl *c)\n{\n\tuiArea *a = uiArea(c);\n\n\tif (a->scrolling)\n\t\tuiprivScrollViewFreeData(a->sv, a->d);\n\t[a->area release];\n\tif (a->scrolling)\n\t\t[a->sv release];\n\tuiFreeControl(uiControl(a));\n}\n\n// called by subclasses of -[NSApplication sendEvent:]\n// by default, NSApplication eats some key events\n// this prevents that from happening with uiArea\n// 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\nint uiprivSendAreaEvents(NSEvent *e)\n{\n\tNSEventType type;\n\tid focused;\n\tareaView *view;\n\n\ttype = [e type];\n\tif (type != NSKeyDown && type != NSKeyUp && type != NSFlagsChanged)\n\t\treturn 0;\n\tfocused = [[e window] firstResponder];\n\tif (focused == nil)\n\t\treturn 0;\n\tif (![focused isKindOfClass:[areaView class]])\n\t\treturn 0;\n\tview = (areaView *) focused;\n\tswitch (type) {\n\tcase NSKeyDown:\n\t\treturn [view doKeyDown:e];\n\tcase NSKeyUp:\n\t\treturn [view doKeyUp:e];\n\tcase NSFlagsChanged:\n\t\treturn [view doFlagsChanged:e];\n\t}\n\treturn 0;\n}\n\nvoid uiAreaSetSize(uiArea *a, int width, int height)\n{\n\tif (!a->scrolling)\n\t\tuiprivUserBug(\"You cannot call uiAreaSetSize() on a non-scrolling uiArea. (area: %p)\", a);\n\t[a->area setScrollingSize:NSMakeSize(width, height)];\n}\n\nvoid uiAreaQueueRedrawAll(uiArea *a)\n{\n\t[a->area setNeedsDisplay:YES];\n}\n\nvoid uiAreaScrollTo(uiArea *a, double x, double y, double width, double height)\n{\n\tif (!a->scrolling)\n\t\tuiprivUserBug(\"You cannot call uiAreaScrollTo() on a non-scrolling uiArea. (area: %p)\", a);\n\t[a->area scrollRectToVisible:NSMakeRect(x, y, width, height)];\n\t// don't worry about the return value; it just says whether scrolling was needed\n}\n\nvoid uiAreaBeginUserWindowMove(uiArea *a)\n{\n\tuiprivNSWindow *w;\n\n\tw = (uiprivNSWindow *) [a->area window];\n\tif (w == nil)\n\t\treturn;\t\t// TODO\n\tif (a->dragevent == nil)\n\t\treturn;\t\t// TODO\n\t[w uiprivDoMove:a->dragevent];\n}\n\nvoid uiAreaBeginUserWindowResize(uiArea *a, uiWindowResizeEdge edge)\n{\n\tuiprivNSWindow *w;\n\n\tw = (uiprivNSWindow *) [a->area window];\n\tif (w == nil)\n\t\treturn;\t\t// TODO\n\tif (a->dragevent == nil)\n\t\treturn;\t\t// TODO\n\t[w uiprivDoResize:a->dragevent on:edge];\n}\n\nuiArea *uiNewArea(uiAreaHandler *ah)\n{\n\tuiArea *a;\n\n\tuiDarwinNewControl(uiArea, a);\n\n\ta->ah = ah;\n\ta->scrolling = NO;\n\n\ta->area = [[areaView alloc] initWithFrame:NSZeroRect area:a];\n\n\ta->view = a->area;\n\n\treturn a;\n}\n\nuiArea *uiNewScrollingArea(uiAreaHandler *ah, int width, int height)\n{\n\tuiArea *a;\n\tuiprivScrollViewCreateParams p;\n\n\tuiDarwinNewControl(uiArea, a);\n\n\ta->ah = ah;\n\ta->scrolling = YES;\n\n\ta->area = [[areaView alloc] initWithFrame:NSMakeRect(0, 0, width, height)\n\t\tarea:a];\n\n\tmemset(&p, 0, sizeof (uiprivScrollViewCreateParams));\n\tp.DocumentView = a->area;\n\tp.BackgroundColor = [NSColor controlColor];\n\tp.DrawsBackground = 1;\n\tp.Bordered = NO;\n\tp.HScroll = YES;\n\tp.VScroll = YES;\n\ta->sv = uiprivMkScrollView(&p, &(a->d));\n\n\ta->view = a->sv;\n\n\treturn a;\n}\n"
  },
  {
    "path": "darwin/areaevents.m",
    "content": "// 30 march 2014\n#import \"uipriv_darwin.h\"\n\n/*\nMac OS X uses its own set of hardware key codes that are different from PC keyboard scancodes, but are positional (like PC keyboard scancodes). These are defined in <HIToolbox/Events.h>, a Carbon header. As far as I can tell, there's no way to include this header without either using an absolute path or linking Carbon into the program, so the constant values are used here instead.\n\nThe Cocoa docs do guarantee that -[NSEvent keyCode] results in key codes that are the same as those returned by Carbon; that is, these codes.\n*/\n\n// use uintptr_t to be safe\nstatic const struct {\n\tuintptr_t keycode;\n\tchar equiv;\n} keycodeKeys[] = {\n\t{ 0x00, 'a' },\n\t{ 0x01, 's' },\n\t{ 0x02, 'd' },\n\t{ 0x03, 'f' },\n\t{ 0x04, 'h' },\n\t{ 0x05, 'g' },\n\t{ 0x06, 'z' },\n\t{ 0x07, 'x' },\n\t{ 0x08, 'c' },\n\t{ 0x09, 'v' },\n\t{ 0x0B, 'b' },\n\t{ 0x0C, 'q' },\n\t{ 0x0D, 'w' },\n\t{ 0x0E, 'e' },\n\t{ 0x0F, 'r' },\n\t{ 0x10, 'y' },\n\t{ 0x11, 't' },\n\t{ 0x12, '1' },\n\t{ 0x13, '2' },\n\t{ 0x14, '3' },\n\t{ 0x15, '4' },\n\t{ 0x16, '6' },\n\t{ 0x17, '5' },\n\t{ 0x18, '=' },\n\t{ 0x19, '9' },\n\t{ 0x1A, '7' },\n\t{ 0x1B, '-' },\n\t{ 0x1C, '8' },\n\t{ 0x1D, '0' },\n\t{ 0x1E, ']' },\n\t{ 0x1F, 'o' },\n\t{ 0x20, 'u' },\n\t{ 0x21, '[' },\n\t{ 0x22, 'i' },\n\t{ 0x23, 'p' },\n\t{ 0x25, 'l' },\n\t{ 0x26, 'j' },\n\t{ 0x27, '\\'' },\n\t{ 0x28, 'k' },\n\t{ 0x29, ';' },\n\t{ 0x2A, '\\\\' },\n\t{ 0x2B, ',' },\n\t{ 0x2C, '/' },\n\t{ 0x2D, 'n' },\n\t{ 0x2E, 'm' },\n\t{ 0x2F, '.' },\n\t{ 0x32, '`' },\n\t{ 0x24, '\\n' },\n\t{ 0x30, '\\t' },\n\t{ 0x31, ' ' },\n\t{ 0x33, '\\b' },\n\t{ 0xFFFF, 0 },\n};\n\nstatic const struct {\n\tuintptr_t keycode;\n\tuiExtKey equiv;\n} keycodeExtKeys[] = {\n\t{ 0x41, uiExtKeyNDot },\n\t{ 0x43, uiExtKeyNMultiply },\n\t{ 0x45, uiExtKeyNAdd },\n\t{ 0x4B, uiExtKeyNDivide },\n\t{ 0x4C, uiExtKeyNEnter },\n\t{ 0x4E, uiExtKeyNSubtract },\n\t{ 0x52, uiExtKeyN0 },\n\t{ 0x53, uiExtKeyN1 },\n\t{ 0x54, uiExtKeyN2 },\n\t{ 0x55, uiExtKeyN3 },\n\t{ 0x56, uiExtKeyN4 },\n\t{ 0x57, uiExtKeyN5 },\n\t{ 0x58, uiExtKeyN6 },\n\t{ 0x59, uiExtKeyN7 },\n\t{ 0x5B, uiExtKeyN8 },\n\t{ 0x5C, uiExtKeyN9 },\n\t{ 0x35, uiExtKeyEscape },\n\t{ 0x60, uiExtKeyF5 },\n\t{ 0x61, uiExtKeyF6 },\n\t{ 0x62, uiExtKeyF7 },\n\t{ 0x63, uiExtKeyF3 },\n\t{ 0x64, uiExtKeyF8 },\n\t{ 0x65, uiExtKeyF9 },\n\t{ 0x67, uiExtKeyF11 },\n\t{ 0x6D, uiExtKeyF10 },\n\t{ 0x6F, uiExtKeyF12 },\n\t{ 0x72, uiExtKeyInsert },\t\t// listed as the Help key but it's in the same position on an Apple keyboard as the Insert key on a Windows keyboard; thanks to SeanieB from irc.badnik.net and Psy in irc.freenode.net/#macdev for confirming they have the same code\n\t{ 0x73, uiExtKeyHome },\n\t{ 0x74, uiExtKeyPageUp },\n\t{ 0x75, uiExtKeyDelete },\n\t{ 0x76, uiExtKeyF4 },\n\t{ 0x77, uiExtKeyEnd },\n\t{ 0x78, uiExtKeyF2 },\n\t{ 0x79, uiExtKeyPageDown },\n\t{ 0x7A, uiExtKeyF1 },\n\t{ 0x7B, uiExtKeyLeft },\n\t{ 0x7C, uiExtKeyRight },\n\t{ 0x7D, uiExtKeyDown },\n\t{ 0x7E, uiExtKeyUp },\n\t{ 0xFFFF, 0 },\n};\n\nstatic const struct {\n\tuintptr_t keycode;\n\tuiModifiers equiv;\n} keycodeModifiers[] = {\n\t{ 0x37, uiModifierSuper },\t\t// left command\n\t{ 0x38, uiModifierShift },\t\t// left shift\n\t{ 0x3A, uiModifierAlt },\t\t// left option\n\t{ 0x3B, uiModifierCtrl },\t\t// left control\n\t{ 0x3C, uiModifierShift },\t\t// right shift\n\t{ 0x3D, uiModifierAlt },\t\t// right alt\n\t{ 0x3E, uiModifierCtrl },\t\t// right control\n\t// the following is not in Events.h for some reason\n\t// thanks to Nicole and jedivulcan from irc.badnik.net\n\t{ 0x36, uiModifierSuper },\t\t// right command\n\t{ 0xFFFF, 0 },\n};\n\nBOOL uiprivFromKeycode(unsigned short keycode, uiAreaKeyEvent *ke)\n{\n\tint i;\n\n\tfor (i = 0; keycodeKeys[i].keycode != 0xFFFF; i++)\n\t\tif (keycodeKeys[i].keycode == keycode) {\n\t\t\tke->Key = keycodeKeys[i].equiv;\n\t\t\treturn YES;\n\t\t}\n\tfor (i = 0; keycodeExtKeys[i].keycode != 0xFFFF; i++)\n\t\tif (keycodeExtKeys[i].keycode == keycode) {\n\t\t\tke->ExtKey = keycodeExtKeys[i].equiv;\n\t\t\treturn YES;\n\t\t}\n\treturn NO;\n}\n\nBOOL uiprivKeycodeModifier(unsigned short keycode, uiModifiers *mod)\n{\n\tint i;\n\n\tfor (i = 0; keycodeModifiers[i].keycode != 0xFFFF; i++)\n\t\tif (keycodeModifiers[i].keycode == keycode) {\n\t\t\t*mod = keycodeModifiers[i].equiv;\n\t\t\treturn YES;\n\t\t}\n\treturn NO;\n}\n"
  },
  {
    "path": "darwin/attrstr.h",
    "content": "// 4 march 2018\n#import \"../common/attrstr.h\"\n\n// opentype.m\nextern CFArrayRef uiprivOpenTypeFeaturesToCTFeatures(const uiOpenTypeFeatures *otf);\n\n// aat.m\ntypedef void (^uiprivAATBlock)(uint16_t type, uint16_t selector);\nextern void uiprivOpenTypeToAAT(char a, char b, char c, char d, uint32_t value, uiprivAATBlock f);\n\n// fontmatch.m\n@interface uiprivFontStyleData : NSObject {\n\tCTFontRef font;\n\tCTFontDescriptorRef desc;\n\tCFDictionaryRef traits;\n\tCTFontSymbolicTraits symbolic;\n\tdouble weight;\n\tdouble width;\n\tBOOL didStyleName;\n\tCFStringRef styleName;\n\tBOOL didVariation;\n\tCFDictionaryRef variation;\n\tBOOL hasRegistrationScope;\n\tCTFontManagerScope registrationScope;\n\tBOOL didPostScriptName;\n\tCFStringRef postScriptName;\n\tCTFontFormat fontFormat;\n\tBOOL didPreferredSubFamilyName;\n\tCFStringRef preferredSubFamilyName;\n\tBOOL didSubFamilyName;\n\tCFStringRef subFamilyName;\n\tBOOL didFullName;\n\tCFStringRef fullName;\n\tBOOL didPreferredFamilyName;\n\tCFStringRef preferredFamilyName;\n\tBOOL didFamilyName;\n\tCFStringRef familyName;\n\tBOOL didVariationAxes;\n\tCFArrayRef variationAxes;\n}\n- (id)initWithFont:(CTFontRef)f;\n- (id)initWithDescriptor:(CTFontDescriptorRef)d;\n- (BOOL)prepare;\n- (void)ensureFont;\n- (CTFontSymbolicTraits)symbolicTraits;\n- (double)weight;\n- (double)width;\n- (CFStringRef)styleName;\n- (CFDictionaryRef)variation;\n- (BOOL)hasRegistrationScope;\n- (CTFontManagerScope)registrationScope;\n- (CFStringRef)postScriptName;\n- (CFDataRef)table:(CTFontTableTag)tag;\n- (CTFontFormat)fontFormat;\n- (CFStringRef)fontName:(CFStringRef)key;\n- (CFStringRef)preferredSubFamilyName;\n- (CFStringRef)subFamilyName;\n- (CFStringRef)fullName;\n- (CFStringRef)preferredFamilyName;\n- (CFStringRef)familyName;\n- (CFArrayRef)variationAxes;\n@end\nextern CTFontDescriptorRef uiprivFontDescriptorToCTFontDescriptor(uiFontDescriptor *fd);\nextern CTFontDescriptorRef uiprivCTFontDescriptorAppendFeatures(CTFontDescriptorRef desc, const uiOpenTypeFeatures *otf);\nextern void uiprivFontDescriptorFromCTFontDescriptor(CTFontDescriptorRef ctdesc, uiFontDescriptor *uidesc);\n\n// fonttraits.m\nextern void uiprivProcessFontTraits(uiprivFontStyleData *d, uiFontDescriptor *out);\n\n// fontvariation.m\nextern NSDictionary *uiprivMakeVariationAxisDict(CFArrayRef axes, CFDataRef avarTable);\nextern void uiprivProcessFontVariation(uiprivFontStyleData *d, NSDictionary *axisDict, uiFontDescriptor *out);\n\n// attrstr.m\nextern void uiprivInitUnderlineColors(void);\nextern void uiprivUninitUnderlineColors(void);\nextern CFAttributedStringRef uiprivAttributedStringToCFAttributedString(uiDrawTextLayoutParams *p, NSArray **backgroundParams);\n\n// drawtext.m\n// TODO figure out where this type should *really* go in all the headers...\n@interface uiprivDrawTextBackgroundParams : NSObject {\n\tsize_t start;\n\tsize_t end;\n\tdouble r;\n\tdouble g;\n\tdouble b;\n\tdouble a;\n}\n- (id)initWithStart:(size_t)s end:(size_t)e r:(double)red g:(double)green b:(double)blue a:(double)alpha;\n- (void)draw:(CGContextRef)c layout:(uiDrawTextLayout *)layout at:(double)x y:(double)y utf8Mapping:(const size_t *)u16tou8;\n@end\n"
  },
  {
    "path": "darwin/attrstr.m",
    "content": "// 12 february 2017\n#import \"uipriv_darwin.h\"\n#import \"attrstr.h\"\n\n// this is what AppKit does internally\n// WebKit does this too; see https://github.com/adobe/webkit/blob/master/Source/WebCore/platform/graphics/mac/GraphicsContextMac.mm\nstatic NSColor *spellingColor = nil;\nstatic NSColor *grammarColor = nil;\nstatic NSColor *auxiliaryColor = nil;\n\nstatic NSColor *tryColorNamed(NSString *name)\n{\n\tNSImage *img;\n\n\timg = [NSImage imageNamed:name];\n\tif (img == nil)\n\t\treturn nil;\n\treturn [NSColor colorWithPatternImage:img];\n}\n\nvoid uiprivInitUnderlineColors(void)\n{\n\tspellingColor = tryColorNamed(@\"NSSpellingDot\");\n\tif (spellingColor == nil) {\n\t\t// WebKit says this is needed for \"older systems\"; not sure how old, but 10.11 AppKit doesn't look for this\n\t\tspellingColor = tryColorNamed(@\"SpellingDot\");\n\t\tif (spellingColor == nil)\n\t\t\tspellingColor = [NSColor redColor];\n\t}\n\t[spellingColor retain];\t\t// override autoreleasing\n\n\tgrammarColor = tryColorNamed(@\"NSGrammarDot\");\n\tif (grammarColor == nil) {\n\t\t// WebKit says this is needed for \"older systems\"; not sure how old, but 10.11 AppKit doesn't look for this\n\t\tgrammarColor = tryColorNamed(@\"GrammarDot\");\n\t\tif (grammarColor == nil)\n\t\t\tgrammarColor = [NSColor greenColor];\n\t}\n\t[grammarColor retain];\t\t// override autoreleasing\n\n\tauxiliaryColor = tryColorNamed(@\"NSCorrectionDot\");\n\tif (auxiliaryColor == nil) {\n\t\t// WebKit says this is needed for \"older systems\"; not sure how old, but 10.11 AppKit doesn't look for this\n\t\tauxiliaryColor = tryColorNamed(@\"CorrectionDot\");\n\t\tif (auxiliaryColor == nil)\n\t\t\tauxiliaryColor = [NSColor blueColor];\n\t}\n\t[auxiliaryColor retain];\t\t// override autoreleasing\n}\n\nvoid uiprivUninitUnderlineColors(void)\n{\n\t[auxiliaryColor release];\n\tauxiliaryColor = nil;\n\t[grammarColor release];\n\tgrammarColor = nil;\n\t[spellingColor release];\n\tspellingColor = nil;\n}\n\n// 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?\n// TODO see if we could use NSAttributedString?\n// TODO consider renaming this struct and the fep variable(s)\n// TODO restructure all this so the important details at the top are below with the combined font attributes type?\n// TODO in fact I should just write something to explain everything in this file...\nstruct foreachParams {\n\tCFMutableAttributedStringRef mas;\n\tNSMutableArray *backgroundParams;\n};\n\n// unlike the other systems, Core Text rolls family, size, weight, italic, width, AND opentype features into the \"font\" attribute\n// 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\n// 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)\n// TODO structure and rewrite this part\n// TODO re-find sources proving support of custom attributes\n// TODO what if this is NULL?\nstatic const CFStringRef combinedFontAttrName = CFSTR(\"libuiCombinedFontAttribute\");\n\nenum {\n\tcFamily,\n\tcSize,\n\tcWeight,\n\tcItalic,\n\tcStretch,\n\tcFeatures,\n\tnc,\n};\n\nstatic const int toc[] = {\n\t[uiAttributeTypeFamily] = cFamily,\n\t[uiAttributeTypeSize] = cSize,\n\t[uiAttributeTypeWeight] = cWeight,\n\t[uiAttributeTypeItalic] = cItalic,\n\t[uiAttributeTypeStretch] = cStretch,\n\t[uiAttributeTypeFeatures] = cFeatures,\n};\n\nstatic uiForEach featuresHash(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value, void *data)\n{\n\tNSUInteger *hash = (NSUInteger *) data;\n\tuint32_t tag;\n\n\ttag = (((uint32_t) a) & 0xFF) << 24;\n\ttag |= (((uint32_t) b) & 0xFF) << 16;\n\ttag |= (((uint32_t) c) & 0xFF) << 8;\n\ttag |= ((uint32_t) d) & 0xFF;\n\t*hash ^= tag;\n\t*hash ^= value;\n\treturn uiForEachContinue;\n}\n\n@interface uiprivCombinedFontAttr : NSObject<NSCopying> {\n\tuiAttribute *attrs[nc];\n\tBOOL hasHash;\n\tNSUInteger hash;\n}\n- (void)addAttribute:(uiAttribute *)attr;\n- (CTFontRef)toCTFontWithDefaultFont:(uiFontDescriptor *)defaultFont;\n@end\n\n@implementation uiprivCombinedFontAttr\n\n- (id)init\n{\n\tself = [super init];\n\tif (self) {\n\t\tmemset(self->attrs, 0, nc * sizeof (uiAttribute *));\n\t\tself->hasHash = NO;\n\t}\n\treturn self;\n}\n\n- (void)dealloc\n{\n\tint i;\n\n\tfor (i = 0; i < nc; i++)\n\t\tif (self->attrs[i] != NULL) {\n\t\t\tuiprivAttributeRelease(self->attrs[i]);\n\t\t\tself->attrs[i] = NULL;\n\t\t}\n\t[super dealloc];\n}\n\n- (id)copyWithZone:(NSZone *)zone\n{\n\tuiprivCombinedFontAttr *ret;\n\tint i;\n\n\tret = [[uiprivCombinedFontAttr allocWithZone:zone] init];\n\tfor (i = 0; i < nc; i++)\n\t\tif (self->attrs[i] != NULL)\n\t\t\tret->attrs[i] = uiprivAttributeRetain(self->attrs[i]);\n\tret->hasHash = self->hasHash;\n\tret->hash = self->hash;\n\treturn ret;\n}\n\n- (void)addAttribute:(uiAttribute *)attr\n{\n\tint index;\n\n\tindex = toc[uiAttributeGetType(attr)];\n\tif (self->attrs[index] != NULL)\n\t\tuiprivAttributeRelease(self->attrs[index]);\n\tself->attrs[index] = uiprivAttributeRetain(attr);\n\tself->hasHash = NO;\n}\n\n- (BOOL)isEqual:(id)bb\n{\n\tuiprivCombinedFontAttr *b = (uiprivCombinedFontAttr *) bb;\n\tint i;\n\n\tif (b == nil)\n\t\treturn NO;\n\tfor (i = 0; i < nc; i++) {\n\t\tif (self->attrs[i] == NULL && b->attrs[i] == NULL)\n\t\t\tcontinue;\n\t\tif (self->attrs[i] == NULL || b->attrs[i] == NULL)\n\t\t\treturn NO;\n\t\tif (!uiprivAttributeEqual(self->attrs[i], b->attrs[i]))\n\t\t\treturn NO;\n\t}\n\treturn YES;\n}\n\n- (NSUInteger)hash\n{\n\tif (self->hasHash)\n\t\treturn self->hash;\n\t@autoreleasepool {\n\t\tNSString *family;\n\t\tNSNumber *size;\n\n\t\tself->hash = 0;\n\t\tif (self->attrs[cFamily] != NULL) {\n\t\t\tfamily = [NSString stringWithUTF8String:uiAttributeFamily(self->attrs[cFamily])];\n\t\t\t// TODO make sure this aligns with case-insensitive compares when those are done in common/attribute.c\n\t\t\tself->hash ^= [[family uppercaseString] hash];\n\t\t}\n\t\tif (self->attrs[cSize] != NULL) {\n\t\t\tsize = [NSNumber numberWithDouble:uiAttributeSize(self->attrs[cSize])];\n\t\t\tself->hash ^= [size hash];\n\t\t}\n\t\tif (self->attrs[cWeight] != NULL)\n\t\t\tself->hash ^= (NSUInteger) uiAttributeWeight(self->attrs[cWeight]);\n\t\tif (self->attrs[cItalic] != NULL)\n\t\t\tself->hash ^= (NSUInteger) uiAttributeItalic(self->attrs[cItalic]);\n\t\tif (self->attrs[cStretch] != NULL)\n\t\t\tself->hash ^= (NSUInteger) uiAttributeStretch(self->attrs[cStretch]);\n\t\tif (self->attrs[cFeatures] != NULL)\n\t\t\tuiOpenTypeFeaturesForEach(uiAttributeFeatures(self->attrs[cFeatures]), featuresHash, &(self->hash));\n\t\tself->hasHash = YES;\n\t}\n\treturn self->hash;\n}\n\n- (CTFontRef)toCTFontWithDefaultFont:(uiFontDescriptor *)defaultFont\n{\n\tuiFontDescriptor uidesc;\n\tCTFontDescriptorRef desc;\n\tCTFontRef font;\n\n\tuidesc = *defaultFont;\n\tif (self->attrs[cFamily] != NULL)\n\t\t// TODO const-correct uiFontDescriptor or change this function below\n\t\tuidesc.Family = (char *) uiAttributeFamily(self->attrs[cFamily]);\n\tif (self->attrs[cSize] != NULL)\n\t\tuidesc.Size = uiAttributeSize(self->attrs[cSize]);\n\tif (self->attrs[cWeight] != NULL)\n\t\tuidesc.Weight = uiAttributeWeight(self->attrs[cWeight]);\n\tif (self->attrs[cItalic] != NULL)\n\t\tuidesc.Italic = uiAttributeItalic(self->attrs[cItalic]);\n\tif (self->attrs[cStretch] != NULL)\n\t\tuidesc.Stretch = uiAttributeStretch(self->attrs[cStretch]);\n\tdesc = uiprivFontDescriptorToCTFontDescriptor(&uidesc);\n\tif (self->attrs[cFeatures] != NULL)\n\t\tdesc = uiprivCTFontDescriptorAppendFeatures(desc, uiAttributeFeatures(self->attrs[cFeatures]));\n\tfont = CTFontCreateWithFontDescriptor(desc, uidesc.Size, NULL);\n\tCFRelease(desc);\t\t\t// TODO correct?\n\treturn font;\n}\n\n@end\n\nstatic void addFontAttributeToRange(struct foreachParams *p, size_t start, size_t end, uiAttribute *attr)\n{\n\tuiprivCombinedFontAttr *cfa;\n\tCFRange range;\n\tsize_t diff;\n\n\twhile (start < end) {\n\t\tcfa = (uiprivCombinedFontAttr *) CFAttributedStringGetAttribute(p->mas, start, combinedFontAttrName, &range);\n\t\tif (cfa == nil)\n\t\t\tcfa = [uiprivCombinedFontAttr new];\n\t\telse\n\t\t\tcfa = [cfa copy];\n\t\t[cfa addAttribute:attr];\n\t\t// clamp range within [start, end)\n\t\tif (range.location < start) {\n\t\t\tdiff = start - range.location;\n\t\t\trange.location = start;\n\t\t\trange.length -= diff;\n\t\t}\n\t\tif ((range.location + range.length) > end)\n\t\t\trange.length = end - range.location;\n\t\tCFAttributedStringSetAttribute(p->mas, range, combinedFontAttrName, cfa);\n\t\t[cfa release];\n\t\tstart += range.length;\n\t}\n}\n\nstatic CGColorRef mkcolor(double r, double g, double b, double a)\n{\n\tCGColorSpaceRef colorspace;\n\tCGColorRef color;\n\tCGFloat components[4];\n\n\t// TODO we should probably just create this once and recycle it throughout program execution...\n\tcolorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);\n\tif (colorspace == NULL) {\n\t\t// TODO\n\t}\n\tcomponents[0] = r;\n\tcomponents[1] = g;\n\tcomponents[2] = b;\n\tcomponents[3] = a;\n\tcolor = CGColorCreate(colorspace, components);\n\tCFRelease(colorspace);\n\treturn color;\n}\n\nstatic void addBackgroundAttribute(struct foreachParams *p, size_t start, size_t end, double r, double g, double b, double a)\n{\n\tuiprivDrawTextBackgroundParams *dtb;\n\n\t// TODO make sure this works properly with line paragraph spacings (after figuring out what that means, of course)\n\tif (uiprivFUTURE_kCTBackgroundColorAttributeName != NULL) {\n\t\tCGColorRef color;\n\t\tCFRange range;\n\n\t\tcolor = mkcolor(r, g, b, a);\n\t\trange.location = start;\n\t\trange.length = end - start;\n\t\tCFAttributedStringSetAttribute(p->mas, range, *uiprivFUTURE_kCTBackgroundColorAttributeName, color);\n\t\tCFRelease(color);\n\t\treturn;\n\t}\n\n\tdtb = [[uiprivDrawTextBackgroundParams alloc] initWithStart:start end:end r:r g:g b:b a:a];\n\t[p->backgroundParams addObject:dtb];\n\t[dtb release];\n}\n\nstatic uiForEach processAttribute(const uiAttributedString *s, const uiAttribute *attr, size_t start, size_t end, void *data)\n{\n\tstruct foreachParams *p = (struct foreachParams *) data;\n\tCFRange range;\n\tCGColorRef color;\n\tint32_t us;\n\tCFNumberRef num;\n\tdouble r, g, b, a;\n\tuiUnderlineColor colorType;\n\n\tstart = uiprivAttributedStringUTF8ToUTF16(s, start);\n\tend = uiprivAttributedStringUTF8ToUTF16(s, end);\n\trange.location = start;\n\trange.length = end - start;\n\tswitch (uiAttributeGetType(attr)) {\n\tcase uiAttributeTypeFamily:\n\tcase uiAttributeTypeSize:\n\tcase uiAttributeTypeWeight:\n\tcase uiAttributeTypeItalic:\n\tcase uiAttributeTypeStretch:\n\tcase uiAttributeTypeFeatures:\n\t\taddFontAttributeToRange(p, start, end, attr);\n\t\tbreak;\n\tcase uiAttributeTypeColor:\n\t\tuiAttributeColor(attr, &r, &g, &b, &a);\n\t\tcolor = mkcolor(r, g, b, a);\n\t\tCFAttributedStringSetAttribute(p->mas, range, kCTForegroundColorAttributeName, color);\n\t\tCFRelease(color);\n\t\tbreak;\n\tcase uiAttributeTypeBackground:\n\t\tuiAttributeColor(attr, &r, &g, &b, &a);\n\t\taddBackgroundAttribute(p, start, end, r, g, b, a);\n\t\tbreak;\n\t// TODO turn into a class, like we did with the font attributes, or even integrate *into* the font attributes\n\tcase uiAttributeTypeUnderline:\n\t\tswitch (uiAttributeUnderline(attr)) {\n\t\tcase uiUnderlineNone:\n\t\t\tus = kCTUnderlineStyleNone;\n\t\t\tbreak;\n\t\tcase uiUnderlineSingle:\n\t\t\tus = kCTUnderlineStyleSingle;\n\t\t\tbreak;\n\t\tcase uiUnderlineDouble:\n\t\t\tus = kCTUnderlineStyleDouble;\n\t\t\tbreak;\n\t\tcase uiUnderlineSuggestion:\n\t\t\t// TODO incorrect if a solid color\n\t\t\tus = kCTUnderlineStyleThick;\n\t\t\tbreak;\n\t\t}\n\t\tnum = CFNumberCreate(NULL, kCFNumberSInt32Type, &us);\n\t\tCFAttributedStringSetAttribute(p->mas, range, kCTUnderlineStyleAttributeName, num);\n\t\tCFRelease(num);\n\t\tbreak;\n\tcase uiAttributeTypeUnderlineColor:\n\t\tuiAttributeUnderlineColor(attr, &colorType, &r, &g, &b, &a);\n\t\tswitch (colorType) {\n\t\tcase uiUnderlineColorCustom:\n\t\t\tcolor = mkcolor(r, g, b, a);\n\t\t\tbreak;\n\t\tcase uiUnderlineColorSpelling:\n\t\t\tcolor = [spellingColor CGColor];\n\t\t\tbreak;\n\t\tcase uiUnderlineColorGrammar:\n\t\t\tcolor = [grammarColor CGColor];\n\t\t\tbreak;\n\t\tcase uiUnderlineColorAuxiliary:\n\t\t\tcolor = [auxiliaryColor CGColor];\n\t\t\tbreak;\n\t\t}\n\t\tCFAttributedStringSetAttribute(p->mas, range, kCTUnderlineColorAttributeName, color);\n\t\tif (colorType == uiUnderlineColorCustom)\n\t\t\tCFRelease(color);\n\t\tbreak;\n\t}\n\treturn uiForEachContinue;\n}\n\nstatic void applyFontAttributes(CFMutableAttributedStringRef mas, uiFontDescriptor *defaultFont)\n{\n\tuiprivCombinedFontAttr *cfa;\n\tCTFontRef font;\n\tCFRange range;\n\tCFIndex n;\n\n\tn = CFAttributedStringGetLength(mas);\n\n\t// first apply the default font to the entire string\n\t// TODO is this necessary given the #if 0'd code in uiprivAttributedStringToCFAttributedString()?\n\tcfa = [uiprivCombinedFontAttr new];\n\tfont = [cfa toCTFontWithDefaultFont:defaultFont];\n\t[cfa release];\n\trange.location = 0;\n\trange.length = n;\n\tCFAttributedStringSetAttribute(mas, range, kCTFontAttributeName, font);\n\tCFRelease(font);\n\n\t// now go through, replacing every uiprivCombinedFontAttr with the proper CTFontRef\n\t// 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\n\trange.location = 0;\n\twhile (range.location < n) {\n\t\t// TODO consider seeing if CFAttributedStringGetAttributeAndLongestEffectiveRange() can make things faster by reducing the number of potential iterations, either here or above\n\t\tcfa = (uiprivCombinedFontAttr *) CFAttributedStringGetAttribute(mas, range.location, combinedFontAttrName, &range);\n\t\tif (cfa != nil) {\n\t\t\tfont = [cfa toCTFontWithDefaultFont:defaultFont];\n\t\t\tCFAttributedStringSetAttribute(mas, range, kCTFontAttributeName, font);\n\t\t\tCFRelease(font);\n\t\t}\n\t\trange.location += range.length;\n\t}\n\n\t// and finally, get rid of all the uiprivCombinedFontAttrs as we won't need them anymore\n\trange.location = 0;\n\trange.length = 0;\n\tCFAttributedStringRemoveAttribute(mas, range, combinedFontAttrName);\n}\n\nstatic const CTTextAlignment ctaligns[] = {\n\t[uiDrawTextAlignLeft] = kCTTextAlignmentLeft,\n\t[uiDrawTextAlignCenter] = kCTTextAlignmentCenter,\n\t[uiDrawTextAlignRight] = kCTTextAlignmentRight,\n};\n\nstatic CTParagraphStyleRef mkParagraphStyle(uiDrawTextLayoutParams *p)\n{\n\tCTParagraphStyleRef ps;\n\tCTParagraphStyleSetting settings[16];\n\tsize_t nSettings = 0;\n\n\tsettings[nSettings].spec = kCTParagraphStyleSpecifierAlignment;\n\tsettings[nSettings].valueSize = sizeof (CTTextAlignment);\n\tsettings[nSettings].value = ctaligns + p->Align;\n\tnSettings++;\n\n\tps = CTParagraphStyleCreate(settings, nSettings);\n\tif (ps == NULL) {\n\t\t// TODO\n\t}\n\treturn ps;\n}\n\n// TODO either rename this (on all platforms) to uiprivDrawTextLayoutParams... or rename this file or both or split the struct or something else...\nCFAttributedStringRef uiprivAttributedStringToCFAttributedString(uiDrawTextLayoutParams *p, NSArray **backgroundParams)\n{\n\tCFStringRef cfstr;\n\tCFMutableDictionaryRef defaultAttrs;\n\tCTParagraphStyleRef ps;\n\tCFAttributedStringRef base;\n\tCFMutableAttributedStringRef mas;\n\tstruct foreachParams fep;\n\n\tcfstr = CFStringCreateWithCharacters(NULL, uiprivAttributedStringUTF16String(p->String), uiprivAttributedStringUTF16Len(p->String));\n\tif (cfstr == NULL) {\n\t\t// TODO\n\t}\n\tdefaultAttrs = CFDictionaryCreateMutable(NULL, 0,\n\t\t&kCFCopyStringDictionaryKeyCallBacks,\n\t\t&kCFTypeDictionaryValueCallBacks);\n\tif (defaultAttrs == NULL) {\n\t\t// TODO\n\t}\n#if 0 /* TODO */\n\tffp.desc = *(p->DefaultFont);\n\tdefaultCTFont = fontdescToCTFont(&ffp);\n\tCFDictionaryAddValue(defaultAttrs, kCTFontAttributeName, defaultCTFont);\n\tCFRelease(defaultCTFont);\n#endif\n\tps = mkParagraphStyle(p);\n\tCFDictionaryAddValue(defaultAttrs, kCTParagraphStyleAttributeName, ps);\n\tCFRelease(ps);\n\n\tbase = CFAttributedStringCreate(NULL, cfstr, defaultAttrs);\n\tif (base == NULL) {\n\t\t// TODO\n\t}\n\tCFRelease(cfstr);\n\tCFRelease(defaultAttrs);\n\tmas = CFAttributedStringCreateMutableCopy(NULL, 0, base);\n\tCFRelease(base);\n\n\tCFAttributedStringBeginEditing(mas);\n\tfep.mas = mas;\n\tfep.backgroundParams = [NSMutableArray new];\n\tuiAttributedStringForEachAttribute(p->String, processAttribute, &fep);\n\tapplyFontAttributes(mas, p->DefaultFont);\n\tCFAttributedStringEndEditing(mas);\n\n\t*backgroundParams = fep.backgroundParams;\n\treturn mas;\n}\n"
  },
  {
    "path": "darwin/autolayout.m",
    "content": "// 15 august 2015\n#import \"uipriv_darwin.h\"\n\nNSLayoutConstraint *uiprivMkConstraint(id view1, NSLayoutAttribute attr1, NSLayoutRelation relation, id view2, NSLayoutAttribute attr2, CGFloat multiplier, CGFloat c, NSString *desc)\n{\n\tNSLayoutConstraint *constraint;\n\n\tconstraint = [NSLayoutConstraint constraintWithItem:view1\n\t\tattribute:attr1\n\t\trelatedBy:relation\n\t\ttoItem:view2\n\t\tattribute:attr2\n\t\tmultiplier:multiplier\n\t\tconstant:c];\n\tuiprivFUTURE_NSLayoutConstraint_setIdentifier(constraint, desc);\n\treturn constraint;\n}\n\nCGFloat uiDarwinMarginAmount(void *reserved)\n{\n\treturn 20.0;\n}\n\nCGFloat uiDarwinPaddingAmount(void *reserved)\n{\n\treturn 8.0;\n}\n\n// 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)\n// turns out it also works on NSTabView and NSBox too, possibly others!\n// and for bonus points, it even seems to fix unsatisfiable-constraint-autoresizing-mask issues with NSTabView and NSBox too!!! this is nuts\nvoid uiprivJiggleViewLayout(NSView *view)\n{\n\t[view setNeedsLayout:YES];\n\t[view layoutSubtreeIfNeeded];\n}\n\nstatic CGFloat margins(int margined)\n{\n\tif (!margined)\n\t\treturn 0.0;\n\treturn uiDarwinMarginAmount(NULL);\n}\n\nvoid uiprivSingleChildConstraintsEstablish(uiprivSingleChildConstraints *c, NSView *contentView, NSView *childView, BOOL hugsTrailing, BOOL hugsBottom, int margined, NSString *desc)\n{\n\tCGFloat margin;\n\n\tmargin = margins(margined);\n\n\tc->leadingConstraint = uiprivMkConstraint(contentView, NSLayoutAttributeLeading,\n\t\tNSLayoutRelationEqual,\n\t\tchildView, NSLayoutAttributeLeading,\n\t\t1, -margin,\n\t\t[desc stringByAppendingString:@\" leading constraint\"]);\n\t[contentView addConstraint:c->leadingConstraint];\n\t[c->leadingConstraint retain];\n\n\tc->topConstraint = uiprivMkConstraint(contentView, NSLayoutAttributeTop,\n\t\tNSLayoutRelationEqual,\n\t\tchildView, NSLayoutAttributeTop,\n\t\t1, -margin,\n\t\t[desc stringByAppendingString:@\" top constraint\"]);\n\t[contentView addConstraint:c->topConstraint];\n\t[c->topConstraint retain];\n\n\tc->trailingConstraintGreater = uiprivMkConstraint(contentView, NSLayoutAttributeTrailing,\n\t\tNSLayoutRelationGreaterThanOrEqual,\n\t\tchildView, NSLayoutAttributeTrailing,\n\t\t1, margin,\n\t\t[desc stringByAppendingString:@\" trailing >= constraint\"]);\n\tif (hugsTrailing)\n\t\t[c->trailingConstraintGreater setPriority:NSLayoutPriorityDefaultLow];\n\t[contentView addConstraint:c->trailingConstraintGreater];\n\t[c->trailingConstraintGreater retain];\n\n\tc->trailingConstraintEqual = uiprivMkConstraint(contentView, NSLayoutAttributeTrailing,\n\t\tNSLayoutRelationEqual,\n\t\tchildView, NSLayoutAttributeTrailing,\n\t\t1, margin,\n\t\t[desc stringByAppendingString:@\" trailing == constraint\"]);\n\tif (!hugsTrailing)\n\t\t[c->trailingConstraintEqual setPriority:NSLayoutPriorityDefaultLow];\n\t[contentView addConstraint:c->trailingConstraintEqual];\n\t[c->trailingConstraintEqual retain];\n\n\tc->bottomConstraintGreater = uiprivMkConstraint(contentView, NSLayoutAttributeBottom,\n\t\tNSLayoutRelationGreaterThanOrEqual,\n\t\tchildView, NSLayoutAttributeBottom,\n\t\t1, margin,\n\t\t[desc stringByAppendingString:@\" bottom >= constraint\"]);\n\tif (hugsBottom)\n\t\t[c->bottomConstraintGreater setPriority:NSLayoutPriorityDefaultLow];\n\t[contentView addConstraint:c->bottomConstraintGreater];\n\t[c->bottomConstraintGreater retain];\n\n\tc->bottomConstraintEqual = uiprivMkConstraint(contentView, NSLayoutAttributeBottom,\n\t\tNSLayoutRelationEqual,\n\t\tchildView, NSLayoutAttributeBottom,\n\t\t1, margin,\n\t\t[desc stringByAppendingString:@\" bottom == constraint\"]);\n\tif (!hugsBottom)\n\t\t[c->bottomConstraintEqual setPriority:NSLayoutPriorityDefaultLow];\n\t[contentView addConstraint:c->bottomConstraintEqual];\n\t[c->bottomConstraintEqual retain];\n}\n\nvoid uiprivSingleChildConstraintsRemove(uiprivSingleChildConstraints *c, NSView *cv)\n{\n\tif (c->leadingConstraint != nil) {\n\t\t[cv removeConstraint:c->leadingConstraint];\n\t\t[c->leadingConstraint release];\n\t\tc->leadingConstraint = nil;\n\t}\n\tif (c->topConstraint != nil) {\n\t\t[cv removeConstraint:c->topConstraint];\n\t\t[c->topConstraint release];\n\t\tc->topConstraint = nil;\n\t}\n\tif (c->trailingConstraintGreater != nil) {\n\t\t[cv removeConstraint:c->trailingConstraintGreater];\n\t\t[c->trailingConstraintGreater release];\n\t\tc->trailingConstraintGreater = nil;\n\t}\n\tif (c->trailingConstraintEqual != nil) {\n\t\t[cv removeConstraint:c->trailingConstraintEqual];\n\t\t[c->trailingConstraintEqual release];\n\t\tc->trailingConstraintEqual = nil;\n\t}\n\tif (c->bottomConstraintGreater != nil) {\n\t\t[cv removeConstraint:c->bottomConstraintGreater];\n\t\t[c->bottomConstraintGreater release];\n\t\tc->bottomConstraintGreater = nil;\n\t}\n\tif (c->bottomConstraintEqual != nil) {\n\t\t[cv removeConstraint:c->bottomConstraintEqual];\n\t\t[c->bottomConstraintEqual release];\n\t\tc->bottomConstraintEqual = nil;\n\t}\n}\n\nvoid uiprivSingleChildConstraintsSetMargined(uiprivSingleChildConstraints *c, int margined)\n{\n\tCGFloat margin;\n\n\tmargin = margins(margined);\n\tif (c->leadingConstraint != nil)\n\t\t[c->leadingConstraint setConstant:-margin];\n\tif (c->topConstraint != nil)\n\t\t[c->topConstraint setConstant:-margin];\n\tif (c->trailingConstraintGreater != nil)\n\t\t[c->trailingConstraintGreater setConstant:margin];\n\tif (c->trailingConstraintEqual != nil)\n\t\t[c->trailingConstraintEqual setConstant:margin];\n\tif (c->bottomConstraintGreater != nil)\n\t\t[c->bottomConstraintGreater setConstant:margin];\n\tif (c->bottomConstraintEqual != nil)\n\t\t[c->bottomConstraintEqual setConstant:margin];\n}\n"
  },
  {
    "path": "darwin/box.m",
    "content": "// 15 august 2015\n#import \"uipriv_darwin.h\"\n\n// TODO hiding all stretchy controls still hugs trailing edge\n\n@interface boxChild : NSObject\n@property uiControl *c;\n@property BOOL stretchy;\n@property NSLayoutPriority oldPrimaryHuggingPri;\n@property NSLayoutPriority oldSecondaryHuggingPri;\n- (NSView *)view;\n@end\n\n@interface boxView : NSView {\n\tuiBox *b;\n\tNSMutableArray *children;\n\tBOOL vertical;\n\tint padded;\n\n\tNSLayoutConstraint *first;\n\tNSMutableArray *inBetweens;\n\tNSLayoutConstraint *last;\n\tNSMutableArray *otherConstraints;\n\n\tNSLayoutAttribute primaryStart;\n\tNSLayoutAttribute primaryEnd;\n\tNSLayoutAttribute secondaryStart;\n\tNSLayoutAttribute secondaryEnd;\n\tNSLayoutAttribute primarySize;\n\tNSLayoutConstraintOrientation primaryOrientation;\n\tNSLayoutConstraintOrientation secondaryOrientation;\n}\n- (id)initWithVertical:(BOOL)vert b:(uiBox *)bb;\n- (void)onDestroy;\n- (void)removeOurConstraints;\n- (void)syncEnableStates:(int)enabled;\n- (CGFloat)paddingAmount;\n- (void)establishOurConstraints;\n- (void)append:(uiControl *)c stretchy:(int)stretchy;\n- (void)delete:(int)n;\n- (int)isPadded;\n- (void)setPadded:(int)p;\n- (BOOL)hugsTrailing;\n- (BOOL)hugsBottom;\n- (int)nStretchy;\n@end\n\nstruct uiBox {\n\tuiDarwinControl c;\n\tboxView *view;\n};\n\n@implementation boxChild\n\n- (NSView *)view\n{\n\treturn (NSView *) uiControlHandle(self.c);\n}\n\n@end\n\n@implementation boxView\n\n- (id)initWithVertical:(BOOL)vert b:(uiBox *)bb\n{\n\tself = [super initWithFrame:NSZeroRect];\n\tif (self != nil) {\n\t\t// the weird names vert and bb are to shut the compiler up about shadowing because implicit this/self is stupid\n\t\tself->b = bb;\n\t\tself->vertical = vert;\n\t\tself->padded = 0;\n\t\tself->children = [NSMutableArray new];\n\n\t\tself->inBetweens = [NSMutableArray new];\n\t\tself->otherConstraints = [NSMutableArray new];\n\n\t\tif (self->vertical) {\n\t\t\tself->primaryStart = NSLayoutAttributeTop;\n\t\t\tself->primaryEnd = NSLayoutAttributeBottom;\n\t\t\tself->secondaryStart = NSLayoutAttributeLeading;\n\t\t\tself->secondaryEnd = NSLayoutAttributeTrailing;\n\t\t\tself->primarySize = NSLayoutAttributeHeight;\n\t\t\tself->primaryOrientation = NSLayoutConstraintOrientationVertical;\n\t\t\tself->secondaryOrientation = NSLayoutConstraintOrientationHorizontal;\n\t\t} else {\n\t\t\tself->primaryStart = NSLayoutAttributeLeading;\n\t\t\tself->primaryEnd = NSLayoutAttributeTrailing;\n\t\t\tself->secondaryStart = NSLayoutAttributeTop;\n\t\t\tself->secondaryEnd = NSLayoutAttributeBottom;\n\t\t\tself->primarySize = NSLayoutAttributeWidth;\n\t\t\tself->primaryOrientation = NSLayoutConstraintOrientationHorizontal;\n\t\t\tself->secondaryOrientation = NSLayoutConstraintOrientationVertical;\n\t\t}\n\t}\n\treturn self;\n}\n\n- (void)onDestroy\n{\n\tboxChild *bc;\n\n\t[self removeOurConstraints];\n\t[self->inBetweens release];\n\t[self->otherConstraints release];\n\n\tfor (bc in self->children) {\n\t\tuiControlSetParent(bc.c, NULL);\n\t\tuiDarwinControlSetSuperview(uiDarwinControl(bc.c), nil);\n\t\tuiControlDestroy(bc.c);\n\t}\n\t[self->children release];\n}\n\n- (void)removeOurConstraints\n{\n\tif (self->first != nil) {\n\t\t[self removeConstraint:self->first];\n\t\t[self->first release];\n\t\tself->first = nil;\n\t}\n\tif ([self->inBetweens count] != 0) {\n\t\t[self removeConstraints:self->inBetweens];\n\t\t[self->inBetweens removeAllObjects];\n\t}\n\tif (self->last != nil) {\n\t\t[self removeConstraint:self->last];\n\t\t[self->last release];\n\t\tself->last = nil;\n\t}\n\tif ([self->otherConstraints count] != 0) {\n\t\t[self removeConstraints:self->otherConstraints];\n\t\t[self->otherConstraints removeAllObjects];\n\t}\n}\n\n- (void)syncEnableStates:(int)enabled\n{\n\tboxChild *bc;\n\n\tfor (bc in self->children)\n\t\tuiDarwinControlSyncEnableState(uiDarwinControl(bc.c), enabled);\n}\n\n- (CGFloat)paddingAmount\n{\n\tif (!self->padded)\n\t\treturn 0.0;\n\treturn uiDarwinPaddingAmount(NULL);\n}\n\n- (void)establishOurConstraints\n{\n\tboxChild *bc;\n\tCGFloat padding;\n\tNSView *prev;\n\tNSLayoutConstraint *c;\n\tBOOL (*hugsSecondary)(uiDarwinControl *);\n\n\t[self removeOurConstraints];\n\tif ([self->children count] == 0)\n\t\treturn;\n\tpadding = [self paddingAmount];\n\n\t// first arrange in the primary direction\n\tprev = nil;\n\tfor (bc in self->children) {\n\t\tif (!uiControlVisible(bc.c))\n\t\t\tcontinue;\n\t\tif (prev == nil) {\t\t\t// first view\n\t\t\tself->first = uiprivMkConstraint(self, self->primaryStart,\n\t\t\t\tNSLayoutRelationEqual,\n\t\t\t\t[bc view], self->primaryStart,\n\t\t\t\t1, 0,\n\t\t\t\t@\"uiBox first primary constraint\");\n\t\t\t[self addConstraint:self->first];\n\t\t\t[self->first retain];\n\t\t\tprev = [bc view];\n\t\t\tcontinue;\n\t\t}\n\t\t// not the first; link it\n\t\tc = uiprivMkConstraint(prev, self->primaryEnd,\n\t\t\tNSLayoutRelationEqual,\n\t\t\t[bc view], self->primaryStart,\n\t\t\t1, -padding,\n\t\t\t@\"uiBox in-between primary constraint\");\n\t\t[self addConstraint:c];\n\t\t[self->inBetweens addObject:c];\n\t\tprev = [bc view];\n\t}\n\tif (prev == nil)\t\t// no control visible; act as if no controls\n\t\treturn;\n\tself->last = uiprivMkConstraint(prev, self->primaryEnd,\n\t\tNSLayoutRelationEqual,\n\t\tself, self->primaryEnd,\n\t\t1, 0,\n\t\t@\"uiBox last primary constraint\");\n\t[self addConstraint:self->last];\n\t[self->last retain];\n\n\t// then arrange in the secondary direction\n\thugsSecondary = uiDarwinControlHugsTrailingEdge;\n\tif (!self->vertical)\n\t\thugsSecondary = uiDarwinControlHugsBottom;\n\tfor (bc in self->children) {\n\t\tif (!uiControlVisible(bc.c))\n\t\t\tcontinue;\n\t\tc = uiprivMkConstraint(self, self->secondaryStart,\n\t\t\tNSLayoutRelationEqual,\n\t\t\t[bc view], self->secondaryStart,\n\t\t\t1, 0,\n\t\t\t@\"uiBox secondary start constraint\");\n\t\t[self addConstraint:c];\n\t\t[self->otherConstraints addObject:c];\n\t\tc = uiprivMkConstraint([bc view], self->secondaryEnd,\n\t\t\tNSLayoutRelationLessThanOrEqual,\n\t\t\tself, self->secondaryEnd,\n\t\t\t1, 0,\n\t\t\t@\"uiBox secondary end <= constraint\");\n\t\tif ((*hugsSecondary)(uiDarwinControl(bc.c)))\n\t\t\t[c setPriority:NSLayoutPriorityDefaultLow];\n\t\t[self addConstraint:c];\n\t\t[self->otherConstraints addObject:c];\n\t\tc = uiprivMkConstraint([bc view], self->secondaryEnd,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tself, self->secondaryEnd,\n\t\t\t1, 0,\n\t\t\t@\"uiBox secondary end == constraint\");\n\t\tif (!(*hugsSecondary)(uiDarwinControl(bc.c)))\n\t\t\t[c setPriority:NSLayoutPriorityDefaultLow];\n\t\t[self addConstraint:c];\n\t\t[self->otherConstraints addObject:c];\n\t}\n\n\t// and make all stretchy controls the same size\n\tif ([self nStretchy] == 0)\n\t\treturn;\n\tprev = nil;\t\t// first stretchy view\n\tfor (bc in self->children) {\n\t\tif (!uiControlVisible(bc.c))\n\t\t\tcontinue;\n\t\tif (!bc.stretchy)\n\t\t\tcontinue;\n\t\tif (prev == nil) {\n\t\t\tprev = [bc view];\n\t\t\tcontinue;\n\t\t}\n\t\tc = uiprivMkConstraint(prev, self->primarySize,\n\t\t\tNSLayoutRelationEqual,\n\t\t\t[bc view], self->primarySize,\n\t\t\t1, 0,\n\t\t\t@\"uiBox stretchy size constraint\");\n\t\t[self addConstraint:c];\n\t\t[self->otherConstraints addObject:c];\n\t}\n}\n\n- (void)append:(uiControl *)c stretchy:(int)stretchy\n{\n\tboxChild *bc;\n\tNSLayoutPriority priority;\n\tint oldnStretchy;\n\n\tbc = [boxChild new];\n\tbc.c = c;\n\tbc.stretchy = stretchy;\n\tbc.oldPrimaryHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(bc.c), self->primaryOrientation);\n\tbc.oldSecondaryHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(bc.c), self->secondaryOrientation);\n\n\tuiControlSetParent(bc.c, uiControl(self->b));\n\tuiDarwinControlSetSuperview(uiDarwinControl(bc.c), self);\n\tuiDarwinControlSyncEnableState(uiDarwinControl(bc.c), uiControlEnabledToUser(uiControl(self->b)));\n\n\t// if a control is stretchy, it should not hug in the primary direction\n\t// otherwise, it should *forcibly* hug\n\tif (bc.stretchy)\n\t\tpriority = NSLayoutPriorityDefaultLow;\n\telse\n\t\t// LONGTERM will default high work?\n\t\tpriority = NSLayoutPriorityRequired;\n\tuiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), priority, self->primaryOrientation);\n\t// make sure controls don't hug their secondary direction so they fill the width of the view\n\tuiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), NSLayoutPriorityDefaultLow, self->secondaryOrientation);\n\n\toldnStretchy = [self nStretchy];\n\t[self->children addObject:bc];\n\n\t[self establishOurConstraints];\n\tif (bc.stretchy)\n\t\tif (oldnStretchy == 0)\n\t\t\tuiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->b));\n\n\t[bc release];\t\t// we don't need the initial reference now\n}\n\n- (void)delete:(int)n\n{\n\tboxChild *bc;\n\tint stretchy;\n\n\tbc = (boxChild *) [self->children objectAtIndex:n];\n\tstretchy = bc.stretchy;\n\n\tuiControlSetParent(bc.c, NULL);\n\tuiDarwinControlSetSuperview(uiDarwinControl(bc.c), nil);\n\n\tuiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), bc.oldPrimaryHuggingPri, self->primaryOrientation);\n\tuiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), bc.oldSecondaryHuggingPri, self->secondaryOrientation);\n\n\t[self->children removeObjectAtIndex:n];\n\n\t[self establishOurConstraints];\n\tif (stretchy)\n\t\tif ([self nStretchy] == 0)\n\t\t\tuiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->b));\n}\n\n- (int)isPadded\n{\n\treturn self->padded;\n}\n\n- (void)setPadded:(int)p\n{\n\tCGFloat padding;\n\tNSLayoutConstraint *c;\n\n\tself->padded = p;\n\tpadding = [self paddingAmount];\n\tfor (c in self->inBetweens)\n\t\t[c setConstant:-padding];\n}\n\n- (BOOL)hugsTrailing\n{\n\tif (self->vertical)\t\t// always hug if vertical\n\t\treturn YES;\n\treturn [self nStretchy] != 0;\n}\n\n- (BOOL)hugsBottom\n{\n\tif (!self->vertical)\t\t// always hug if horizontal\n\t\treturn YES;\n\treturn [self nStretchy] != 0;\n}\n\n- (int)nStretchy\n{\n\tboxChild *bc;\n\tint n;\n\n\tn = 0;\n\tfor (bc in self->children) {\n\t\tif (!uiControlVisible(bc.c))\n\t\t\tcontinue;\n\t\tif (bc.stretchy)\n\t\t\tn++;\n\t}\n\treturn n;\n}\n\n@end\n\nstatic void uiBoxDestroy(uiControl *c)\n{\n\tuiBox *b = uiBox(c);\n\n\t[b->view onDestroy];\n\t[b->view release];\n\tuiFreeControl(uiControl(b));\n}\n\nuiDarwinControlDefaultHandle(uiBox, view)\nuiDarwinControlDefaultParent(uiBox, view)\nuiDarwinControlDefaultSetParent(uiBox, view)\nuiDarwinControlDefaultToplevel(uiBox, view)\nuiDarwinControlDefaultVisible(uiBox, view)\nuiDarwinControlDefaultShow(uiBox, view)\nuiDarwinControlDefaultHide(uiBox, view)\nuiDarwinControlDefaultEnabled(uiBox, view)\nuiDarwinControlDefaultEnable(uiBox, view)\nuiDarwinControlDefaultDisable(uiBox, view)\n\nstatic void uiBoxSyncEnableState(uiDarwinControl *c, int enabled)\n{\n\tuiBox *b = uiBox(c);\n\n\tif (uiDarwinShouldStopSyncEnableState(uiDarwinControl(b), enabled))\n\t\treturn;\n\t[b->view syncEnableStates:enabled];\n}\n\nuiDarwinControlDefaultSetSuperview(uiBox, view)\n\nstatic BOOL uiBoxHugsTrailingEdge(uiDarwinControl *c)\n{\n\tuiBox *b = uiBox(c);\n\n\treturn [b->view hugsTrailing];\n}\n\nstatic BOOL uiBoxHugsBottom(uiDarwinControl *c)\n{\n\tuiBox *b = uiBox(c);\n\n\treturn [b->view hugsBottom];\n}\n\nstatic void uiBoxChildEdgeHuggingChanged(uiDarwinControl *c)\n{\n\tuiBox *b = uiBox(c);\n\n\t[b->view establishOurConstraints];\n}\n\nuiDarwinControlDefaultHuggingPriority(uiBox, view)\nuiDarwinControlDefaultSetHuggingPriority(uiBox, view)\n\nstatic void uiBoxChildVisibilityChanged(uiDarwinControl *c)\n{\n\tuiBox *b = uiBox(c);\n\n\t[b->view establishOurConstraints];\n}\n\nvoid uiBoxAppend(uiBox *b, uiControl *c, int stretchy)\n{\n\t// LONGTERM on other platforms\n\t// or at leat allow this and implicitly turn it into a spacer\n\tif (c == NULL)\n\t\tuiprivUserBug(\"You cannot add NULL to a uiBox.\");\n\t[b->view append:c stretchy:stretchy];\n}\n\nvoid uiBoxDelete(uiBox *b, int n)\n{\n\t[b->view delete:n];\n}\n\nint uiBoxPadded(uiBox *b)\n{\n\treturn [b->view isPadded];\n}\n\nvoid uiBoxSetPadded(uiBox *b, int padded)\n{\n\t[b->view setPadded:padded];\n}\n\nstatic uiBox *finishNewBox(BOOL vertical)\n{\n\tuiBox *b;\n\n\tuiDarwinNewControl(uiBox, b);\n\n\tb->view = [[boxView alloc] initWithVertical:vertical b:b];\n\n\treturn b;\n}\n\nuiBox *uiNewHorizontalBox(void)\n{\n\treturn finishNewBox(NO);\n}\n\nuiBox *uiNewVerticalBox(void)\n{\n\treturn finishNewBox(YES);\n}\n"
  },
  {
    "path": "darwin/button.m",
    "content": "// 13 august 2015\n#import \"uipriv_darwin.h\"\n\nstruct uiButton {\n\tuiDarwinControl c;\n\tNSButton *button;\n\tvoid (*onClicked)(uiButton *, void *);\n\tvoid *onClickedData;\n};\n\n@interface buttonDelegateClass : NSObject {\n\tuiprivMap *buttons;\n}\n- (IBAction)onClicked:(id)sender;\n- (void)registerButton:(uiButton *)b;\n- (void)unregisterButton:(uiButton *)b;\n@end\n\n@implementation buttonDelegateClass\n\n- (id)init\n{\n\tself = [super init];\n\tif (self)\n\t\tself->buttons = uiprivNewMap();\n\treturn self;\n}\n\n- (void)dealloc\n{\n\tuiprivMapDestroy(self->buttons);\n\t[super dealloc];\n}\n\n- (IBAction)onClicked:(id)sender\n{\n\tuiButton *b;\n\n\tb = (uiButton *) uiprivMapGet(self->buttons, sender);\n\t(*(b->onClicked))(b, b->onClickedData);\n}\n\n- (void)registerButton:(uiButton *)b\n{\n\tuiprivMapSet(self->buttons, b->button, b);\n\t[b->button setTarget:self];\n\t[b->button setAction:@selector(onClicked:)];\n}\n\n- (void)unregisterButton:(uiButton *)b\n{\n\t[b->button setTarget:nil];\n\tuiprivMapDelete(self->buttons, b->button);\n}\n\n@end\n\nstatic buttonDelegateClass *buttonDelegate = nil;\n\nuiDarwinControlAllDefaultsExceptDestroy(uiButton, button)\n\nstatic void uiButtonDestroy(uiControl *c)\n{\n\tuiButton *b = uiButton(c);\n\n\t[buttonDelegate unregisterButton:b];\n\t[b->button release];\n\tuiFreeControl(uiControl(b));\n}\n\nchar *uiButtonText(uiButton *b)\n{\n\treturn uiDarwinNSStringToText([b->button title]);\n}\n\nvoid uiButtonSetText(uiButton *b, const char *text)\n{\n\t[b->button setTitle:uiprivToNSString(text)];\n}\n\nvoid uiButtonOnClicked(uiButton *b, void (*f)(uiButton *, void *), void *data)\n{\n\tb->onClicked = f;\n\tb->onClickedData = data;\n}\n\nstatic void defaultOnClicked(uiButton *b, void *data)\n{\n\t// do nothing\n}\n\nuiButton *uiNewButton(const char *text)\n{\n\tuiButton *b;\n\n\tuiDarwinNewControl(uiButton, b);\n\n\tb->button = [[NSButton alloc] initWithFrame:NSZeroRect];\n\t[b->button setTitle:uiprivToNSString(text)];\n\t[b->button setButtonType:NSMomentaryPushInButton];\n\t[b->button setBordered:YES];\n\t[b->button setBezelStyle:NSRoundedBezelStyle];\n\tuiDarwinSetControlFont(b->button, NSRegularControlSize);\n\n\tif (buttonDelegate == nil) {\n\t\tbuttonDelegate = [[buttonDelegateClass new] autorelease];\n\t\t[uiprivDelegates addObject:buttonDelegate];\n\t}\n\t[buttonDelegate registerButton:b];\n\tuiButtonOnClicked(b, defaultOnClicked, NULL);\n\n\treturn b;\n}\n"
  },
  {
    "path": "darwin/checkbox.m",
    "content": "// 14 august 2015\n#import \"uipriv_darwin.h\"\n\nstruct uiCheckbox {\n\tuiDarwinControl c;\n\tNSButton *button;\n\tvoid (*onToggled)(uiCheckbox *, void *);\n\tvoid *onToggledData;\n};\n\n@interface checkboxDelegateClass : NSObject {\n\tuiprivMap *buttons;\n}\n- (IBAction)onToggled:(id)sender;\n- (void)registerCheckbox:(uiCheckbox *)c;\n- (void)unregisterCheckbox:(uiCheckbox *)c;\n@end\n\n@implementation checkboxDelegateClass\n\n- (id)init\n{\n\tself = [super init];\n\tif (self)\n\t\tself->buttons = uiprivNewMap();\n\treturn self;\n}\n\n- (void)dealloc\n{\n\tuiprivMapDestroy(self->buttons);\n\t[super dealloc];\n}\n\n- (IBAction)onToggled:(id)sender\n{\n\tuiCheckbox *c;\n\n\tc = (uiCheckbox *) uiprivMapGet(self->buttons, sender);\n\t(*(c->onToggled))(c, c->onToggledData);\n}\n\n- (void)registerCheckbox:(uiCheckbox *)c\n{\n\tuiprivMapSet(self->buttons, c->button, c);\n\t[c->button setTarget:self];\n\t[c->button setAction:@selector(onToggled:)];\n}\n\n- (void)unregisterCheckbox:(uiCheckbox *)c\n{\n\t[c->button setTarget:nil];\n\tuiprivMapDelete(self->buttons, c->button);\n}\n\n@end\n\nstatic checkboxDelegateClass *checkboxDelegate = nil;\n\nuiDarwinControlAllDefaultsExceptDestroy(uiCheckbox, button)\n\nstatic void uiCheckboxDestroy(uiControl *cc)\n{\n\tuiCheckbox *c = uiCheckbox(cc);\n\n\t[checkboxDelegate unregisterCheckbox:c];\n\t[c->button release];\n\tuiFreeControl(uiControl(c));\n}\n\nchar *uiCheckboxText(uiCheckbox *c)\n{\n\treturn uiDarwinNSStringToText([c->button title]);\n}\n\nvoid uiCheckboxSetText(uiCheckbox *c, const char *text)\n{\n\t[c->button setTitle:uiprivToNSString(text)];\n}\n\nvoid uiCheckboxOnToggled(uiCheckbox *c, void (*f)(uiCheckbox *, void *), void *data)\n{\n\tc->onToggled = f;\n\tc->onToggledData = data;\n}\n\nint uiCheckboxChecked(uiCheckbox *c)\n{\n\treturn [c->button state] == NSOnState;\n}\n\nvoid uiCheckboxSetChecked(uiCheckbox *c, int checked)\n{\n\tNSInteger state;\n\n\tstate = NSOnState;\n\tif (!checked)\n\t\tstate = NSOffState;\n\t[c->button setState:state];\n}\n\nstatic void defaultOnToggled(uiCheckbox *c, void *data)\n{\n\t// do nothing\n}\n\nuiCheckbox *uiNewCheckbox(const char *text)\n{\n\tuiCheckbox *c;\n\n\tuiDarwinNewControl(uiCheckbox, c);\n\n\tc->button = [[NSButton alloc] initWithFrame:NSZeroRect];\n\t[c->button setTitle:uiprivToNSString(text)];\n\t[c->button setButtonType:NSSwitchButton];\n\t// doesn't seem to have an associated bezel style\n\t[c->button setBordered:NO];\n\t[c->button setTransparent:NO];\n\tuiDarwinSetControlFont(c->button, NSRegularControlSize);\n\n\tif (checkboxDelegate == nil) {\n\t\tcheckboxDelegate = [[checkboxDelegateClass new] autorelease];\n\t\t[uiprivDelegates addObject:checkboxDelegate];\n\t}\n\t[checkboxDelegate registerCheckbox:c];\n\tuiCheckboxOnToggled(c, defaultOnToggled, NULL);\n\n\treturn c;\n}\n"
  },
  {
    "path": "darwin/colorbutton.m",
    "content": "// 15 may 2016\n#import \"uipriv_darwin.h\"\n\n// TODO no intrinsic height?\n\n@interface colorButton : NSColorWell {\n\tuiColorButton *libui_b;\n\tBOOL libui_changing;\n\tBOOL libui_setting;\n}\n- (id)initWithFrame:(NSRect)frame libuiColorButton:(uiColorButton *)b;\n- (void)deactivateOnClose:(NSNotification *)note;\n- (void)libuiColor:(double *)r g:(double *)g b:(double *)b a:(double *)a;\n- (void)libuiSetColor:(double)r g:(double)g b:(double)b a:(double)a;\n@end\n\n// only one may be active at one time\nstatic colorButton *activeColorButton = nil;\n\nstruct uiColorButton {\n\tuiDarwinControl c;\n\tcolorButton *button;\n\tvoid (*onChanged)(uiColorButton *, void *);\n\tvoid *onChangedData;\n};\n\n@implementation colorButton\n\n- (id)initWithFrame:(NSRect)frame libuiColorButton:(uiColorButton *)b\n{\n\tself = [super initWithFrame:frame];\n\tif (self) {\n\t\t// the default color is white; set it to black first (see -setColor: below for why we do it first)\n\t\t[self libuiSetColor:0.0 g:0.0 b:0.0 a:1.0];\n\n\t\tself->libui_b = b;\n\t\tself->libui_changing = NO;\n\t}\n\treturn self;\n}\n\n- (void)activate:(BOOL)exclusive\n{\n\tif (activeColorButton != nil)\n\t\tactiveColorButton->libui_changing = YES;\n\t[NSColorPanel setPickerMask:NSColorPanelAllModesMask];\n\t[[NSColorPanel sharedColorPanel] setShowsAlpha:YES];\n\t[super activate:YES];\n\tactiveColorButton = self;\n\t// see stddialogs.m for details\n\t[[NSColorPanel sharedColorPanel] setWorksWhenModal:NO];\n\t[[NSNotificationCenter defaultCenter] addObserver:self\n\t\tselector:@selector(deactivateOnClose:)\n\t\tname:NSWindowWillCloseNotification\n\t\tobject:[NSColorPanel sharedColorPanel]];\n}\n\n- (void)deactivate\n{\n\t[super deactivate];\n\tactiveColorButton = nil;\n\tif (!self->libui_changing)\n\t\t[[NSColorPanel sharedColorPanel] orderOut:nil];\n\t[[NSNotificationCenter defaultCenter] removeObserver:self\n\t\tname:NSWindowWillCloseNotification\n\t\tobject:[NSColorPanel sharedColorPanel]];\n\tself->libui_changing = NO;\n}\n\n- (void)deactivateOnClose:(NSNotification *)note\n{\n\t[self deactivate];\n}\n\n- (void)setColor:(NSColor *)color\n{\n\tuiColorButton *b = self->libui_b;\n\n\t[super setColor:color];\n\t// this is called by NSColorWell's init, so we have to guard\n\t// also don't signal during a programmatic change\n\tif (b != nil && !self->libui_setting)\n\t\t(*(b->onChanged))(b, b->onChangedData);\n}\n\n- (void)libuiColor:(double *)r g:(double *)g b:(double *)b a:(double *)a\n{\n\tNSColor *rgba;\n\tCGFloat cr, cg, cb, ca;\n\n\t// the given color may not be an RGBA color, which will cause the -getRed:green:blue:alpha: call to throw an exception\n\trgba = [[self color] colorUsingColorSpace:[NSColorSpace sRGBColorSpace]];\n\t[rgba getRed:&cr green:&cg blue:&cb alpha:&ca];\n\t*r = cr;\n\t*g = cg;\n\t*b = cb;\n\t*a = ca;\n\t// rgba will be autoreleased since it isn't a new or init call\n}\n\n- (void)libuiSetColor:(double)r g:(double)g b:(double)b a:(double)a\n{\n\tself->libui_setting = YES;\n\t[self setColor:[NSColor colorWithSRGBRed:r green:g blue:b alpha:a]];\n\tself->libui_setting = NO;\n}\n\n// NSColorWell has no intrinsic size by default; give it the default Interface Builder size.\n- (NSSize)intrinsicContentSize\n{\n\treturn NSMakeSize(44, 23);\n}\n\n@end\n\nuiDarwinControlAllDefaults(uiColorButton, button)\n\n// we do not want color change events to be sent to any controls other than the color buttons\n// see main.m for more details\nBOOL uiprivColorButtonInhibitSendAction(SEL sel, id from, id to)\n{\n\tif (sel != @selector(changeColor:))\n\t\treturn NO;\n\treturn ![to isKindOfClass:[colorButton class]];\n}\n\nstatic void defaultOnChanged(uiColorButton *b, void *data)\n{\n\t// do nothing\n}\n\nvoid uiColorButtonColor(uiColorButton *b, double *r, double *g, double *bl, double *a)\n{\n\t[b->button libuiColor:r g:g b:bl a:a];\n}\n\nvoid uiColorButtonSetColor(uiColorButton *b, double r, double g, double bl, double a)\n{\n\t[b->button libuiSetColor:r g:g b:bl a:a];\n}\n\nvoid uiColorButtonOnChanged(uiColorButton *b, void (*f)(uiColorButton *, void *), void *data)\n{\n\tb->onChanged = f;\n\tb->onChangedData = data;\n}\n\nuiColorButton *uiNewColorButton(void)\n{\n\tuiColorButton *b;\n\n\tuiDarwinNewControl(uiColorButton, b);\n\n\tb->button = [[colorButton alloc] initWithFrame:NSZeroRect libuiColorButton:b];\n\n\tuiColorButtonOnChanged(b, defaultOnChanged, NULL);\n\n\treturn b;\n}\n"
  },
  {
    "path": "darwin/combobox.m",
    "content": "// 14 august 2015\n#import \"uipriv_darwin.h\"\n\n// NSComboBoxes have no intrinsic width; we'll use the default Interface Builder width for them.\n// NSPopUpButton is fine.\n#define comboboxWidth 96\n\nstruct uiCombobox {\n\tuiDarwinControl c;\n\tNSPopUpButton *pb;\n\tNSArrayController *pbac;\n\tvoid (*onSelected)(uiCombobox *, void *);\n\tvoid *onSelectedData;\n};\n\n@interface comboboxDelegateClass : NSObject {\n\tuiprivMap *comboboxes;\n}\n- (IBAction)onSelected:(id)sender;\n- (void)registerCombobox:(uiCombobox *)c;\n- (void)unregisterCombobox:(uiCombobox *)c;\n@end\n\n@implementation comboboxDelegateClass\n\n- (id)init\n{\n\tself = [super init];\n\tif (self)\n\t\tself->comboboxes = uiprivNewMap();\n\treturn self;\n}\n\n- (void)dealloc\n{\n\tuiprivMapDestroy(self->comboboxes);\n\t[super dealloc];\n}\n\n- (IBAction)onSelected:(id)sender\n{\n\tuiCombobox *c;\n\n\tc = uiCombobox(uiprivMapGet(self->comboboxes, sender));\n\t(*(c->onSelected))(c, c->onSelectedData);\n}\n\n- (void)registerCombobox:(uiCombobox *)c\n{\n\tuiprivMapSet(self->comboboxes, c->pb, c);\n\t[c->pb setTarget:self];\n\t[c->pb setAction:@selector(onSelected:)];\n}\n\n- (void)unregisterCombobox:(uiCombobox *)c\n{\n\t[c->pb setTarget:nil];\n\tuiprivMapDelete(self->comboboxes, c->pb);\n}\n\n@end\n\nstatic comboboxDelegateClass *comboboxDelegate = nil;\n\nuiDarwinControlAllDefaultsExceptDestroy(uiCombobox, pb)\n\nstatic void uiComboboxDestroy(uiControl *cc)\n{\n\tuiCombobox *c = uiCombobox(cc);\n\n\t[comboboxDelegate unregisterCombobox:c];\n\t[c->pb unbind:@\"contentObjects\"];\n\t[c->pb unbind:@\"selectedIndex\"];\n\t[c->pbac release];\n\t[c->pb release];\n\tuiFreeControl(uiControl(c));\n}\n\nvoid uiComboboxAppend(uiCombobox *c, const char *text)\n{\n\t[c->pbac addObject:uiprivToNSString(text)];\n}\n\nint uiComboboxSelected(uiCombobox *c)\n{\n\treturn [c->pb indexOfSelectedItem];\n}\n\nvoid uiComboboxSetSelected(uiCombobox *c, int n)\n{\n\t[c->pb selectItemAtIndex:n];\n}\n\nvoid uiComboboxOnSelected(uiCombobox *c, void (*f)(uiCombobox *c, void *data), void *data)\n{\n\tc->onSelected = f;\n\tc->onSelectedData = data;\n}\n\nstatic void defaultOnSelected(uiCombobox *c, void *data)\n{\n\t// do nothing\n}\n\nuiCombobox *uiNewCombobox(void)\n{\n\tuiCombobox *c;\n\tNSPopUpButtonCell *pbcell;\n\n\tuiDarwinNewControl(uiCombobox, c);\n\n\tc->pb = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO];\n\t[c->pb setPreferredEdge:NSMinYEdge];\n\tpbcell = (NSPopUpButtonCell *) [c->pb cell];\n\t[pbcell setArrowPosition:NSPopUpArrowAtBottom];\n\t// the font defined by Interface Builder is Menu 13, which is lol\n\t// just use the regular control size for consistency\n\tuiDarwinSetControlFont(c->pb, NSRegularControlSize);\n\n\t// NSPopUpButton doesn't work like a combobox\n\t// - it automatically selects the first item\n\t// - it doesn't support duplicates\n\t// but we can use a NSArrayController and Cocoa bindings to bypass these restrictions\n\tc->pbac = [NSArrayController new];\n\t[c->pbac setAvoidsEmptySelection:NO];\n\t[c->pbac setSelectsInsertedObjects:NO];\n\t[c->pbac setAutomaticallyRearrangesObjects:NO];\n\t[c->pb bind:@\"contentValues\"\n\t\ttoObject:c->pbac\n\t\twithKeyPath:@\"arrangedObjects\"\n\t\toptions:nil];\n\t[c->pb bind:@\"selectedIndex\"\n\t\ttoObject:c->pbac\n\t\twithKeyPath:@\"selectionIndex\"\n\t\toptions:nil];\n\n\tif (comboboxDelegate == nil) {\n\t\tcomboboxDelegate = [[comboboxDelegateClass new] autorelease];\n\t\t[uiprivDelegates addObject:comboboxDelegate];\n\t}\n\t[comboboxDelegate registerCombobox:c];\n\tuiComboboxOnSelected(c, defaultOnSelected, NULL);\n\n\treturn c;\n}\n"
  },
  {
    "path": "darwin/control.m",
    "content": "// 16 august 2015\n#import \"uipriv_darwin.h\"\n\nvoid uiDarwinControlSyncEnableState(uiDarwinControl *c, int state)\n{\n\t(*(c->SyncEnableState))(c, state);\n}\n\nvoid uiDarwinControlSetSuperview(uiDarwinControl *c, NSView *superview)\n{\n\t(*(c->SetSuperview))(c, superview);\n}\n\nBOOL uiDarwinControlHugsTrailingEdge(uiDarwinControl *c)\n{\n\treturn (*(c->HugsTrailingEdge))(c);\n}\n\nBOOL uiDarwinControlHugsBottom(uiDarwinControl *c)\n{\n\treturn (*(c->HugsBottom))(c);\n}\n\nvoid uiDarwinControlChildEdgeHuggingChanged(uiDarwinControl *c)\n{\n\t(*(c->ChildEdgeHuggingChanged))(c);\n}\n\nNSLayoutPriority uiDarwinControlHuggingPriority(uiDarwinControl *c, NSLayoutConstraintOrientation orientation)\n{\n\treturn (*(c->HuggingPriority))(c, orientation);\n}\n\nvoid uiDarwinControlSetHuggingPriority(uiDarwinControl *c, NSLayoutPriority priority, NSLayoutConstraintOrientation orientation)\n{\n\t(*(c->SetHuggingPriority))(c, priority, orientation);\n}\n\nvoid uiDarwinControlChildVisibilityChanged(uiDarwinControl *c)\n{\n\t(*(c->ChildVisibilityChanged))(c);\n}\n\nvoid uiDarwinSetControlFont(NSControl *c, NSControlSize size)\n{\n\t[c setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:size]]];\n}\n\n#define uiDarwinControlSignature 0x44617277\n\nuiDarwinControl *uiDarwinAllocControl(size_t n, uint32_t typesig, const char *typenamestr)\n{\n\treturn uiDarwinControl(uiAllocControl(n, uiDarwinControlSignature, typesig, typenamestr));\n}\n\nBOOL uiDarwinShouldStopSyncEnableState(uiDarwinControl *c, BOOL enabled)\n{\n\tint ce;\n\n\tce = uiControlEnabled(uiControl(c));\n\t// only stop if we're going from disabled back to enabled; don't stop under any other condition\n\t// (if we stop when going from enabled to disabled then enabled children of a disabled control won't get disabled at the OS level)\n\tif (!ce && enabled)\n\t\treturn YES;\n\treturn NO;\n}\n\nvoid uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl *c)\n{\n\tuiControl *parent;\n\n\tparent = uiControlParent(uiControl(c));\n\tif (parent != NULL)\n\t\tuiDarwinControlChildEdgeHuggingChanged(uiDarwinControl(parent));\n}\n\nvoid uiDarwinNotifyVisibilityChanged(uiDarwinControl *c)\n{\n\tuiControl *parent;\n\n\tparent = uiControlParent(uiControl(c));\n\tif (parent != NULL)\n\t\tuiDarwinControlChildVisibilityChanged(uiDarwinControl(parent));\n}\n"
  },
  {
    "path": "darwin/datetimepicker.m",
    "content": "// 14 august 2015\n#import \"uipriv_darwin.h\"\n\nstruct uiDateTimePicker {\n\tuiDarwinControl c;\n\tNSDatePicker *dp;\n\tvoid (*onChanged)(uiDateTimePicker *, void *);\n\tvoid *onChangedData;\n\tBOOL blockSendOnce;\n};\n\n// TODO see if target-action works here or not; I forgot what cody271@ originally said\n// 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\n@interface uiprivDatePickerDelegateClass : NSObject<NSDatePickerCellDelegate> {\n\tuiprivMap *pickers;\n}\n- (void)datePickerCell:(NSDatePickerCell *)aDatePickerCell validateProposedDateValue:(NSDate **)proposedDateValue timeInterval:(NSTimeInterval *)proposedTimeInterval;\n- (void)doTimer:(NSTimer *)timer;\n- (void)registerPicker:(uiDateTimePicker *)d;\n- (void)unregisterPicker:(uiDateTimePicker *)d;\n@end\n\n@implementation uiprivDatePickerDelegateClass\n\n- (id)init\n{\n\tself = [super init];\n\tif (self)\n\t\tself->pickers = uiprivNewMap();\n\treturn self;\n}\n\n- (void)dealloc\n{\n\tuiprivMapDestroy(self->pickers);\n\t[super dealloc];\n}\n\n- (void)datePickerCell:(NSDatePickerCell *)cell validateProposedDateValue:(NSDate **)proposedDateValue timeInterval:(NSTimeInterval *)proposedTimeInterval\n{\n\tuiDateTimePicker *d;\n\n\td = (uiDateTimePicker *) uiprivMapGet(self->pickers, cell);\n\t[NSTimer scheduledTimerWithTimeInterval:0\n\t\ttarget:self\n\t\tselector:@selector(doTimer:)\n\t\tuserInfo:[NSValue valueWithPointer:d]\n\t\trepeats:NO];\n}\n\n- (void)doTimer:(NSTimer *)timer\n{\n\tNSValue *v;\n\tuiDateTimePicker *d;\n\n\tv = (NSValue *) [timer userInfo];\n\td = (uiDateTimePicker *) [v pointerValue];\n\tif (d->blockSendOnce) {\n\t\td->blockSendOnce = NO;\n\t\treturn;\n\t}\n\t(*(d->onChanged))(d, d->onChangedData);\n}\n\n- (void)registerPicker:(uiDateTimePicker *)d\n{\n\tuiprivMapSet(self->pickers, d->dp.cell, d);\n\t[d->dp setDelegate:self];\n}\n\n- (void)unregisterPicker:(uiDateTimePicker *)d\n{\n\t[d->dp setDelegate:nil];\n\tuiprivMapDelete(self->pickers, d->dp.cell);\n}\n\n@end\n\nstatic uiprivDatePickerDelegateClass *datePickerDelegate = nil;\n\nuiDarwinControlAllDefaultsExceptDestroy(uiDateTimePicker, dp)\n\nstatic void uiDateTimePickerDestroy(uiControl *c)\n{\n\tuiDateTimePicker *d = uiDateTimePicker(c);\n\n\t[datePickerDelegate unregisterPicker:d];\n\t[d->dp release];\n\tuiFreeControl(uiControl(d));\n}\n\nstatic void defaultOnChanged(uiDateTimePicker *d, void *data)\n{\n\t// do nothing\n}\n\n// TODO consider using NSDateComponents iff we ever need the extra accuracy of not using NSTimeInterval\nvoid uiDateTimePickerTime(uiDateTimePicker *d, struct tm *time)\n{\n\ttime_t t;\n\tstruct tm tmbuf;\n\tNSDate *date;\n\n\tdate = [d->dp dateValue];\n\tt = (time_t) [date timeIntervalSince1970];\n\n\t// Copy time to minimize a race condition\n\t// time.h functions use global non-thread-safe data\n\ttmbuf = *localtime(&t);\n\tmemcpy(time, &tmbuf, sizeof (struct tm));\n}\n\nvoid uiDateTimePickerSetTime(uiDateTimePicker *d, const struct tm *time)\n{\n\ttime_t t;\n\tstruct tm tmbuf;\n\n\t// Copy time because mktime() modifies its argument\n\tmemcpy(&tmbuf, time, sizeof (struct tm));\n\tt = mktime(&tmbuf);\n\n\t// TODO get rid of the need for this\n\td->blockSendOnce = YES;\n\t[d->dp setDateValue:[NSDate dateWithTimeIntervalSince1970:t]];\n}\n\nvoid uiDateTimePickerOnChanged(uiDateTimePicker *d, void (*f)(uiDateTimePicker *, void *), void *data)\n{\n\td->onChanged = f;\n\td->onChangedData = data;\n}\n\nstatic uiDateTimePicker *finishNewDateTimePicker(NSDatePickerElementFlags elements)\n{\n\tuiDateTimePicker *d;\n\n\tuiDarwinNewControl(uiDateTimePicker, d);\n\n\td->dp = [[NSDatePicker alloc] initWithFrame:NSZeroRect];\n\t[d->dp setDateValue:[NSDate date]];\n\t[d->dp setBordered:NO];\n\t[d->dp setBezeled:YES];\n\t[d->dp setDrawsBackground:YES];\n\t[d->dp setDatePickerStyle:NSTextFieldAndStepperDatePickerStyle];\n\t[d->dp setDatePickerElements:elements];\n\t[d->dp setDatePickerMode:NSSingleDateMode];\n\tuiDarwinSetControlFont(d->dp, NSRegularControlSize);\n\n\tif (datePickerDelegate == nil) {\n\t\tdatePickerDelegate = [[uiprivDatePickerDelegateClass new] autorelease];\n\t\t[uiprivDelegates addObject:datePickerDelegate];\n\t}\n\t[datePickerDelegate registerPicker:d];\n\tuiDateTimePickerOnChanged(d, defaultOnChanged, NULL);\n\n\treturn d;\n}\n\nuiDateTimePicker *uiNewDateTimePicker(void)\n{\n\treturn finishNewDateTimePicker(NSYearMonthDayDatePickerElementFlag | NSHourMinuteSecondDatePickerElementFlag);\n}\n\nuiDateTimePicker *uiNewDatePicker(void)\n{\n\treturn finishNewDateTimePicker(NSYearMonthDayDatePickerElementFlag);\n}\n\nuiDateTimePicker *uiNewTimePicker(void)\n{\n\treturn finishNewDateTimePicker(NSHourMinuteSecondDatePickerElementFlag);\n}\n"
  },
  {
    "path": "darwin/debug.m",
    "content": "// 13 may 2016\n#import \"uipriv_darwin.h\"\n\n// LONGTERM don't halt on release builds\n\nvoid uiprivRealBug(const char *file, const char *line, const char *func, const char *prefix, const char *format, va_list ap)\n{\n\tNSMutableString *str;\n\tNSString *formatted;\n\n\tstr = [NSMutableString new];\n\t[str appendString:[NSString stringWithFormat:@\"[libui] %s:%s:%s() %s\", file, line, func, prefix]];\n\tformatted = [[NSString alloc] initWithFormat:[NSString stringWithUTF8String:format] arguments:ap];\n\t[str appendString:formatted];\n\t[formatted release];\n\tNSLog(@\"%@\", str);\n\t[str release];\n\t__builtin_trap();\n}\n"
  },
  {
    "path": "darwin/draw.h",
    "content": "// 6 january 2017\n\n// TODO why do we still have this file; should we just split draw.m or not\n\nstruct uiDrawContext {\n\tCGContextRef c;\n\tCGFloat height;\t\t\t\t// needed for text; see below\n};\n"
  },
  {
    "path": "darwin/draw.m",
    "content": "// 6 september 2015\n#import \"uipriv_darwin.h\"\n#import \"draw.h\"\n\nstruct uiDrawPath {\n\tCGMutablePathRef path;\n\tuiDrawFillMode fillMode;\n\tBOOL ended;\n};\n\nuiDrawPath *uiDrawNewPath(uiDrawFillMode mode)\n{\n\tuiDrawPath *p;\n\n\tp = uiprivNew(uiDrawPath);\n\tp->path = CGPathCreateMutable();\n\tp->fillMode = mode;\n\treturn p;\n}\n\nvoid uiDrawFreePath(uiDrawPath *p)\n{\n\tCGPathRelease((CGPathRef) (p->path));\n\tuiprivFree(p);\n}\n\nvoid uiDrawPathNewFigure(uiDrawPath *p, double x, double y)\n{\n\tif (p->ended)\n\t\tuiprivUserBug(\"You cannot call uiDrawPathNewFigure() on a uiDrawPath that has already been ended. (path; %p)\", p);\n\tCGPathMoveToPoint(p->path, NULL, x, y);\n}\n\nvoid uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)\n{\n\tdouble sinStart, cosStart;\n\tdouble startx, starty;\n\n\tif (p->ended)\n\t\tuiprivUserBug(\"You cannot call uiDrawPathNewFigureWithArc() on a uiDrawPath that has already been ended. (path; %p)\", p);\n\tsinStart = sin(startAngle);\n\tcosStart = cos(startAngle);\n\tstartx = xCenter + radius * cosStart;\n\tstarty = yCenter + radius * sinStart;\n\tCGPathMoveToPoint(p->path, NULL, startx, starty);\n\tuiDrawPathArcTo(p, xCenter, yCenter, radius, startAngle, sweep, negative);\n}\n\nvoid uiDrawPathLineTo(uiDrawPath *p, double x, double y)\n{\n\t// TODO refine this to require being in a path\n\tif (p->ended)\n\t\tuiprivImplBug(\"attempt to add line to ended path in uiDrawPathLineTo()\");\n\tCGPathAddLineToPoint(p->path, NULL, x, y);\n}\n\nvoid uiDrawPathArcTo(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)\n{\n\tbool cw;\n\n\t// TODO likewise\n\tif (p->ended)\n\t\tuiprivImplBug(\"attempt to add arc to ended path in uiDrawPathArcTo()\");\n\tif (sweep > 2 * uiPi)\n\t\tsweep = 2 * uiPi;\n\tcw = false;\n\tif (negative)\n\t\tcw = true;\n\tCGPathAddArc(p->path, NULL,\n\t\txCenter, yCenter,\n\t\tradius,\n\t\tstartAngle, startAngle + sweep,\n\t\tcw);\n}\n\nvoid uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, double c2y, double endX, double endY)\n{\n\t// TODO likewise\n\tif (p->ended)\n\t\tuiprivImplBug(\"attempt to add bezier to ended path in uiDrawPathBezierTo()\");\n\tCGPathAddCurveToPoint(p->path, NULL,\n\t\tc1x, c1y,\n\t\tc2x, c2y,\n\t\tendX, endY);\n}\n\nvoid uiDrawPathCloseFigure(uiDrawPath *p)\n{\n\t// TODO likewise\n\tif (p->ended)\n\t\tuiprivImplBug(\"attempt to close figure of ended path in uiDrawPathCloseFigure()\");\n\tCGPathCloseSubpath(p->path);\n}\n\nvoid uiDrawPathAddRectangle(uiDrawPath *p, double x, double y, double width, double height)\n{\n\tif (p->ended)\n\t\tuiprivUserBug(\"You cannot call uiDrawPathAddRectangle() on a uiDrawPath that has already been ended. (path; %p)\", p);\n\tCGPathAddRect(p->path, NULL, CGRectMake(x, y, width, height));\n}\n\nvoid uiDrawPathEnd(uiDrawPath *p)\n{\n\tp->ended = TRUE;\n}\n\nuiDrawContext *uiprivDrawNewContext(CGContextRef ctxt, CGFloat height)\n{\n\tuiDrawContext *c;\n\n\tc = uiprivNew(uiDrawContext);\n\tc->c = ctxt;\n\tc->height = height;\n\treturn c;\n}\n\nvoid uiprivDrawFreeContext(uiDrawContext *c)\n{\n\tuiprivFree(c);\n}\n\n// a stroke is identical to a fill of a stroked path\n// we need to do this in order to stroke with a gradient; see http://stackoverflow.com/a/25034854/3408572\n// doing this for other brushes works too\nvoid uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStrokeParams *p)\n{\n\tCGLineCap cap;\n\tCGLineJoin join;\n\tCGPathRef dashPath;\n\tCGFloat *dashes;\n\tsize_t i;\n\tuiDrawPath p2;\n\n\tif (!path->ended)\n\t\tuiprivUserBug(\"You cannot call uiDrawStroke() on a uiDrawPath that has not been ended. (path: %p)\", path);\n\n\tswitch (p->Cap) {\n\tcase uiDrawLineCapFlat:\n\t\tcap = kCGLineCapButt;\n\t\tbreak;\n\tcase uiDrawLineCapRound:\n\t\tcap = kCGLineCapRound;\n\t\tbreak;\n\tcase uiDrawLineCapSquare:\n\t\tcap = kCGLineCapSquare;\n\t\tbreak;\n\t}\n\tswitch (p->Join) {\n\tcase uiDrawLineJoinMiter:\n\t\tjoin = kCGLineJoinMiter;\n\t\tbreak;\n\tcase uiDrawLineJoinRound:\n\t\tjoin = kCGLineJoinRound;\n\t\tbreak;\n\tcase uiDrawLineJoinBevel:\n\t\tjoin = kCGLineJoinBevel;\n\t\tbreak;\n\t}\n\n\t// create a temporary path identical to the previous one\n\tdashPath = (CGPathRef) path->path;\n\tif (p->NumDashes != 0) {\n\t\tdashes = (CGFloat *) uiprivAlloc(p->NumDashes * sizeof (CGFloat), \"CGFloat[]\");\n\t\tfor (i = 0; i < p->NumDashes; i++)\n\t\t\tdashes[i] = p->Dashes[i];\n\t\tdashPath = CGPathCreateCopyByDashingPath(path->path,\n\t\t\tNULL,\n\t\t\tp->DashPhase,\n\t\t\tdashes,\n\t\t\tp->NumDashes);\n\t\tuiprivFree(dashes);\n\t}\n\t// the documentation is wrong: this produces a path suitable for calling CGPathCreateCopyByStrokingPath(), not for filling directly\n\t// the cast is safe; we never modify the CGPathRef and always cast it back to a CGPathRef anyway\n\tp2.path = (CGMutablePathRef) CGPathCreateCopyByStrokingPath(dashPath,\n\t\tNULL,\n\t\tp->Thickness,\n\t\tcap,\n\t\tjoin,\n\t\tp->MiterLimit);\n\tif (p->NumDashes != 0)\n\t\tCGPathRelease(dashPath);\n\n\t// always draw stroke fills using the winding rule\n\t// otherwise intersecting figures won't draw correctly\n\tp2.fillMode = uiDrawFillModeWinding;\n\tp2.ended = path->ended;\n\tuiDrawFill(c, &p2, b);\n\t// and clean up\n\tCGPathRelease((CGPathRef) (p2.path));\n}\n\n// for a solid fill, we can merely have Core Graphics fill directly\nstatic void fillSolid(CGContextRef ctxt, uiDrawPath *p, uiDrawBrush *b)\n{\n\t// TODO this uses DeviceRGB; switch to sRGB\n\tCGContextSetRGBFillColor(ctxt, b->R, b->G, b->B, b->A);\n\tswitch (p->fillMode) {\n\tcase uiDrawFillModeWinding:\n\t\tCGContextFillPath(ctxt);\n\t\tbreak;\n\tcase uiDrawFillModeAlternate:\n\t\tCGContextEOFillPath(ctxt);\n\t\tbreak;\n\t}\n}\n\n// for a gradient fill, we need to clip to the path and then draw the gradient\n// see http://stackoverflow.com/a/25034854/3408572\nstatic void fillGradient(CGContextRef ctxt, uiDrawPath *p, uiDrawBrush *b)\n{\n\tCGGradientRef gradient;\n\tCGColorSpaceRef colorspace;\n\tCGFloat *colors;\n\tCGFloat *locations;\n\tsize_t i;\n\n\t// gradients need a color space\n\t// for consistency with windows, use sRGB\n\tcolorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);\n\tif (colorspace == NULL) {\n\t\t// TODO\n\t}\n\t// TODO add NULL check to other uses of CGColorSpace\n\n\t// make the gradient\n\tcolors = uiprivAlloc(b->NumStops * 4 * sizeof (CGFloat), \"CGFloat[]\");\n\tlocations = uiprivAlloc(b->NumStops * sizeof (CGFloat), \"CGFloat[]\");\n\tfor (i = 0; i < b->NumStops; i++) {\n\t\tcolors[i * 4 + 0] = b->Stops[i].R;\n\t\tcolors[i * 4 + 1] = b->Stops[i].G;\n\t\tcolors[i * 4 + 2] = b->Stops[i].B;\n\t\tcolors[i * 4 + 3] = b->Stops[i].A;\n\t\tlocations[i] = b->Stops[i].Pos;\n\t}\n\tgradient = CGGradientCreateWithColorComponents(colorspace, colors, locations, b->NumStops);\n\tuiprivFree(locations);\n\tuiprivFree(colors);\n\n\t// because we're mucking with clipping, we need to save the graphics state and restore it later\n\tCGContextSaveGState(ctxt);\n\n\t// clip\n\tswitch (p->fillMode) {\n\tcase uiDrawFillModeWinding:\n\t\tCGContextClip(ctxt);\n\t\tbreak;\n\tcase uiDrawFillModeAlternate:\n\t\tCGContextEOClip(ctxt);\n\t\tbreak;\n\t}\n\n\t// draw the gradient\n\tswitch (b->Type) {\n\tcase uiDrawBrushTypeLinearGradient:\n\t\tCGContextDrawLinearGradient(ctxt,\n\t\t\tgradient,\n\t\t\tCGPointMake(b->X0, b->Y0),\n\t\t\tCGPointMake(b->X1, b->Y1),\n\t\t\tkCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);\n\t\tbreak;\n\tcase uiDrawBrushTypeRadialGradient:\n\t\tCGContextDrawRadialGradient(ctxt,\n\t\t\tgradient,\n\t\t\tCGPointMake(b->X0, b->Y0),\n\t\t\t// make the start circle radius 0 to make it a point\n\t\t\t0,\n\t\t\tCGPointMake(b->X1, b->Y1),\n\t\t\tb->OuterRadius,\n\t\t\tkCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);\n\t\tbreak;\n\t}\n\n\t// and clean up\n\tCGContextRestoreGState(ctxt);\n\tCGGradientRelease(gradient);\n\tCGColorSpaceRelease(colorspace);\n}\n\nvoid uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b)\n{\n\tif (!path->ended)\n\t\tuiprivUserBug(\"You cannot call uiDrawStroke() on a uiDrawPath that has not been ended. (path: %p)\", path);\n\tCGContextAddPath(c->c, (CGPathRef) (path->path));\n\tswitch (b->Type) {\n\tcase uiDrawBrushTypeSolid:\n\t\tfillSolid(c->c, path, b);\n\t\treturn;\n\tcase uiDrawBrushTypeLinearGradient:\n\tcase uiDrawBrushTypeRadialGradient:\n\t\tfillGradient(c->c, path, b);\n\t\treturn;\n//\tcase uiDrawBrushTypeImage:\n\t\t// TODO\n\t\treturn;\n\t}\n\tuiprivUserBug(\"Unknown brush type %d passed to uiDrawFill().\", b->Type);\n}\n\nstatic void m2c(uiDrawMatrix *m, CGAffineTransform *c)\n{\n\tc->a = m->M11;\n\tc->b = m->M12;\n\tc->c = m->M21;\n\tc->d = m->M22;\n\tc->tx = m->M31;\n\tc->ty = m->M32;\n}\n\nstatic void c2m(CGAffineTransform *c, uiDrawMatrix *m)\n{\n\tm->M11 = c->a;\n\tm->M12 = c->b;\n\tm->M21 = c->c;\n\tm->M22 = c->d;\n\tm->M31 = c->tx;\n\tm->M32 = c->ty;\n}\n\nvoid uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y)\n{\n\tCGAffineTransform c;\n\n\tm2c(m, &c);\n\tc = CGAffineTransformTranslate(c, x, y);\n\tc2m(&c, m);\n}\n\nvoid uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x, double y)\n{\n\tCGAffineTransform c;\n\tdouble xt, yt;\n\n\tm2c(m, &c);\n\txt = x;\n\tyt = y;\n\tuiprivScaleCenter(xCenter, yCenter, &xt, &yt);\n\tc = CGAffineTransformTranslate(c, xt, yt);\n\tc = CGAffineTransformScale(c, x, y);\n\tc = CGAffineTransformTranslate(c, -xt, -yt);\n\tc2m(&c, m);\n}\n\nvoid uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount)\n{\n\tCGAffineTransform c;\n\n\tm2c(m, &c);\n\tc = CGAffineTransformTranslate(c, x, y);\n\tc = CGAffineTransformRotate(c, amount);\n\tc = CGAffineTransformTranslate(c, -x, -y);\n\tc2m(&c, m);\n}\n\nvoid uiDrawMatrixSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount)\n{\n\tuiprivFallbackSkew(m, x, y, xamount, yamount);\n}\n\nvoid uiDrawMatrixMultiply(uiDrawMatrix *dest, uiDrawMatrix *src)\n{\n\tCGAffineTransform c;\n\tCGAffineTransform d;\n\n\tm2c(dest, &c);\n\tm2c(src, &d);\n\tc = CGAffineTransformConcat(c, d);\n\tc2m(&c, dest);\n}\n\n// there is no test for invertibility; CGAffineTransformInvert() is merely documented as returning the matrix unchanged if it isn't invertible\n// therefore, special care must be taken to catch matrices who are their own inverses\n// TODO figure out which matrices these are and do so\nint uiDrawMatrixInvertible(uiDrawMatrix *m)\n{\n\tCGAffineTransform c, d;\n\n\tm2c(m, &c);\n\td = CGAffineTransformInvert(c);\n\treturn CGAffineTransformEqualToTransform(c, d) == false;\n}\n\nint uiDrawMatrixInvert(uiDrawMatrix *m)\n{\n\tCGAffineTransform c, d;\n\n\tm2c(m, &c);\n\td = CGAffineTransformInvert(c);\n\tif (CGAffineTransformEqualToTransform(c, d))\n\t\treturn 0;\n\tc2m(&d, m);\n\treturn 1;\n}\n\nvoid uiDrawMatrixTransformPoint(uiDrawMatrix *m, double *x, double *y)\n{\n\tCGAffineTransform c;\n\tCGPoint p;\n\n\tm2c(m, &c);\n\tp = CGPointApplyAffineTransform(CGPointMake(*x, *y), c);\n\t*x = p.x;\n\t*y = p.y;\n}\n\nvoid uiDrawMatrixTransformSize(uiDrawMatrix *m, double *x, double *y)\n{\n\tCGAffineTransform c;\n\tCGSize s;\n\n\tm2c(m, &c);\n\ts = CGSizeApplyAffineTransform(CGSizeMake(*x, *y), c);\n\t*x = s.width;\n\t*y = s.height;\n}\n\nvoid uiDrawTransform(uiDrawContext *c, uiDrawMatrix *m)\n{\n\tCGAffineTransform cm;\n\n\tm2c(m, &cm);\n\tCGContextConcatCTM(c->c, cm);\n}\n\nvoid uiDrawClip(uiDrawContext *c, uiDrawPath *path)\n{\n\tif (!path->ended)\n\t\tuiprivUserBug(\"You cannot call uiDrawCilp() on a uiDrawPath that has not been ended. (path: %p)\", path);\n\tCGContextAddPath(c->c, (CGPathRef) (path->path));\n\tswitch (path->fillMode) {\n\tcase uiDrawFillModeWinding:\n\t\tCGContextClip(c->c);\n\t\tbreak;\n\tcase uiDrawFillModeAlternate:\n\t\tCGContextEOClip(c->c);\n\t\tbreak;\n\t}\n}\n\n// TODO figure out what besides transforms these save/restore on all platforms\nvoid uiDrawSave(uiDrawContext *c)\n{\n\tCGContextSaveGState(c->c);\n}\n\nvoid uiDrawRestore(uiDrawContext *c)\n{\n\tCGContextRestoreGState(c->c);\n}\n"
  },
  {
    "path": "darwin/drawtext.m",
    "content": "// 7 march 2018\n#import \"uipriv_darwin.h\"\n#import \"draw.h\"\n#import \"attrstr.h\"\n\n// 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\n// solution: for those cases, maintain a separate framesetter just for computing those things\n// in the usual case, the separate copy will just be identical to the regular one, with extra references to everything within\n@interface uiprivTextFrame : NSObject {\n\tCFAttributedStringRef attrstr;\n\tNSArray *backgroundParams;\n\tCTFramesetterRef framesetter;\n\tCGSize size;\n\tCGPathRef path;\n\tCTFrameRef frame;\n}\n- (id)initWithLayoutParams:(uiDrawTextLayoutParams *)p;\n- (void)draw:(uiDrawContext *)c textLayout:(uiDrawTextLayout *)tl at:(double)x y:(double)y;\n- (void)returnWidth:(double *)width height:(double *)height;\n- (CFArrayRef)lines;\n@end\n\n@implementation uiprivDrawTextBackgroundParams\n\n- (id)initWithStart:(size_t)s end:(size_t)e r:(double)red g:(double)green b:(double)blue a:(double)alpha\n{\n\tself = [super init];\n\tif (self) {\n\t\tself->start = s;\n\t\tself->end = e;\n\t\tself->r = red;\n\t\tself->g = green;\n\t\tself->b = blue;\n\t\tself->a = alpha;\n\t}\n\treturn self;\n}\n\n- (void)draw:(CGContextRef)c layout:(uiDrawTextLayout *)layout at:(double)x y:(double)y utf8Mapping:(const size_t *)u16tou8\n{\n\t// TODO\n}\n\n@end\n\n@implementation uiprivTextFrame\n\n- (id)initWithLayoutParams:(uiDrawTextLayoutParams *)p\n{\n\tCFRange range;\n\tCGFloat cgwidth;\n\tCFRange unused;\n\tCGRect rect;\n\n\tself = [super init];\n\tif (self) {\n\t\tself->attrstr = uiprivAttributedStringToCFAttributedString(p, &(self->backgroundParams));\n\t\t// TODO kCTParagraphStyleSpecifierMaximumLineSpacing, kCTParagraphStyleSpecifierMinimumLineSpacing, kCTParagraphStyleSpecifierLineSpacingAdjustment for line spacing\n\t\tself->framesetter = CTFramesetterCreateWithAttributedString(self->attrstr);\n\t\tif (self->framesetter == NULL) {\n\t\t\t// TODO\n\t\t}\n\n\t\trange.location = 0;\n\t\trange.length = CFAttributedStringGetLength(self->attrstr);\n\n\t\tcgwidth = (CGFloat) (p->Width);\n\t\tif (cgwidth < 0)\n\t\t\tcgwidth = CGFLOAT_MAX;\n\t\tself->size = CTFramesetterSuggestFrameSizeWithConstraints(self->framesetter,\n\t\t\trange,\n\t\t\t// TODO kCTFramePathWidthAttributeName?\n\t\t\tNULL,\n\t\t\tCGSizeMake(cgwidth, CGFLOAT_MAX),\n\t\t\t&unused);\t\t\t// not documented as accepting NULL (TODO really?)\n\n\t\trect.origin = CGPointZero;\n\t\trect.size = self->size;\n\t\tself->path = CGPathCreateWithRect(rect, NULL);\n\t\tself->frame = CTFramesetterCreateFrame(self->framesetter,\n\t\t\trange,\n\t\t\tself->path,\n\t\t\t// TODO kCTFramePathWidthAttributeName?\n\t\t\tNULL);\n\t\tif (self->frame == NULL) {\n\t\t\t// TODO\n\t\t}\n\t}\n\treturn self;\n}\n\n- (void)dealloc\n{\n\tCFRelease(self->frame);\n\tCFRelease(self->path);\n\tCFRelease(self->framesetter);\n\t[self->backgroundParams release];\n\tCFRelease(self->attrstr);\n\t[super dealloc];\n}\n\n- (void)draw:(uiDrawContext *)c textLayout:(uiDrawTextLayout *)tl at:(double)x y:(double)y\n{\n\tuiprivDrawTextBackgroundParams *dtb;\n\tCGAffineTransform textMatrix;\n\n\tCGContextSaveGState(c->c);\n\t// save the text matrix because it's not part of the graphics state\n\ttextMatrix = CGContextGetTextMatrix(c->c);\n\n\tfor (dtb in self->backgroundParams)\n\t\t/* TODO */;\n\n\t// Core Text doesn't draw onto a flipped view correctly; we have to pretend it was unflipped\n\t// 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)\n\t// TODO how is this affected by a non-identity CTM?\n\tCGContextTranslateCTM(c->c, 0, c->height);\n\tCGContextScaleCTM(c->c, 1.0, -1.0);\n\tCGContextSetTextMatrix(c->c, CGAffineTransformIdentity);\n\n\t// wait, that's not enough; we need to offset y values to account for our new flipping\n\t// TODO explain this calculation\n\ty = c->height - self->size.height - y;\n\n\t// CTFrameDraw() draws in the path we specified when creating the frame\n\t// this means that in our usage, CTFrameDraw() will draw at (0,0)\n\t// so move the origin to be at (x,y) instead\n\t// TODO are the signs correct?\n\tCGContextTranslateCTM(c->c, x, y);\n\n\tCTFrameDraw(self->frame, c->c);\n\n\tCGContextSetTextMatrix(c->c, textMatrix);\n\tCGContextRestoreGState(c->c);\n}\n\n- (void)returnWidth:(double *)width height:(double *)height\n{\n\tif (width != NULL)\n\t\t*width = self->size.width;\n\tif (height != NULL)\n\t\t*height = self->size.height;\n}\n\n- (CFArrayRef)lines\n{\n\treturn CTFrameGetLines(self->frame);\n}\n\n@end\n\nstruct uiDrawTextLayout {\n\tuiprivTextFrame *frame;\n\tuiprivTextFrame *forLines;\n\tBOOL empty;\n\n\t// for converting CFAttributedString indices from/to byte offsets\n\tsize_t *u8tou16;\n\tsize_t nUTF8;\n\tsize_t *u16tou8;\n\tsize_t nUTF16;\n};\n\nuiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *p)\n{\n\tuiDrawTextLayout *tl;\n\n\ttl = uiprivNew(uiDrawTextLayout);\n\ttl->frame = [[uiprivTextFrame alloc] initWithLayoutParams:p];\n\tif (uiAttributedStringLen(p->String) != 0)\n\t\ttl->forLines = [tl->frame retain];\n\telse {\n\t\tuiAttributedString *space;\n\t\tuiDrawTextLayoutParams p2;\n\n\t\ttl->empty = YES;\n\t\tspace = uiNewAttributedString(\" \");\n\t\tp2 = *p;\n\t\tp2.String = space;\n\t\ttl->forLines = [[uiprivTextFrame alloc] initWithLayoutParams:&p2];\n\t\tuiFreeAttributedString(space);\n\t}\n\n\t// and finally copy the UTF-8/UTF-16 conversion tables\n\ttl->u8tou16 = uiprivAttributedStringCopyUTF8ToUTF16Table(p->String, &(tl->nUTF8));\n\ttl->u16tou8 = uiprivAttributedStringCopyUTF16ToUTF8Table(p->String, &(tl->nUTF16));\n\treturn tl;\n}\n\nvoid uiDrawFreeTextLayout(uiDrawTextLayout *tl)\n{\n\tuiprivFree(tl->u16tou8);\n\tuiprivFree(tl->u8tou16);\n\t[tl->forLines release];\n\t[tl->frame release];\n\tuiprivFree(tl);\n}\n\n// TODO document that (x,y) is the top-left corner of the *entire frame*\nvoid uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)\n{\n\t[tl->frame draw:c textLayout:tl at:x y:y];\n}\n\n// TODO document that the width and height of a layout is not necessarily the sum of the widths and heights of its constituent lines\n// TODO width doesn't include trailing whitespace...\n// TODO figure out how paragraph spacing should play into this\n// TODO standardize and document the behavior of this on an empty layout\nvoid uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height)\n{\n\t// TODO explain this, given the above\n\t[tl->frame returnWidth:width height:NULL];\n\t[tl->forLines returnWidth:NULL height:height];\n}\n"
  },
  {
    "path": "darwin/editablecombo.m",
    "content": "// 14 august 2015\n#import \"uipriv_darwin.h\"\n\n// So why did I split uiCombobox into uiCombobox and uiEditableCombobox? Here's (90% of the; the other 10% is GTK+ events) answer:\n// When you type a value into a NSComboBox that just happens to be in the list, it will autoselect that item!\n// I can't seem to find a workaround.\n// Fortunately, there's other weird behaviors that made this split worth it.\n// And besides, selected items make little sense with editable comboboxes... you either separate or combine them with the text entry :V\n\n// NSComboBoxes have no intrinsic width; we'll use the default Interface Builder width for them.\n#define comboboxWidth 96\n\n@interface libui_intrinsicWidthNSComboBox : NSComboBox\n@end\n\n@implementation libui_intrinsicWidthNSComboBox\n\n- (NSSize)intrinsicContentSize\n{\n\tNSSize s;\n\n\ts = [super intrinsicContentSize];\n\ts.width = comboboxWidth;\n\treturn s;\n}\n\n@end\n\nstruct uiEditableCombobox {\n\tuiDarwinControl c;\n\tNSComboBox *cb;\n\tvoid (*onChanged)(uiEditableCombobox *, void *);\n\tvoid *onChangedData;\n};\n\n@interface editableComboboxDelegateClass : NSObject<NSComboBoxDelegate> {\n\tuiprivMap *comboboxes;\n}\n- (void)controlTextDidChange:(NSNotification *)note;\n- (void)comboBoxSelectionDidChange:(NSNotification *)note;\n- (void)registerCombobox:(uiEditableCombobox *)c;\n- (void)unregisterCombobox:(uiEditableCombobox *)c;\n@end\n\n@implementation editableComboboxDelegateClass\n\n- (id)init\n{\n\tself = [super init];\n\tif (self)\n\t\tself->comboboxes = uiprivNewMap();\n\treturn self;\n}\n\n- (void)dealloc\n{\n\tuiprivMapDestroy(self->comboboxes);\n\t[super dealloc];\n}\n\n- (void)controlTextDidChange:(NSNotification *)note\n{\n\tuiEditableCombobox *c;\n\n\t// TODO normalize the cast styles in these calls\n\tc = uiEditableCombobox(uiprivMapGet(self->comboboxes, [note object]));\n\t(*(c->onChanged))(c, c->onChangedData);\n}\n\n// the above doesn't handle when an item is selected; this will\n- (void)comboBoxSelectionDidChange:(NSNotification *)note\n{\n\t// except this is sent BEFORE the entry is changed, and that doesn't send the above, so\n\t// this is via http://stackoverflow.com/a/21059819/3408572 - it avoids the need to manage selected items\n\t// this still isn't perfect — I get residual changes to the same value while navigating the list — but it's good enough\n\t[self performSelector:@selector(controlTextDidChange:)\n\t\twithObject:note\n\t\tafterDelay:0];\n}\n\n- (void)registerCombobox:(uiEditableCombobox *)c\n{\n\tuiprivMapSet(self->comboboxes, c->cb, c);\n\t[c->cb setDelegate:self];\n}\n\n- (void)unregisterCombobox:(uiEditableCombobox *)c\n{\n\t[c->cb setDelegate:nil];\n\tuiprivMapDelete(self->comboboxes, c->cb);\n}\n\n@end\n\nstatic editableComboboxDelegateClass *comboboxDelegate = nil;\n\nuiDarwinControlAllDefaultsExceptDestroy(uiEditableCombobox, cb)\n\nstatic void uiEditableComboboxDestroy(uiControl *cc)\n{\n\tuiEditableCombobox *c = uiEditableCombobox(cc);\n\n\t[comboboxDelegate unregisterCombobox:c];\n\t[c->cb release];\n\tuiFreeControl(uiControl(c));\n}\n\nvoid uiEditableComboboxAppend(uiEditableCombobox *c, const char *text)\n{\n\t[c->cb addItemWithObjectValue:uiprivToNSString(text)];\n}\n\nchar *uiEditableComboboxText(uiEditableCombobox *c)\n{\n\treturn uiDarwinNSStringToText([c->cb stringValue]);\n}\n\nvoid uiEditableComboboxSetText(uiEditableCombobox *c, const char *text)\n{\n\tNSString *t;\n\n\tt = uiprivToNSString(text);\n\t[c->cb setStringValue:t];\n\t// yes, let's imitate the behavior that caused uiEditableCombobox to be separate in the first place!\n\t// just to avoid confusion when users see an option in the list in the text field but not selected in the list\n\t[c->cb selectItemWithObjectValue:t];\n}\n\n#if 0\n// LONGTERM\nvoid uiEditableComboboxSetSelected(uiEditableCombobox *c, int n)\n{\n\tif (c->editable) {\n\t\t// see https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ComboBox/Tasks/SettingComboBoxValue.html#//apple_ref/doc/uid/20000256\n\t\tid delegate;\n\n\t\t// this triggers the delegate; turn it off for now\n\t\tdelegate = [c->cb delegate];\n\t\t[c->cb setDelegate:nil];\n\n\t\t// this seems to work fine for -1 too\n\t\t[c->cb selectItemAtIndex:n];\n\t\tif (n == -1)\n\t\t\t[c->cb setObjectValue:@\"\"];\n\t\telse\n\t\t\t[c->cb setObjectValue:[c->cb objectValueOfSelectedItem]];\n\n\t\t[c->cb setDelegate:delegate];\n\t\treturn;\n\t}\n\t[c->pb selectItemAtIndex:n];\n}\n#endif\n\nvoid uiEditableComboboxOnChanged(uiEditableCombobox *c, void (*f)(uiEditableCombobox *c, void *data), void *data)\n{\n\tc->onChanged = f;\n\tc->onChangedData = data;\n}\n\nstatic void defaultOnChanged(uiEditableCombobox *c, void *data)\n{\n\t// do nothing\n}\n\nuiEditableCombobox *uiNewEditableCombobox(void)\n{\n\tuiEditableCombobox *c;\n\n\tuiDarwinNewControl(uiEditableCombobox, c);\n\n\tc->cb = [[libui_intrinsicWidthNSComboBox alloc] initWithFrame:NSZeroRect];\n\t[c->cb setUsesDataSource:NO];\n\t[c->cb setButtonBordered:YES];\n\t[c->cb setCompletes:NO];\n\tuiDarwinSetControlFont(c->cb, NSRegularControlSize);\n\n\tif (comboboxDelegate == nil) {\n\t\tcomboboxDelegate = [[editableComboboxDelegateClass new] autorelease];\n\t\t[uiprivDelegates addObject:comboboxDelegate];\n\t}\n\t[comboboxDelegate registerCombobox:c];\n\tuiEditableComboboxOnChanged(c, defaultOnChanged, NULL);\n\n\treturn c;\n}\n"
  },
  {
    "path": "darwin/entry.m",
    "content": "// 14 august 2015\n#import \"uipriv_darwin.h\"\n\n// Text fields for entering text have no intrinsic width; we'll use the default Interface Builder width for them.\n#define textfieldWidth 96\n\n@interface libui_intrinsicWidthNSTextField : NSTextField\n@end\n\n@implementation libui_intrinsicWidthNSTextField\n\n- (NSSize)intrinsicContentSize\n{\n\tNSSize s;\n\n\ts = [super intrinsicContentSize];\n\ts.width = textfieldWidth;\n\treturn s;\n}\n\n@end\n\n// TODO does this have one on its own?\n@interface libui_intrinsicWidthNSSecureTextField : NSSecureTextField\n@end\n\n@implementation libui_intrinsicWidthNSSecureTextField\n\n- (NSSize)intrinsicContentSize\n{\n\tNSSize s;\n\n\ts = [super intrinsicContentSize];\n\ts.width = textfieldWidth;\n\treturn s;\n}\n\n@end\n\n// TODO does this have one on its own?\n@interface libui_intrinsicWidthNSSearchField : NSSearchField\n@end\n\n@implementation libui_intrinsicWidthNSSearchField\n\n- (NSSize)intrinsicContentSize\n{\n\tNSSize s;\n\n\ts = [super intrinsicContentSize];\n\ts.width = textfieldWidth;\n\treturn s;\n}\n\n@end\n\nstruct uiEntry {\n\tuiDarwinControl c;\n\tNSTextField *textfield;\n\tvoid (*onChanged)(uiEntry *, void *);\n\tvoid *onChangedData;\n};\n\nstatic BOOL isSearchField(NSTextField *tf)\n{\n\treturn [tf isKindOfClass:[NSSearchField class]];\n}\n\n@interface entryDelegateClass : NSObject<NSTextFieldDelegate> {\n\tuiprivMap *entries;\n}\n- (void)controlTextDidChange:(NSNotification *)note;\n- (IBAction)onSearch:(id)sender;\n- (void)registerEntry:(uiEntry *)e;\n- (void)unregisterEntry:(uiEntry *)e;\n@end\n\n@implementation entryDelegateClass\n\n- (id)init\n{\n\tself = [super init];\n\tif (self)\n\t\tself->entries = uiprivNewMap();\n\treturn self;\n}\n\n- (void)dealloc\n{\n\tuiprivMapDestroy(self->entries);\n\t[super dealloc];\n}\n\n- (void)controlTextDidChange:(NSNotification *)note\n{\n\t[self onSearch:[note object]];\n}\n\n- (IBAction)onSearch:(id)sender\n{\n\tuiEntry *e;\n\n\te = (uiEntry *) uiprivMapGet(self->entries, sender);\n\t(*(e->onChanged))(e, e->onChangedData);\n}\n\n- (void)registerEntry:(uiEntry *)e\n{\n\tuiprivMapSet(self->entries, e->textfield, e);\n\tif (isSearchField(e->textfield)) {\n\t\t[e->textfield setTarget:self];\n\t\t[e->textfield setAction:@selector(onSearch:)];\n\t} else\n\t\t[e->textfield setDelegate:self];\n}\n\n- (void)unregisterEntry:(uiEntry *)e\n{\n\tif (isSearchField(e->textfield))\n\t\t[e->textfield setTarget:nil];\n\telse\n\t\t[e->textfield setDelegate:nil];\n\tuiprivMapDelete(self->entries, e->textfield);\n}\n\n@end\n\nstatic entryDelegateClass *entryDelegate = nil;\n\nuiDarwinControlAllDefaultsExceptDestroy(uiEntry, textfield)\n\nstatic void uiEntryDestroy(uiControl *c)\n{\n\tuiEntry *e = uiEntry(c);\n\n\t[entryDelegate unregisterEntry:e];\n\t[e->textfield release];\n\tuiFreeControl(uiControl(e));\n}\n\nchar *uiEntryText(uiEntry *e)\n{\n\treturn uiDarwinNSStringToText([e->textfield stringValue]);\n}\n\nvoid uiEntrySetText(uiEntry *e, const char *text)\n{\n\t[e->textfield setStringValue:uiprivToNSString(text)];\n\t// don't queue the control for resize; entry sizes are independent of their contents\n}\n\nvoid uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *, void *), void *data)\n{\n\te->onChanged = f;\n\te->onChangedData = data;\n}\n\nint uiEntryReadOnly(uiEntry *e)\n{\n\treturn [e->textfield isEditable] == NO;\n}\n\nvoid uiEntrySetReadOnly(uiEntry *e, int readonly)\n{\n\tBOOL editable;\n\n\teditable = YES;\n\tif (readonly)\n\t\teditable = NO;\n\t[e->textfield setEditable:editable];\n}\n\nstatic void defaultOnChanged(uiEntry *e, void *data)\n{\n\t// do nothing\n}\n\n// 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 :/\nvoid uiprivFinishNewTextField(NSTextField *t, BOOL isEntry)\n{\n\tuiDarwinSetControlFont(t, NSRegularControlSize);\n\n\t// THE ORDER OF THESE CALLS IS IMPORTANT; CHANGE IT AND THE BORDERS WILL DISAPPEAR\n\t[t setBordered:NO];\n\t[t setBezelStyle:NSTextFieldSquareBezel];\n\t[t setBezeled:isEntry];\n\n\t// we don't need to worry about substitutions/autocorrect here; see window_darwin.m for details\n\n\t[[t cell] setLineBreakMode:NSLineBreakByClipping];\n\t[[t cell] setScrollable:YES];\n}\n\nstatic NSTextField *realNewEditableTextField(Class class)\n{\n\tNSTextField *tf;\n\n\ttf = [[class alloc] initWithFrame:NSZeroRect];\n\t[tf setSelectable:YES];\t\t// otherwise the setting is masked by the editable default of YES\n\tuiprivFinishNewTextField(tf, YES);\n\treturn tf;\n}\n\nNSTextField *uiprivNewEditableTextField(void)\n{\n\treturn realNewEditableTextField([libui_intrinsicWidthNSTextField class]);\n}\n\nstatic uiEntry *finishNewEntry(Class class)\n{\n\tuiEntry *e;\n\n\tuiDarwinNewControl(uiEntry, e);\n\n\te->textfield = realNewEditableTextField(class);\n\n\tif (entryDelegate == nil) {\n\t\tentryDelegate = [[entryDelegateClass new] autorelease];\n\t\t[uiprivDelegates addObject:entryDelegate];\n\t}\n\t[entryDelegate registerEntry:e];\n\tuiEntryOnChanged(e, defaultOnChanged, NULL);\n\n\treturn e;\n}\n\nuiEntry *uiNewEntry(void)\n{\n\treturn finishNewEntry([libui_intrinsicWidthNSTextField class]);\n}\n\nuiEntry *uiNewPasswordEntry(void)\n{\n\treturn finishNewEntry([libui_intrinsicWidthNSSecureTextField class]);\n}\n\nuiEntry *uiNewSearchEntry(void)\n{\n\tuiEntry *e;\n\tNSSearchField *s;\n\n\te = finishNewEntry([libui_intrinsicWidthNSSearchField class]);\n\ts = (NSSearchField *) (e->textfield);\n\t// TODO these are only on 10.10\n//\t[s setSendsSearchStringImmediately:NO];\n//\t[s setSendsWholeSearchString:NO];\n\t[s setBordered:NO];\n\t[s setBezelStyle:NSTextFieldRoundedBezel];\n\t[s setBezeled:YES];\n\treturn e;\n}\n"
  },
  {
    "path": "darwin/fontbutton.m",
    "content": "// 14 april 2016\n#import \"uipriv_darwin.h\"\n#import \"attrstr.h\"\n\n@interface uiprivFontButton : NSButton {\n\tuiFontButton *libui_b;\n\tNSFont *libui_font;\n}\n- (id)initWithFrame:(NSRect)frame libuiFontButton:(uiFontButton *)b;\n- (void)updateFontButtonLabel;\n- (IBAction)fontButtonClicked:(id)sender;\n- (void)activateFontButton;\n- (void)deactivateFontButton:(BOOL)activatingAnother;\n- (void)deactivateOnClose:(NSNotification *)note;\n- (void)getfontdesc:(uiFontDescriptor *)uidesc;\n@end\n\n// only one may be active at one time\nstatic uiprivFontButton *activeFontButton = nil;\n\nstruct uiFontButton {\n\tuiDarwinControl c;\n\tuiprivFontButton *button;\n\tvoid (*onChanged)(uiFontButton *, void *);\n\tvoid *onChangedData;\n};\n\n@implementation uiprivFontButton\n\n- (id)initWithFrame:(NSRect)frame libuiFontButton:(uiFontButton *)b\n{\n\tself = [super initWithFrame:frame];\n\tif (self) {\n\t\tself->libui_b = b;\n\n\t\t// imitate a NSColorWell in appearance\n\t\t[self setButtonType:NSPushOnPushOffButton];\n\t\t[self setBordered:YES];\n\t\t[self setBezelStyle:NSShadowlessSquareBezelStyle];\n\n\t\t// default font values according to the CTFontDescriptor reference\n\t\t// this is autoreleased (thanks swillits in irc.freenode.net/#macdev)\n\t\tself->libui_font = [[NSFont fontWithName:@\"Helvetica\" size:12.0] retain];\n\t\t[self updateFontButtonLabel];\n\n\t\t// for when clicked\n\t\t[self setTarget:self];\n\t\t[self setAction:@selector(fontButtonClicked:)];\n\t}\n\treturn self;\n}\n\n- (void)dealloc\n{\n\t// clean up notifications\n\tif (activeFontButton == self)\n\t\t[self deactivateFontButton:NO];\n\t[self->libui_font release];\n\t[super dealloc];\n}\n\n- (void)updateFontButtonLabel\n{\n\tNSString *title;\n\n\ttitle = [NSString stringWithFormat:@\"%@ %g\",\n\t\t[self->libui_font displayName],\n\t\t[self->libui_font pointSize]];\n\t[self setTitle:title];\n}\n\n- (IBAction)fontButtonClicked:(id)sender\n{\n\tif ([self state] == NSOnState)\n\t\t[self activateFontButton];\n\telse\n\t\t[self deactivateFontButton:NO];\n}\n\n- (void)activateFontButton\n{\n\tNSFontManager *sfm;\n\n\tsfm = [NSFontManager sharedFontManager];\n\tif (activeFontButton != nil)\n\t\t[activeFontButton deactivateFontButton:YES];\n\t[sfm setTarget:self];\n\t[sfm setSelectedFont:self->libui_font isMultiple:NO];\n\t[sfm orderFrontFontPanel:self];\n\tactiveFontButton = self;\n\t[[NSNotificationCenter defaultCenter] addObserver:self\n\t\tselector:@selector(deactivateOnClose:)\n\t\tname:NSWindowWillCloseNotification\n\t\tobject:[NSFontPanel sharedFontPanel]];\n\t[self setState:NSOnState];\n}\n\n- (void)deactivateFontButton:(BOOL)activatingAnother\n{\n\tNSFontManager *sfm;\n\n\tsfm = [NSFontManager sharedFontManager];\n\t[sfm setTarget:nil];\n\tif (!activatingAnother)\n\t\t[[NSFontPanel sharedFontPanel] orderOut:self];\n\tactiveFontButton = nil;\n\t[[NSNotificationCenter defaultCenter] removeObserver:self\n\t\tname:NSWindowWillCloseNotification\n\t\tobject:[NSFontPanel sharedFontPanel]];\n\t[self setState:NSOffState];\n}\n\n- (void)deactivateOnClose:(NSNotification *)note\n{\n\t[self deactivateFontButton:NO];\n}\n\n- (void)changeFont:(id)sender\n{\n\tNSFontManager *fm;\n\tNSFont *old;\n\tuiFontButton *b = self->libui_b;\n\n\tfm = (NSFontManager *) sender;\n\told = self->libui_font;\n\tself->libui_font = [sender convertFont:self->libui_font];\n\t// do this even if it returns the same; we don't own anything that isn't from a new or alloc/init\n\t[self->libui_font retain];\n\t// do this second just in case\n\t[old release];\n\t[self updateFontButtonLabel];\n\t(*(b->onChanged))(b, b->onChangedData);\n}\n\n- (NSUInteger)validModesForFontPanel:(NSFontPanel *)panel\n{\n\treturn NSFontPanelFaceModeMask |\n\t\tNSFontPanelSizeModeMask |\n\t\tNSFontPanelCollectionModeMask;\n}\n\n- (void)getfontdesc:(uiFontDescriptor *)uidesc\n{\n\tCTFontRef ctfont;\n\tCTFontDescriptorRef ctdesc;\n\n\tctfont = (CTFontRef) (self->libui_font);\n\tctdesc = CTFontCopyFontDescriptor(ctfont);\n\tuiprivFontDescriptorFromCTFontDescriptor(ctdesc, uidesc);\n\tCFRelease(ctdesc);\n\tuidesc->Size = CTFontGetSize(ctfont);\n}\n\n@end\n\nuiDarwinControlAllDefaults(uiFontButton, button)\n\n// we do not want font change events to be sent to any controls other than the font buttons\n// see main.m for more details\nBOOL uiprivFontButtonInhibitSendAction(SEL sel, id from, id to)\n{\n\tif (sel != @selector(changeFont:))\n\t\treturn NO;\n\treturn ![to isKindOfClass:[uiprivFontButton class]];\n}\n\n// we do not want NSFontPanelValidation messages to be sent to any controls other than the font buttons when a font button is active\n// see main.m for more details\nBOOL uiprivFontButtonOverrideTargetForAction(SEL sel, id from, id to, id *override)\n{\n\tif (activeFontButton == nil)\n\t\treturn NO;\n\tif (sel != @selector(validModesForFontPanel:))\n\t\treturn NO;\n\t*override = activeFontButton;\n\treturn YES;\n}\n\n// we also don't want the panel to be usable when there's a dialog running; see stddialogs.m for more details on that\n// unfortunately the panel seems to ignore -setWorksWhenModal: so we'll have to do things ourselves\n@interface uiprivNonModalFontPanel : NSFontPanel\n@end\n\n@implementation uiprivNonModalFontPanel\n\n- (BOOL)worksWhenModal\n{\n\treturn NO;\n}\n\n@end\n\nvoid uiprivSetupFontPanel(void)\n{\n\t[NSFontManager setFontPanelFactory:[uiprivNonModalFontPanel class]];\n}\n\nstatic void defaultOnChanged(uiFontButton *b, void *data)\n{\n\t// do nothing\n}\n\nvoid uiFontButtonFont(uiFontButton *b, uiFontDescriptor *desc)\n{\n\t[b->button getfontdesc:desc];\n}\n\nvoid uiFontButtonOnChanged(uiFontButton *b, void (*f)(uiFontButton *, void *), void *data)\n{\n\tb->onChanged = f;\n\tb->onChangedData = data;\n}\n\nuiFontButton *uiNewFontButton(void)\n{\n\tuiFontButton *b;\n\n\tuiDarwinNewControl(uiFontButton, b);\n\n\tb->button = [[uiprivFontButton alloc] initWithFrame:NSZeroRect libuiFontButton:b];\n\tuiDarwinSetControlFont(b->button, NSRegularControlSize);\n\n\tuiFontButtonOnChanged(b, defaultOnChanged, NULL);\n\n\treturn b;\n}\n\nvoid uiFreeFontButtonFont(uiFontDescriptor *desc)\n{\n\t// TODO ensure this is synchronized with fontmatch.m\n\tuiFreeText((char *) (desc->Family));\n}\n"
  },
  {
    "path": "darwin/fontmatch.m",
    "content": "// 3 january 2017\n#import \"uipriv_darwin.h\"\n#import \"attrstr.h\"\n\n// TODOs:\n// - switching from Skia to a non-fvar-based font crashes because the CTFontDescriptorRef we get has an empty variation dictionary for some reason...\n// - Futura causes the Courier New in the drawtext example to be bold for some reason...\n\n// Core Text exposes font style info in two forms:\n// - Fonts with a QuickDraw GX font variation (fvar) table, a feature\n// \tadopted by OpenType, expose variations directly.\n// - All other fonts have Core Text normalize the font style info\n// \tinto a traits dictionary.\n// Of course this setup doesn't come without its hiccups and\n// glitches. In particular, not only are the exact rules not well\n// defined, but also font matching doesn't work as we want it to\n// (exactly how varies based on the way the style info is exposed).\n// So we'll have to implement style matching ourselves.\n// We can use Core Text's matching to get a complete list of\n// *possible* matches, and then we can filter out the ones we don't\n// want ourselves.\n//\n// To make things easier for us, we'll match by converting Core\n// Text's values back into libui values. This allows us to also use the\n// normalization code for filling in uiFontDescriptors from\n// Core Text fonts and font descriptors.\n//\n// Style matching needs to be done early in the font loading process;\n// in particular, we have to do this before adding any features,\n// because the descriptors returned by Core Text's own font\n// matching won't have any.\n\n@implementation uiprivFontStyleData\n\n- (id)initWithFont:(CTFontRef)f\n{\n\tself = [super init];\n\tif (self) {\n\t\tself->font = f;\n\t\tCFRetain(self->font);\n\t\tself->desc = CTFontCopyFontDescriptor(self->font);\n\t\tif (![self prepare]) {\n\t\t\t[self release];\n\t\t\treturn nil;\n\t\t}\n\t}\n\treturn self;\n}\n\n- (id)initWithDescriptor:(CTFontDescriptorRef)d\n{\n\tself = [super init];\n\tif (self) {\n\t\tself->font = NULL;\n\t\tself->desc = d;\n\t\tCFRetain(self->desc);\n\t\tif (![self prepare]) {\n\t\t\t[self release];\n\t\t\treturn nil;\n\t\t}\n\t}\n\treturn self;\n}\n\n- (void)dealloc\n{\n#define REL(x) if (x != NULL) { CFRelease(x); x = NULL; }\n\tREL(self->variationAxes);\n\tREL(self->familyName);\n\tREL(self->preferredFamilyName);\n\tREL(self->fullName);\n\tREL(self->subFamilyName);\n\tREL(self->preferredSubFamilyName);\n\tREL(self->postScriptName);\n\tREL(self->variation);\n\tREL(self->styleName);\n\tREL(self->traits);\n\tCFRelease(self->desc);\n\tREL(self->font);\n\t[super dealloc];\n}\n\n- (BOOL)prepare\n{\n\tCFNumberRef num;\n\tBoolean success;\n\n\tself->traits = NULL;\n\tself->symbolic = 0;\n\tself->weight = 0;\n\tself->width = 0;\n\tself->didStyleName = NO;\n\tself->styleName = NULL;\n\tself->didVariation = NO;\n\tself->variation = NULL;\n\tself->hasRegistrationScope = NO;\n\tself->registrationScope = 0;\n\tself->didPostScriptName = NO;\n\tself->postScriptName = NULL;\n\tself->fontFormat = 0;\n\tself->didPreferredSubFamilyName = NO;\n\tself->preferredSubFamilyName = NULL;\n\tself->didSubFamilyName = NO;\n\tself->subFamilyName = NULL;\n\tself->didFullName = NO;\n\tself->fullName = NULL;\n\tself->didPreferredFamilyName = NO;\n\tself->preferredFamilyName = NULL;\n\tself->didFamilyName = NO;\n\tself->familyName = NULL;\n\tself->didVariationAxes = NO;\n\tself->variationAxes = NULL;\n\n\tself->traits = (CFDictionaryRef) CTFontDescriptorCopyAttribute(self->desc, kCTFontTraitsAttribute);\n\tif (self->traits == NULL)\n\t\treturn NO;\n\n\tnum = (CFNumberRef) CFDictionaryGetValue(self->traits, kCTFontSymbolicTrait);\n\tif (num == NULL)\n\t\treturn NO;\n\tif (CFNumberGetValue(num, kCFNumberSInt32Type, &(self->symbolic)) == false)\n\t\treturn NO;\n\n\tnum = (CFNumberRef) CFDictionaryGetValue(self->traits, kCTFontWeightTrait);\n\tif (num == NULL)\n\t\treturn NO;\n\tif (CFNumberGetValue(num, kCFNumberDoubleType, &(self->weight)) == false)\n\t\treturn NO;\n\n\tnum = (CFNumberRef) CFDictionaryGetValue(self->traits, kCTFontWidthTrait);\n\tif (num == NULL)\n\t\treturn NO;\n\tif (CFNumberGetValue(num, kCFNumberDoubleType, &(self->width)) == false)\n\t\treturn NO;\n\n\t// do these now for the sake of error checking\n\tnum = (CFNumberRef) CTFontDescriptorCopyAttribute(desc, kCTFontRegistrationScopeAttribute);\n\tself->hasRegistrationScope = num != NULL;\n\tif (self->hasRegistrationScope) {\n\t\tsuccess = CFNumberGetValue(num, kCFNumberSInt32Type, &(self->registrationScope));\n\t\tCFRelease(num);\n\t\tif (success == false)\n\t\t\treturn NO;\n\t}\n\n\tnum = (CFNumberRef) CTFontDescriptorCopyAttribute(self->desc, kCTFontFormatAttribute);\n\tif (num == NULL)\n\t\treturn NO;\n\tsuccess = CFNumberGetValue(num, kCFNumberSInt32Type, &(self->fontFormat));\n\tCFRelease(num);\n\tif (success == false)\n\t\treturn NO;\n\n\treturn YES;\n}\n\n- (void)ensureFont\n{\n\tif (self->font != NULL)\n\t\treturn;\n\tself->font = CTFontCreateWithFontDescriptor(self->desc, 0.0, NULL);\n}\n\n- (CTFontSymbolicTraits)symbolicTraits\n{\n\treturn self->symbolic;\n}\n\n- (double)weight\n{\n\treturn self->weight;\n}\n\n- (double)width\n{\n\treturn self->width;\n}\n\n- (CFStringRef)styleName\n{\n\tif (!self->didStyleName) {\n\t\tself->didStyleName = YES;\n\t\tself->styleName = (CFStringRef) CTFontDescriptorCopyAttribute(self->desc, kCTFontStyleNameAttribute);\n\t\t// The code we use this for (guessItalicOblique() below) checks if this is NULL or not, so we're good.\n\t}\n\treturn self->styleName;\n}\n\n- (CFDictionaryRef)variation\n{\n\tif (!self->didVariation) {\n\t\tself->didVariation = YES;\n\t\tself->variation = (CFDictionaryRef) CTFontDescriptorCopyAttribute(self->desc, kCTFontVariationAttribute);\n\t\t// This being NULL is used to determine whether a font uses variations at all, so we don't need to worry now.\n\t}\n\treturn self->variation;\n}\n\n- (BOOL)hasRegistrationScope\n{\n\treturn self->hasRegistrationScope;\n}\n\n- (CTFontManagerScope)registrationScope\n{\n\treturn self->registrationScope;\n}\n\n- (CFStringRef)postScriptName\n{\n\tif (!self->didPostScriptName) {\n\t\tself->didPostScriptName = YES;\n\t\t[self ensureFont];\n\t\tself->postScriptName = CTFontCopyPostScriptName(self->font);\n\t}\n\treturn self->postScriptName;\n}\n\n- (CFDataRef)table:(CTFontTableTag)tag\n{\n\t[self ensureFont];\n\treturn CTFontCopyTable(self->font, tag, kCTFontTableOptionNoOptions);\n}\n\n- (CTFontFormat)fontFormat\n{\n\treturn self->fontFormat;\n}\n\n// 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.\n- (CFStringRef)fontName:(CFStringRef)key\n{\n\t[self ensureFont];\n\treturn CTFontCopyName(self->font, key);\n}\n\n#define FONTNAME(sel, did, var, key) \\\n\t- (CFStringRef)sel \\\n\t{ \\\n\t\tif (!did) { \\\n\t\t\tdid = YES; \\\n\t\t\tvar = [self fontName:key]; \\\n\t\t} \\\n\t\treturn var; \\\n\t}\nFONTNAME(preferredSubFamilyName,\n\tself->didPreferredSubFamilyName,\n\tself->preferredSubFamilyName,\n\tuiprivUNDOC_kCTFontPreferredSubFamilyNameKey)\nFONTNAME(subFamilyName,\n\tself->didSubFamilyName,\n\tself->subFamilyName,\n\tkCTFontSubFamilyNameKey)\nFONTNAME(fullName,\n\tself->didFullName,\n\tself->fullName,\n\tkCTFontFullNameKey)\nFONTNAME(preferredFamilyName,\n\tself->didPreferredFamilyName,\n\tself->preferredFamilyName,\n\tuiprivUNDOC_kCTFontPreferredFamilyNameKey)\nFONTNAME(familyName,\n\tself->didFamilyName,\n\tself->familyName,\n\tkCTFontFamilyNameKey)\n\n- (CFArrayRef)variationAxes\n{\n\tif (!self->didVariationAxes) {\n\t\tself->didVariationAxes = YES;\n\t\t[self ensureFont];\n\t\tself->variationAxes = CTFontCopyVariationAxes(self->font);\n\t\t// We don't care about the return value because we call this only on fonts that we know have variations anyway.\n\t}\n\treturn self->variationAxes;\n}\n\n@end\n\nstruct closeness {\n\tCFIndex index;\n\tuiTextWeight weight;\n\tdouble italic;\n\tuiTextStretch stretch;\n\tdouble distance;\n};\n\n// remember that in closeness, 0 means exact\n// 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\"\nstatic const double italicClosenessNormal[] = { 0, 1, 1 };\nstatic const double italicClosenessOblique[] = { 1, 0, 0.5 };\nstatic const double italicClosenessItalic[] = { 1, 0.5, 0 };\nstatic const double *italicClosenesses[] = {\n\t[uiTextItalicNormal] = italicClosenessNormal,\n\t[uiTextItalicOblique] = italicClosenessOblique,\n\t[uiTextItalicItalic] = italicClosenessItalic,\n};\n\n// Core Text doesn't seem to differentiate between Italic and Oblique.\n// 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\nstatic uiTextItalic guessItalicOblique(uiprivFontStyleData *d)\n{\n\tCFStringRef styleName;\n\tBOOL isOblique;\n\n\tisOblique = NO;\t\t// default value\n\tstyleName = [d styleName];\n\tif (styleName != NULL) {\n\t\tCFRange range;\n\n\t\trange = CFStringFind(styleName, CFSTR(\"Oblique\"), kCFCompareBackwards);\n\t\tif (range.location != kCFNotFound)\n\t\t\tisOblique = YES;\n\t}\n\tif (isOblique)\n\t\treturn uiTextItalicOblique;\n\treturn uiTextItalicItalic;\n}\n\n// Italics are hard because Core Text does NOT distinguish between italic and oblique.\n// All Core Text provides is a slant value and the italic bit of the symbolic traits mask.\n// 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.\n// 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.)\n// 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)\nstatic void setItalic(uiprivFontStyleData *d, uiFontDescriptor *out)\n{\n\tout->Italic = uiTextItalicNormal;\n\tif (([d symbolicTraits] & kCTFontItalicTrait) != 0)\n\t\tout->Italic = guessItalicOblique(d);\n}\n\nstatic void fillDescStyleFields(uiprivFontStyleData *d, NSDictionary *axisDict, uiFontDescriptor *out)\n{\n\tsetItalic(d, out);\n\tif (axisDict != nil)\n\t\tuiprivProcessFontVariation(d, axisDict, out);\n\telse\n\t\tuiprivProcessFontTraits(d, out);\n}\n\nstatic CTFontDescriptorRef matchStyle(CTFontDescriptorRef against, uiFontDescriptor *styles)\n{\n\tCFArrayRef matching;\n\tCFIndex i, n;\n\tstruct closeness *closeness;\n\tCTFontDescriptorRef current;\n\tCTFontDescriptorRef out;\n\tuiprivFontStyleData *d;\n\tNSDictionary *axisDict;\n\n\tmatching = CTFontDescriptorCreateMatchingFontDescriptors(against, NULL);\n\tif (matching == NULL)\n\t\t// no matches; give the original back and hope for the best\n\t\treturn against;\n\tn = CFArrayGetCount(matching);\n\tif (n == 0) {\n\t\t// likewise\n\t\tCFRelease(matching);\n\t\treturn against;\n\t}\n\n\tcurrent = (CTFontDescriptorRef) CFArrayGetValueAtIndex(matching, 0);\n\td = [[uiprivFontStyleData alloc] initWithDescriptor:current];\n\taxisDict = nil;\n\tif ([d variation] != NULL)\n\t\taxisDict = uiprivMakeVariationAxisDict([d variationAxes], [d table:kCTFontTableAvar]);\n\n\tcloseness = (struct closeness *) uiprivAlloc(n * sizeof (struct closeness), \"struct closeness[]\");\n\tfor (i = 0; i < n; i++) {\n\t\tuiFontDescriptor fields;\n\n\t\tcloseness[i].index = i;\n\t\tif (i != 0) {\n\t\t\tcurrent = (CTFontDescriptorRef) CFArrayGetValueAtIndex(matching, i);\n\t\t\td = [[uiprivFontStyleData alloc] initWithDescriptor:current];\n\t\t}\n\t\tfillDescStyleFields(d, axisDict, &fields);\n\t\tcloseness[i].weight = fields.Weight - styles->Weight;\n\t\tcloseness[i].italic = italicClosenesses[styles->Italic][fields.Italic];\n\t\tcloseness[i].stretch = fields.Stretch - styles->Stretch;\n\t\t[d release];\n\t}\n\n\t// now figure out the 3-space difference between the three and sort by that\n\t// TODO merge this loop with the previous loop?\n\tfor (i = 0; i < n; i++) {\n\t\tdouble weight, italic, stretch;\n\n\t\tweight = (double) (closeness[i].weight);\n\t\tweight *= weight;\n\t\titalic = closeness[i].italic;\n\t\titalic *= italic;\n\t\tstretch = (double) (closeness[i].stretch);\n\t\tstretch *= stretch;\n\t\tcloseness[i].distance = sqrt(weight + italic + stretch);\n\t}\n\tqsort_b(closeness, n, sizeof (struct closeness), ^(const void *aa, const void *bb) {\n\t\tconst struct closeness *a = (const struct closeness *) aa;\n\t\tconst struct closeness *b = (const struct closeness *) bb;\n\n\t\t// via http://www.gnu.org/software/libc/manual/html_node/Comparison-Functions.html#Comparison-Functions\n\t\t// 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; ?\n\t\treturn (a->distance > b->distance) - (a->distance < b->distance);\n\t});\n\t// and the first element of the sorted array is what we want\n\tout = CFArrayGetValueAtIndex(matching, closeness[0].index);\n\tCFRetain(out);\t\t\t// get rule\n\n\t// release everything\n\tif (axisDict != nil)\n\t\t[axisDict release];\n\tuiprivFree(closeness);\n\tCFRelease(matching);\n\t// and release the original descriptor since we no longer need it\n\tCFRelease(against);\n\n\treturn out;\n}\n\nCTFontDescriptorRef uiprivFontDescriptorToCTFontDescriptor(uiFontDescriptor *fd)\n{\n\tCFMutableDictionaryRef attrs;\n\tCFStringRef cffamily;\n\tCFNumberRef cfsize;\n\tCTFontDescriptorRef basedesc;\n\n\tattrs = CFDictionaryCreateMutable(NULL, 2,\n\t\t// TODO are these correct?\n\t\t&kCFCopyStringDictionaryKeyCallBacks,\n\t\t&kCFTypeDictionaryValueCallBacks);\n\tif (attrs == NULL) {\n\t\t// TODO\n\t}\n\tcffamily = CFStringCreateWithCString(NULL, fd->Family, kCFStringEncodingUTF8);\n\tif (cffamily == NULL) {\n\t\t// TODO\n\t}\n\tCFDictionaryAddValue(attrs, kCTFontFamilyNameAttribute, cffamily);\n\tCFRelease(cffamily);\n\tcfsize = CFNumberCreate(NULL, kCFNumberDoubleType, &(fd->Size));\n\tCFDictionaryAddValue(attrs, kCTFontSizeAttribute, cfsize);\n\tCFRelease(cfsize);\n\n\tbasedesc = CTFontDescriptorCreateWithAttributes(attrs);\n\tCFRelease(attrs);\t\t\t// TODO correct?\n\treturn matchStyle(basedesc, fd);\n}\n\n// fortunately features that aren't supported are simply ignored, so we can copy them all in\nCTFontDescriptorRef uiprivCTFontDescriptorAppendFeatures(CTFontDescriptorRef desc, const uiOpenTypeFeatures *otf)\n{\n\tCTFontDescriptorRef new;\n\tCFArrayRef featuresArray;\n\tCFDictionaryRef attrs;\n\tconst void *keys[1], *values[1];\n\n\tfeaturesArray = uiprivOpenTypeFeaturesToCTFeatures(otf);\n\tkeys[0] = kCTFontFeatureSettingsAttribute;\n\tvalues[0] = featuresArray;\n\tattrs = CFDictionaryCreate(NULL,\n\t\tkeys, values, 1,\n\t\t// TODO are these correct?\n\t\t&kCFCopyStringDictionaryKeyCallBacks,\n\t\t&kCFTypeDictionaryValueCallBacks);\n\tCFRelease(featuresArray);\n\tnew = CTFontDescriptorCreateCopyWithAttributes(desc, attrs);\n\tCFRelease(attrs);\n\tCFRelease(desc);\n\treturn new;\n}\n\nvoid uiprivFontDescriptorFromCTFontDescriptor(CTFontDescriptorRef ctdesc, uiFontDescriptor *uidesc)\n{\n\tCFStringRef cffamily;\n\tuiprivFontStyleData *d;\n\tNSDictionary *axisDict;\n\n\tcffamily = (CFStringRef) CTFontDescriptorCopyAttribute(ctdesc, kCTFontFamilyNameAttribute);\n\tif (cffamily == NULL) {\n\t\t// TODO\n\t}\n\t// TODO normalize this by adding a uiDarwinCFStringToText()\n\tuidesc->Family = uiDarwinNSStringToText((NSString *) cffamily);\n\tCFRelease(cffamily);\n\n\td = [[uiprivFontStyleData alloc] initWithDescriptor:ctdesc];\n\taxisDict = nil;\n\tif ([d variation] != NULL)\n\t\taxisDict = uiprivMakeVariationAxisDict([d variationAxes], [d table:kCTFontTableAvar]);\n\tfillDescStyleFields(d, axisDict, uidesc);\n\tif (axisDict != nil)\n\t\t[axisDict release];\n\t[d release];\n}\n"
  },
  {
    "path": "darwin/fonttraits.m",
    "content": "// 1 november 2017\n#import \"uipriv_darwin.h\"\n#import \"attrstr.h\"\n\n// This is the part of the font style matching and normalization code\n// that handles fonts that use a traits dictionary.\n//\n// Matching stupidity: Core Text requires an **exact match for the\n// entire traits dictionary**, otherwise it will **drop ALL the traits**.\n//\n// Normalization stupidity: Core Text uses its own scaled values for\n// weight and width, but the values are different if the font is not\n// registered and if said font is TrueType or OpenType. The values\n// for all other cases do have some named constants starting with\n// OS X 10.11, but even these aren't very consistent in practice.\n//\n// Of course, none of this is documented anywhere, so I had to do\n// both trial-and-error AND reverse engineering to figure out what's\n// what. We'll just convert Core Text's values into libui constants\n// and use those for matching.\n\nstatic BOOL fontRegistered(uiprivFontStyleData *d)\n{\n\tif (![d hasRegistrationScope])\n\t\t// header says this should be treated as the same as not registered\n\t\treturn NO;\n\t// examination of Core Text shows this is accurate\n\treturn [d registrationScope] != kCTFontManagerScopeNone;\n}\n\n// Core Text does (usWidthClass / 10) - 0.5 here.\n// 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.\n// We'll just treat them as identical to 1 and 9, respectively.\nstatic const uiTextStretch os2WidthsToStretches[] = {\n\tuiTextStretchUltraCondensed,\n\tuiTextStretchUltraCondensed,\n\tuiTextStretchExtraCondensed,\n\tuiTextStretchCondensed,\n\tuiTextStretchSemiCondensed,\n\tuiTextStretchNormal,\n\tuiTextStretchSemiExpanded,\n\tuiTextStretchExpanded,\n\tuiTextStretchExtraExpanded,\n\tuiTextStretchUltraExpanded,\n\tuiTextStretchUltraExpanded,\n};\n\nstatic const CFStringRef exceptions[] = {\n\tCFSTR(\"LucidaGrande\"),\n\tCFSTR(\".LucidaGrandeUI\"),\n\tCFSTR(\"STHeiti\"),\n\tCFSTR(\"STXihei\"),\n\tCFSTR(\"TimesNewRomanPSMT\"),\n\tNULL,\n};\n\nstatic void trySecondaryOS2Values(uiprivFontStyleData *d, uiFontDescriptor *out, BOOL *hasWeight, BOOL *hasWidth)\n{\n\tCFDataRef os2;\n\tuint16_t usWeightClass, usWidthClass;\n\tCFStringRef psname;\n\tconst CFStringRef *ex;\n\n\t*hasWeight = NO;\n\t*hasWidth = NO;\n\n\t// only applies to unregistered fonts\n\tif (fontRegistered(d))\n\t\treturn;\n\n\tos2 = [d table:kCTFontTableOS2];\n\tif (os2 == NULL)\n\t\t// no OS2 table, so no secondary values\n\t\treturn;\n\n\tif (CFDataGetLength(os2) > 77) {\n\t\tconst UInt8 *b;\n\n\t\tb = CFDataGetBytePtr(os2);\n\n\t\tusWeightClass = ((uint16_t) (b[4])) << 8;\n\t\tusWeightClass |= (uint16_t) (b[5]);\n\t\tif (usWeightClass <= 1000) {\n\t\t\tif (usWeightClass < 11)\n\t\t\t\tusWeightClass *= 100;\n\t\t\t*hasWeight = YES;\n\t\t}\n\n\t\tusWidthClass = ((uint16_t) (b[6])) << 8;\n\t\tusWidthClass |= (uint16_t) (b[7]);\n\t\tif (usWidthClass <= 10)\n\t\t\t*hasWidth = YES;\n\t} else {\n\t\tusWeightClass = 0;\n\t\t*hasWeight = YES;\n\n\t\tusWidthClass = 0;\n\t\t*hasWidth = YES;\n\t}\n\tif (*hasWeight)\n\t\t// we can just use this directly\n\t\tout->Weight = usWeightClass;\n\tif (*hasWidth)\n\t\tout->Stretch = os2WidthsToStretches[usWidthClass];\n\tCFRelease(os2);\n\n\t// don't use secondary weights in the event of special predefined names\n\tpsname = [d postScriptName];\n\tfor (ex = exceptions; *ex != NULL; ex++)\n\t\tif (CFEqual(psname, *ex)) {\n\t\t\t*hasWeight = NO;\n\t\t\tbreak;\n\t\t}\n}\n\nstatic BOOL testTTFOTFSubfamilyName(CFStringRef name, CFStringRef want)\n{\n\tCFRange range;\n\n\tif (name == NULL)\n\t\treturn NO;\n\trange.location = 0;\n\trange.length = CFStringGetLength(name);\n\treturn CFStringFindWithOptions(name, want, range,\n\t\t(kCFCompareCaseInsensitive | kCFCompareBackwards | kCFCompareNonliteral), NULL) != false;\n}\n\nstatic BOOL testTTFOTFSubfamilyNames(uiprivFontStyleData *d, CFStringRef want)\n{\n\tswitch ([d fontFormat]) {\n\tcase kCTFontFormatOpenTypePostScript:\n\tcase kCTFontFormatOpenTypeTrueType:\n\tcase kCTFontFormatTrueType:\n\t\tbreak;\n\tdefault:\n\t\treturn NO;\n\t}\n\n\tif (testTTFOTFSubfamilyName([d preferredSubFamilyName], want))\n\t\treturn YES;\n\tif (testTTFOTFSubfamilyName([d subFamilyName], want))\n\t\treturn YES;\n\tif (testTTFOTFSubfamilyName([d fullName], want))\n\t\treturn YES;\n\tif (testTTFOTFSubfamilyName([d preferredFamilyName], want))\n\t\treturn YES;\n\treturn testTTFOTFSubfamilyName([d familyName], want);\n}\n\n// work around a bug in libFontRegistry.dylib\nstatic BOOL shouldReallyBeThin(uiprivFontStyleData *d)\n{\n\treturn testTTFOTFSubfamilyNames(d, CFSTR(\"W1\"));\n}\n\n// work around a bug in libFontRegistry.dylib\nstatic BOOL shouldReallyBeSemiCondensed(uiprivFontStyleData *d)\n{\n\treturn testTTFOTFSubfamilyNames(d, CFSTR(\"Semi Condensed\"));\n}\n\nvoid uiprivProcessFontTraits(uiprivFontStyleData *d, uiFontDescriptor *out)\n{\n\tdouble weight, width;\n\tBOOL hasWeight, hasWidth;\n\n\thasWeight = NO;\n\thasWidth = NO;\n\ttrySecondaryOS2Values(d, out, &hasWeight, &hasWidth);\n\n\tweight = [d weight];\n\twidth = [d width];\n\n\tif (!hasWeight)\n\t\t// TODO this scale is a bit lopsided\n\t\tif (weight <= -0.7)\n\t\t\tout->Weight = uiTextWeightThin;\n\t\telse if (weight <= -0.5)\n\t\t\tout->Weight = uiTextWeightUltraLight;\n\t\telse if (weight <= -0.3)\n\t\t\tout->Weight = uiTextWeightLight;\n\t\telse if (weight <= -0.23) {\n\t\t\tout->Weight = uiTextWeightBook;\n\t\t\tif (shouldReallyBeThin(d))\n\t\t\t\tout->Weight = uiTextWeightThin;\n\t\t} else if (weight <= 0.0)\n\t\t\tout->Weight = uiTextWeightNormal;\n\t\telse if (weight <= 0.23)\n\t\t\tout->Weight = uiTextWeightMedium;\n\t\telse if (weight <= 0.3)\n\t\t\tout->Weight = uiTextWeightSemiBold;\n\t\telse if (weight <= 0.4)\n\t\t\tout->Weight = uiTextWeightBold;\n\t\telse if (weight <= 0.5)\n\t\t\tout->Weight = uiTextWeightUltraBold;\n\t\telse if (weight <= 0.7)\n\t\t\tout->Weight = uiTextWeightHeavy;\n\t\telse\n\t\t\tout->Weight = uiTextWeightUltraHeavy;\n\n\tif (!hasWidth)\n\t\t// TODO this scale is a bit lopsided\n\t\tif (width <= -0.7) {\n\t\t\tout->Stretch = uiTextStretchUltraCondensed;\n\t\t\tif (shouldReallyBeSemiCondensed(d))\n\t\t\t\tout->Stretch = uiTextStretchSemiCondensed;\n\t\t} else if (width <= -0.5)\n\t\t\tout->Stretch = uiTextStretchExtraCondensed;\n\t\telse if (width <= -0.2)\n\t\t\tout->Stretch = uiTextStretchCondensed;\n\t\telse if (width <= -0.1)\n\t\t\tout->Stretch = uiTextStretchSemiCondensed;\n\t\telse if (width <= 0.0)\n\t\t\tout->Stretch = uiTextStretchNormal;\n\t\telse if (width <= 0.1)\n\t\t\tout->Stretch = uiTextStretchSemiExpanded;\n\t\telse if (width <= 0.2)\n\t\t\tout->Stretch = uiTextStretchExpanded;\n\t\telse if (width <= 0.6)\n\t\t\tout->Stretch = uiTextStretchExtraExpanded;\n\t\telse\n\t\t\tout->Stretch = uiTextStretchUltraExpanded;\n}\n"
  },
  {
    "path": "darwin/fontvariation.m",
    "content": "// 2 november 2017\n#import \"uipriv_darwin.h\"\n#import \"attrstr.h\"\n\n// This is the part of the font style matching and normalization code\n// that handles fonts that use the fvar table.\n//\n// Matching stupidity: Core Text **doesn't even bother** matching\n// these, even if you tell it to do so explicitly. It'll always return\n// all variations for a given font.\n//\n// Normalization stupidity: Core Text doesn't normalize the fvar\n// table values for us, so we'll have to do it ourselves. Furthermore,\n// Core Text doesn't provide an API for accessing the avar table, if\n// any, so we must do so ourselves. (TODO does Core Text even\n// follow the avar table if a font has it?)\n//\n// Thankfully, normalization is well-defined in both TrueType and\n// OpenType and seems identical in both, so we can just normalize\n// the values and then convert them linearly to libui values for\n// matching.\n//\n// References:\n// - https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6fvar.html\n// - https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6avar.html\n// - https://www.microsoft.com/typography/otspec/fvar.htm\n// - https://www.microsoft.com/typography/otspec/otvaroverview.htm#CSN\n// - https://www.microsoft.com/typography/otspec/otff.htm\n// - https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6.html#Types\n// - https://www.microsoft.com/typography/otspec/avar.htm\n\n// TODO Skia doesn't quite map correctly; notice what passes for condensed in the drawtext example\n// 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)\n\n#define fvarWeight 0x77676874\n#define fvarWidth 0x77647468\n\n// TODO explain why these are signed\ntypedef int32_t fixed1616;\ntypedef int16_t fixed214;\n\n// 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\nstatic fixed1616 doubleToFixed1616(double d)\n{\n\tdouble ipart, fpart;\n\tlong flong;\n\tint16_t i16;\n\tuint32_t ret;\n\n\tfpart = modf(d, &ipart);\n\t// fpart must be unsigned; modf() gives us fpart with the same sign as d (so we have to adjust both ipart and fpart appropriately)\n\tif (fpart < 0) {\n\t\tipart -= 1;\n\t\tfpart = 1 + fpart;\n\t}\n\tfpart *= 65536;\n\tflong = lround(fpart);\n\ti16 = (int16_t) ipart;\n\tret = (uint32_t) ((uint16_t) i16);\n\tret <<= 16;\n\tret |= (uint16_t) (flong & 0xFFFF);\n\treturn (fixed1616) ret;\n}\n\n// see also https://stackoverflow.com/questions/8506317/fixed-point-unsigned-division-in-c and freetype's FT_DivFix()\n// TODO figure out the specifics of freetype's more complex implementation that shifts b and juggles signs\nstatic fixed1616 fixed1616Divide(fixed1616 a, fixed1616 b)\n{\n\tuint32_t u;\n\tint64_t a64;\n\n\tu = (uint32_t) a;\n\ta64 = (int64_t) (((uint64_t) u) << 16);\n\treturn (fixed1616) (a64 / b);\n}\n\nstatic fixed214 fixed1616ToFixed214(fixed1616 f)\n{\n\tuint32_t t;\n\tuint32_t topbit;\n\n\tt = (uint32_t) (f + 0x00000002);\n\ttopbit = t & 0x80000000;\n\tt >>= 2;\n\tif (topbit != 0)\n\t\tt |= 0xC000000;\n\treturn (fixed214) (t & 0xFFFF);\n}\n\nstatic double fixed214ToDouble(fixed214 f)\n{\n\tuint16_t u;\n\tdouble base;\n\tdouble frac;\n\n\tu = (uint16_t) f;\n\tswitch ((u >> 14) & 0x3) {\n\tcase 0:\n\t\tbase = 0;\n\t\tbreak;\n\tcase 1:\n\t\tbase = 1;\n\t\tbreak;\n\tcase 2:\n\t\tbase = -2;\n\t\tbreak;\n\tcase 3:\n\t\tbase = -1;\n\t}\n\tfrac = ((double) (u & 0x3FFF)) / 16384;\n\treturn base + frac;\n}\n\nstatic fixed1616 fixed214ToFixed1616(fixed214 f)\n{\n\tint32_t t;\n\n\tt = (int32_t) ((int16_t) f);\n\tt <<= 2;\n\treturn (fixed1616) (t - 0x00000002);\n}\n\nstatic const fixed1616 fixed1616Negative1 = (int32_t) ((uint32_t) 0xFFFF0000);\nstatic const fixed1616 fixed1616Zero = 0x00000000;\nstatic const fixed1616 fixed1616Positive1 = 0x00010000;\n\nstatic fixed1616 fixed1616Normalize(fixed1616 val, fixed1616 min, fixed1616 max, fixed1616 def)\n{\n\tif (val < min)\n\t\tval = min;\n\tif (val > max)\n\t\tval = max;\n\tif (val < def)\n\t\treturn fixed1616Divide(-(def - val), (def - min));\n\tif (val > def)\n\t\treturn fixed1616Divide((val - def), (max - def));\n\treturn fixed1616Zero;\n}\n\nstatic fixed214 normalizedTo214(fixed1616 val, const fixed1616 *avarMappings, size_t avarCount)\n{\n\tif (val < fixed1616Negative1)\n\t\tval = fixed1616Negative1;\n\tif (val > fixed1616Positive1)\n\t\tval = fixed1616Positive1;\n\tif (avarCount != 0) {\n\t\tsize_t start, end;\n\t\tfixed1616 startFrom, endFrom;\n\t\tfixed1616 startTo, endTo;\n\n\t\tfor (end = 0; end < avarCount; end += 2) {\n\t\t\tendFrom = avarMappings[end];\n\t\t\tendTo = avarMappings[end + 1];\n\t\t\tif (endFrom >= val)\n\t\t\t\tbreak;\n\t\t}\n\t\tif (endFrom == val)\n\t\t\tval = endTo;\n\t\telse {\n\t\t\tstart = end - 2;\n\t\t\tstartFrom = avarMappings[start];\n\t\t\tstartTo = avarMappings[start + 1];\n\t\t\tval = fixed1616Divide((val - startFrom), (endFrom - startFrom));\n\t\t\t// TODO find a font with an avar table and make sure this works, or if we need to use special code for this too\n\t\t\tval *= (endTo - startTo);\n\t\t\tval += startTo;\n\t\t}\n\t}\n\treturn fixed1616ToFixed214(val);\n}\n\nstatic fixed1616 *avarExtract(CFDataRef table, CFIndex index, size_t *n)\n{\n\tconst UInt8 *b;\n\tsize_t off;\n\tsize_t i, nEntries;\n\tfixed1616 *entries;\n\tfixed1616 *p;\n\n\tb = CFDataGetBytePtr(table);\n\toff = 8;\n#define nextuint16be() ((((uint16_t) (b[off])) << 8) | ((uint16_t) (b[off + 1])))\n\tfor (; index > 0; index--) {\n\t\tnEntries = (size_t) nextuint16be();\n\t\toff += 2;\n\t\toff += 4 * nEntries;\n\t}\n\tnEntries = nextuint16be();\n\t*n = nEntries * 2;\n\tentries = (fixed1616 *) uiprivAlloc(*n * sizeof (fixed1616), \"fixed1616[]\");\n\tp = entries;\n\tfor (i = 0; i < *n; i++) {\n\t\t*p++ = fixed214ToFixed1616((fixed214) nextuint16be());\n\t\toff += 2;\n\t}\n\treturn entries;\n}\n\nstatic BOOL extractAxisDictValue(CFDictionaryRef dict, CFStringRef key, fixed1616 *out)\n{\n\tCFNumberRef num;\n\tdouble v;\n\n\tnum = (CFNumberRef) CFDictionaryGetValue(dict, key);\n\tif (CFNumberGetValue(num, kCFNumberDoubleType, &v) == false)\n\t\treturn NO;\n\t*out = doubleToFixed1616(v);\n\treturn YES;\n}\n\n// TODO here and elsewhere: make sure all Objective-C classes and possibly also custom method names have uipriv prefixes\n@interface fvarAxis : NSObject {\n\tfixed1616 min;\n\tfixed1616 max;\n\tfixed1616 def;\n\tfixed1616 *avarMappings;\n\tsize_t avarCount;\n}\n- (id)initWithIndex:(CFIndex)i dict:(CFDictionaryRef)dict avarTable:(CFDataRef)table;\n- (double)normalize:(double)v;\n@end\n\n@implementation fvarAxis\n\n- (id)initWithIndex:(CFIndex)i dict:(CFDictionaryRef)dict avarTable:(CFDataRef)table\n{\n\tself = [super init];\n\tif (self) {\n\t\tself->avarMappings = NULL;\n\t\tself->avarCount = 0;\n\t\tif (!extractAxisDictValue(dict, kCTFontVariationAxisMinimumValueKey, &(self->min)))\n\t\t\tgoto fail;\n\t\tif (!extractAxisDictValue(dict, kCTFontVariationAxisMaximumValueKey, &(self->max)))\n\t\t\tgoto fail;\n\t\tif (!extractAxisDictValue(dict, kCTFontVariationAxisDefaultValueKey, &(self->def)))\n\t\t\tgoto fail;\n\t\tif (table != NULL)\n\t\t\tself->avarMappings = avarExtract(table, i, &(self->avarCount));\n\t}\n\treturn self;\n\nfail:\n\t[self release];\n\treturn nil;\n}\n\n- (void)dealloc\n{\n\tif (self->avarMappings != NULL) {\n\t\tuiprivFree(self->avarMappings);\n\t\tself->avarMappings = NULL;\n\t}\n\t[super dealloc];\n}\n\n- (double)normalize:(double)d\n{\n\tfixed1616 n;\n\tfixed214 n2;\n\n\tn = doubleToFixed1616(d);\n\tn = fixed1616Normalize(n, self->min, self->max, self->def);\n\tn2 = normalizedTo214(n, self->avarMappings, self->avarCount);\n\treturn fixed214ToDouble(n2);\n}\n\n@end\n\nNSDictionary *uiprivMakeVariationAxisDict(CFArrayRef axes, CFDataRef avarTable)\n{\n\tCFDictionaryRef axis;\n\tCFIndex i, n;\n\tNSMutableDictionary *out;\n\n\tn = CFArrayGetCount(axes);\n\tout = [NSMutableDictionary new];\n\tfor (i = 0; i < n; i++) {\n\t\tCFNumberRef key;\n\n\t\taxis = (CFDictionaryRef) CFArrayGetValueAtIndex(axes, i);\n\t\tkey = (CFNumberRef) CFDictionaryGetValue(axis, kCTFontVariationAxisIdentifierKey);\n\t\t[out setObject:[[fvarAxis alloc] initWithIndex:i dict:axis avarTable:avarTable]\n\t\t\tforKey:((NSNumber *) key)];\n\t}\n\tif (avarTable != NULL)\n\t\tCFRelease(avarTable);\n\treturn out;\n}\n\n#define fvarAxisKey(n) [NSNumber numberWithUnsignedInteger:n]\n\nstatic BOOL tryAxis(NSDictionary *axisDict, CFDictionaryRef var, NSNumber *key, double *out)\n{\n\tfvarAxis *axis;\n\tCFNumberRef num;\n\n\taxis = (fvarAxis *) [axisDict objectForKey:key];\n\tif (axis == nil)\n\t\treturn NO;\n\tnum = (CFNumberRef) CFDictionaryGetValue(var, (CFNumberRef) key);\n\tif (num == nil)\n\t\treturn NO;\n\tif (CFNumberGetValue(num, kCFNumberDoubleType, out) == false) {\n\t\t// TODO\n\t\treturn NO;\n\t}\n\t*out = [axis normalize:*out];\n\treturn YES;\n}\n\nvoid uiprivProcessFontVariation(uiprivFontStyleData *d, NSDictionary *axisDict, uiFontDescriptor *out)\n{\n\tCFDictionaryRef var;\n\tdouble v;\n\n\tout->Weight = uiTextWeightNormal;\n\tout->Stretch = uiTextStretchNormal;\n\n\tvar = [d variation];\n\n\tif (tryAxis(axisDict, var, fvarAxisKey(fvarWeight), &v)) {\n\t\t// v is now a value between -1 and 1 scaled linearly between discrete points\n\t\t// we want a linear value between 0 and 1000 with 400 being normal\n\t\tif (v < 0) {\n\t\t\tv += 1;\n\t\t\tout->Weight = (uiTextWeight) (v * 400);\n\t\t} else if (v > 0)\n\t\t\tout->Weight += (uiTextWeight) (v * 600);\n\t}\n\n\tif (tryAxis(axisDict, var, fvarAxisKey(fvarWidth), &v)) {\n\t\t// likewise, but with stretches, we go from 0 to 8 with 4 being directly between the two, so this is sufficient\n\t\tv += 1;\n\t\tout->Stretch = (uiTextStretch) (v * 4);\n\t}\n}\n"
  },
  {
    "path": "darwin/form.m",
    "content": "// 7 june 2016\n#import \"uipriv_darwin.h\"\n\n// TODO in the test program, sometimes one of the radio buttons can disappear (try when spaced)\n\n@interface formChild : NSView\n@property uiControl *c;\n@property (strong) NSTextField *label;\n@property BOOL stretchy;\n@property NSLayoutPriority oldHorzHuggingPri;\n@property NSLayoutPriority oldVertHuggingPri;\n@property (strong) NSLayoutConstraint *baseline;\n@property (strong) NSLayoutConstraint *leading;\n@property (strong) NSLayoutConstraint *top;\n@property (strong) NSLayoutConstraint *trailing;\n@property (strong) NSLayoutConstraint *bottom;\n- (id)initWithLabel:(NSTextField *)l;\n- (void)onDestroy;\n- (NSView *)view;\n@end\n\n@interface formView : NSView {\n\tuiForm *f;\n\tNSMutableArray *children;\n\tint padded;\n\n\tNSLayoutConstraint *first;\n\tNSMutableArray *inBetweens;\n\tNSLayoutConstraint *last;\n\tNSMutableArray *widths;\n\tNSMutableArray *leadings;\n\tNSMutableArray *middles;\n\tNSMutableArray *trailings;\n}\n- (id)initWithF:(uiForm *)ff;\n- (void)onDestroy;\n- (void)removeOurConstraints;\n- (void)syncEnableStates:(int)enabled;\n- (CGFloat)paddingAmount;\n- (void)establishOurConstraints;\n- (void)append:(NSString *)label c:(uiControl *)c stretchy:(int)stretchy;\n- (void)delete:(int)n;\n- (int)isPadded;\n- (void)setPadded:(int)p;\n- (BOOL)hugsTrailing;\n- (BOOL)hugsBottom;\n- (int)nStretchy;\n@end\n\nstruct uiForm {\n\tuiDarwinControl c;\n\tformView *view;\n};\n\n@implementation formChild\n\n- (id)initWithLabel:(NSTextField *)l\n{\n\tself = [super initWithFrame:NSZeroRect];\n\tif (self) {\n\t\tself.label = l;\n\t\t[self.label setTranslatesAutoresizingMaskIntoConstraints:NO];\n\t\t[self.label setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal];\n\t\t[self.label setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationVertical];\n\t\t[self.label setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal];\n\t\t[self.label setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationVertical];\n\t\t[self addSubview:self.label];\n\n\t\tself.leading = uiprivMkConstraint(self.label, NSLayoutAttributeLeading,\n\t\t\tNSLayoutRelationGreaterThanOrEqual,\n\t\t\tself, NSLayoutAttributeLeading,\n\t\t\t1, 0,\n\t\t\t@\"uiForm label leading\");\n\t\t[self addConstraint:self.leading];\n\t\tself.top = uiprivMkConstraint(self.label, NSLayoutAttributeTop,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tself, NSLayoutAttributeTop,\n\t\t\t1, 0,\n\t\t\t@\"uiForm label top\");\n\t\t[self addConstraint:self.top];\n\t\tself.trailing = uiprivMkConstraint(self.label, NSLayoutAttributeTrailing,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tself, NSLayoutAttributeTrailing,\n\t\t\t1, 0,\n\t\t\t@\"uiForm label trailing\");\n\t\t[self addConstraint:self.trailing];\n\t\tself.bottom = uiprivMkConstraint(self.label, NSLayoutAttributeBottom,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tself, NSLayoutAttributeBottom,\n\t\t\t1, 0,\n\t\t\t@\"uiForm label bottom\");\n\t\t[self addConstraint:self.bottom];\n\t}\n\treturn self;\n}\n\n- (void)onDestroy\n{\n\t[self removeConstraint:self.trailing];\n\tself.trailing = nil;\n\t[self removeConstraint:self.top];\n\tself.top = nil;\n\t[self removeConstraint:self.bottom];\n\tself.bottom = nil;\n\n\t[self.label removeFromSuperview];\n\tself.label = nil;\n}\n\n- (NSView *)view\n{\n\treturn (NSView *) uiControlHandle(self.c);\n}\n\n@end\n\n@implementation formView\n\n- (id)initWithF:(uiForm *)ff\n{\n\tself = [super initWithFrame:NSZeroRect];\n\tif (self != nil) {\n\t\tself->f = ff;\n\t\tself->padded = 0;\n\t\tself->children = [NSMutableArray new];\n\n\t\tself->inBetweens = [NSMutableArray new];\n\t\tself->widths = [NSMutableArray new];\n\t\tself->leadings = [NSMutableArray new];\n\t\tself->middles = [NSMutableArray new];\n\t\tself->trailings = [NSMutableArray new];\n\t}\n\treturn self;\n}\n\n- (void)onDestroy\n{\n\tformChild *fc;\n\n\t[self removeOurConstraints];\n\t[self->inBetweens release];\n\t[self->widths release];\n\t[self->leadings release];\n\t[self->middles release];\n\t[self->trailings release];\n\n\tfor (fc in self->children) {\n\t\t[self removeConstraint:fc.baseline];\n\t\tfc.baseline = nil;\n\t\tuiControlSetParent(fc.c, NULL);\n\t\tuiDarwinControlSetSuperview(uiDarwinControl(fc.c), nil);\n\t\tuiControlDestroy(fc.c);\n\t\t[fc onDestroy];\n\t\t[fc removeFromSuperview];\n\t}\n\t[self->children release];\n}\n\n- (void)removeOurConstraints\n{\n\tif (self->first != nil) {\n\t\t[self removeConstraint:self->first];\n\t\t[self->first release];\n\t\tself->first = nil;\n\t}\n\tif ([self->inBetweens count] != 0) {\n\t\t[self removeConstraints:self->inBetweens];\n\t\t[self->inBetweens removeAllObjects];\n\t}\n\tif (self->last != nil) {\n\t\t[self removeConstraint:self->last];\n\t\t[self->last release];\n\t\tself->last = nil;\n\t}\n\tif ([self->widths count] != 0) {\n\t\t[self removeConstraints:self->widths];\n\t\t[self->widths removeAllObjects];\n\t}\n\tif ([self->leadings count] != 0) {\n\t\t[self removeConstraints:self->leadings];\n\t\t[self->leadings removeAllObjects];\n\t}\n\tif ([self->middles count] != 0) {\n\t\t[self removeConstraints:self->middles];\n\t\t[self->middles removeAllObjects];\n\t}\n\tif ([self->trailings count] != 0) {\n\t\t[self removeConstraints:self->trailings];\n\t\t[self->trailings removeAllObjects];\n\t}\n}\n\n- (void)syncEnableStates:(int)enabled\n{\n\tformChild *fc;\n\n\tfor (fc in self->children)\n\t\tuiDarwinControlSyncEnableState(uiDarwinControl(fc.c), enabled);\n}\n\n- (CGFloat)paddingAmount\n{\n\tif (!self->padded)\n\t\treturn 0.0;\n\treturn uiDarwinPaddingAmount(NULL);\n}\n\n- (void)establishOurConstraints\n{\n\tformChild *fc;\n\tCGFloat padding;\n\tNSView *prev, *prevlabel;\n\tNSLayoutConstraint *c;\n\n\t[self removeOurConstraints];\n\tif ([self->children count] == 0)\n\t\treturn;\n\tpadding = [self paddingAmount];\n\n\t// first arrange the children vertically and make them the same width\n\tprev = nil;\n\tfor (fc in self->children) {\n\t\t[fc setHidden:!uiControlVisible(fc.c)];\n\t\tif (!uiControlVisible(fc.c))\n\t\t\tcontinue;\n\t\tif (prev == nil) {\t\t\t// first view\n\t\t\tself->first = uiprivMkConstraint(self, NSLayoutAttributeTop,\n\t\t\t\tNSLayoutRelationEqual,\n\t\t\t\t[fc view], NSLayoutAttributeTop,\n\t\t\t\t1, 0,\n\t\t\t\t@\"uiForm first vertical constraint\");\n\t\t\t[self addConstraint:self->first];\n\t\t\t[self->first retain];\n\t\t\tprev = [fc view];\n\t\t\tprevlabel = fc;\n\t\t\tcontinue;\n\t\t}\n\t\t// not the first; link it\n\t\tc = uiprivMkConstraint(prev, NSLayoutAttributeBottom,\n\t\t\tNSLayoutRelationEqual,\n\t\t\t[fc view], NSLayoutAttributeTop,\n\t\t\t1, -padding,\n\t\t\t@\"uiForm in-between vertical constraint\");\n\t\t[self addConstraint:c];\n\t\t[self->inBetweens addObject:c];\n\t\t// and make the same width\n\t\tc = uiprivMkConstraint(prev, NSLayoutAttributeWidth,\n\t\t\tNSLayoutRelationEqual,\n\t\t\t[fc view], NSLayoutAttributeWidth,\n\t\t\t1, 0,\n\t\t\t@\"uiForm control width constraint\");\n\t\t[self addConstraint:c];\n\t\t[self->widths addObject:c];\n\t\tc = uiprivMkConstraint(prevlabel, NSLayoutAttributeWidth,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tfc, NSLayoutAttributeWidth,\n\t\t\t1, 0,\n\t\t\t@\"uiForm label lwidth constraint\");\n\t\t[self addConstraint:c];\n\t\t[self->widths addObject:c];\n\t\tprev = [fc view];\n\t\tprevlabel = fc;\n\t}\n\tif (prev == nil)\t\t// all hidden; act as if nothing there\n\t\treturn;\n\tself->last = uiprivMkConstraint(prev, NSLayoutAttributeBottom,\n\t\tNSLayoutRelationEqual,\n\t\tself, NSLayoutAttributeBottom,\n\t\t1, 0,\n\t\t@\"uiForm last vertical constraint\");\n\t[self addConstraint:self->last];\n\t[self->last retain];\n\n\t// now arrange the controls horizontally\n\tfor (fc in self->children) {\n\t\tif (!uiControlVisible(fc.c))\n\t\t\tcontinue;\n\t\tc = uiprivMkConstraint(self, NSLayoutAttributeLeading,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tfc, NSLayoutAttributeLeading,\n\t\t\t1, 0,\n\t\t\t@\"uiForm leading constraint\");\n\t\t[self addConstraint:c];\n\t\t[self->leadings addObject:c];\n\t\t// coerce the control to be as wide as possible\n\t\t// see http://stackoverflow.com/questions/37710892/in-auto-layout-i-set-up-labels-that-shouldnt-grow-horizontally-and-controls-th\n\t\tc = uiprivMkConstraint(self, NSLayoutAttributeLeading,\n\t\t\tNSLayoutRelationEqual,\n\t\t\t[fc view], NSLayoutAttributeLeading,\n\t\t\t1, 0,\n\t\t\t@\"uiForm leading constraint\");\n\t\t[c setPriority:NSLayoutPriorityDefaultHigh];\n\t\t[self addConstraint:c];\n\t\t[self->leadings addObject:c];\n\t\tc = uiprivMkConstraint(fc, NSLayoutAttributeTrailing,\n\t\t\tNSLayoutRelationEqual,\n\t\t\t[fc view], NSLayoutAttributeLeading,\n\t\t\t1, -padding,\n\t\t\t@\"uiForm middle constraint\");\n\t\t[self addConstraint:c];\n\t\t[self->middles addObject:c];\n\t\tc = uiprivMkConstraint([fc view], NSLayoutAttributeTrailing,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tself, NSLayoutAttributeTrailing,\n\t\t\t1, 0,\n\t\t\t@\"uiForm trailing constraint\");\n\t\t[self addConstraint:c];\n\t\t[self->trailings addObject:c];\n\t\t// TODO\n\t\tc = uiprivMkConstraint(fc, NSLayoutAttributeBottom,\n\t\t\tNSLayoutRelationLessThanOrEqual,\n\t\t\tself, NSLayoutAttributeBottom,\n\t\t\t1, 0,\n\t\t\t@\"TODO\");\n\t\t[self addConstraint:c];\n\t\t[self->trailings addObject:c];\n\t}\n\n\t// and make all stretchy controls have the same height\n\tprev = nil;\n\tfor (fc in self->children) {\n\t\tif (!uiControlVisible(fc.c))\n\t\t\tcontinue;\n\t\tif (!fc.stretchy)\n\t\t\tcontinue;\n\t\tif (prev == nil) {\n\t\t\tprev = [fc view];\n\t\t\tcontinue;\n\t\t}\n\t\tc = uiprivMkConstraint([fc view], NSLayoutAttributeHeight,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tprev, NSLayoutAttributeHeight,\n\t\t\t1, 0,\n\t\t\t@\"uiForm stretchy constraint\");\n\t\t[self addConstraint:c];\n\t\t// TODO make a dedicated array for this\n\t\t[self->leadings addObject:c];\n\t}\n\n\t// 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)\n}\n\n- (void)append:(NSString *)label c:(uiControl *)c stretchy:(int)stretchy\n{\n\tformChild *fc;\n\tNSLayoutPriority priority;\n\tNSLayoutAttribute attribute;\n\tint oldnStretchy;\n\n\tfc = [[formChild alloc] initWithLabel:uiprivNewLabel(label)];\n\tfc.c = c;\n\tfc.stretchy = stretchy;\n\tfc.oldHorzHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(fc.c), NSLayoutConstraintOrientationHorizontal);\n\tfc.oldVertHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(fc.c), NSLayoutConstraintOrientationVertical);\n\t[fc setTranslatesAutoresizingMaskIntoConstraints:NO];\n\t[self addSubview:fc];\n\n\tuiControlSetParent(fc.c, uiControl(self->f));\n\tuiDarwinControlSetSuperview(uiDarwinControl(fc.c), self);\n\tuiDarwinControlSyncEnableState(uiDarwinControl(fc.c), uiControlEnabledToUser(uiControl(self->f)));\n\n\t// if a control is stretchy, it should not hug vertically\n\t// otherwise, it should *forcibly* hug\n\tif (fc.stretchy)\n\t\tpriority = NSLayoutPriorityDefaultLow;\n\telse\n\t\t// LONGTERM will default high work?\n\t\tpriority = NSLayoutPriorityRequired;\n\tuiDarwinControlSetHuggingPriority(uiDarwinControl(fc.c), priority, NSLayoutConstraintOrientationVertical);\n\t// make sure controls don't hug their horizontal direction so they fill the width of the view\n\tuiDarwinControlSetHuggingPriority(uiDarwinControl(fc.c), NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationHorizontal);\n\n\t// and constrain the baselines to position the label vertically\n\t// if the view is a scroll view, align tops, not baselines\n\t// this is what Interface Builder does\n\tattribute = NSLayoutAttributeBaseline;\n\tif ([[fc view] isKindOfClass:[NSScrollView class]])\n\t\tattribute = NSLayoutAttributeTop;\n\tfc.baseline = uiprivMkConstraint(fc.label, attribute,\n\t\tNSLayoutRelationEqual,\n\t\t[fc view], attribute,\n\t\t1, 0,\n\t\t@\"uiForm baseline constraint\");\n\t[self addConstraint:fc.baseline];\n\n\toldnStretchy = [self nStretchy];\n\t[self->children addObject:fc];\n\n\t[self establishOurConstraints];\n\tif (fc.stretchy)\n\t\tif (oldnStretchy == 0)\n\t\t\tuiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->f));\n\n\t[fc release];\t\t// we don't need the initial reference now\n}\n\n- (void)delete:(int)n\n{\n\tformChild *fc;\n\tint stretchy;\n\n\tfc = (formChild *) [self->children objectAtIndex:n];\n\tstretchy = fc.stretchy;\n\n\tuiControlSetParent(fc.c, NULL);\n\tuiDarwinControlSetSuperview(uiDarwinControl(fc.c), nil);\n\n\tuiDarwinControlSetHuggingPriority(uiDarwinControl(fc.c), fc.oldHorzHuggingPri, NSLayoutConstraintOrientationHorizontal);\n\tuiDarwinControlSetHuggingPriority(uiDarwinControl(fc.c), fc.oldVertHuggingPri, NSLayoutConstraintOrientationVertical);\n\n\t[fc onDestroy];\n\t[self->children removeObjectAtIndex:n];\n\n\t[self establishOurConstraints];\n\tif (stretchy)\n\t\tif ([self nStretchy] == 0)\n\t\t\tuiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->f));\n}\n\n- (int)isPadded\n{\n\treturn self->padded;\n}\n\n- (void)setPadded:(int)p\n{\n\tCGFloat padding;\n\tNSLayoutConstraint *c;\n\n\tself->padded = p;\n\tpadding = [self paddingAmount];\n\tfor (c in self->inBetweens)\n\t\t[c setConstant:-padding];\n\tfor (c in self->middles)\n\t\t[c setConstant:-padding];\n}\n\n- (BOOL)hugsTrailing\n{\n\treturn YES;\t\t\t// always hug trailing\n}\n\n- (BOOL)hugsBottom\n{\n\t// only hug if we have stretchy\n\treturn [self nStretchy] != 0;\n}\n\n- (int)nStretchy\n{\n\tformChild *fc;\n\tint n;\n\n\tn = 0;\n\tfor (fc in self->children) {\n\t\tif (!uiControlVisible(fc.c))\n\t\t\tcontinue;\n\t\tif (fc.stretchy)\n\t\t\tn++;\n\t}\n\treturn n;\n}\n\n@end\n\nstatic void uiFormDestroy(uiControl *c)\n{\n\tuiForm *f = uiForm(c);\n\n\t[f->view onDestroy];\n\t[f->view release];\n\tuiFreeControl(uiControl(f));\n}\n\nuiDarwinControlDefaultHandle(uiForm, view)\nuiDarwinControlDefaultParent(uiForm, view)\nuiDarwinControlDefaultSetParent(uiForm, view)\nuiDarwinControlDefaultToplevel(uiForm, view)\nuiDarwinControlDefaultVisible(uiForm, view)\nuiDarwinControlDefaultShow(uiForm, view)\nuiDarwinControlDefaultHide(uiForm, view)\nuiDarwinControlDefaultEnabled(uiForm, view)\nuiDarwinControlDefaultEnable(uiForm, view)\nuiDarwinControlDefaultDisable(uiForm, view)\n\nstatic void uiFormSyncEnableState(uiDarwinControl *c, int enabled)\n{\n\tuiForm *f = uiForm(c);\n\n\tif (uiDarwinShouldStopSyncEnableState(uiDarwinControl(f), enabled))\n\t\treturn;\n\t[f->view syncEnableStates:enabled];\n}\n\nuiDarwinControlDefaultSetSuperview(uiForm, view)\n\nstatic BOOL uiFormHugsTrailingEdge(uiDarwinControl *c)\n{\n\tuiForm *f = uiForm(c);\n\n\treturn [f->view hugsTrailing];\n}\n\nstatic BOOL uiFormHugsBottom(uiDarwinControl *c)\n{\n\tuiForm *f = uiForm(c);\n\n\treturn [f->view hugsBottom];\n}\n\nstatic void uiFormChildEdgeHuggingChanged(uiDarwinControl *c)\n{\n\tuiForm *f = uiForm(c);\n\n\t[f->view establishOurConstraints];\n}\n\nuiDarwinControlDefaultHuggingPriority(uiForm, view)\nuiDarwinControlDefaultSetHuggingPriority(uiForm, view)\n\nstatic void uiFormChildVisibilityChanged(uiDarwinControl *c)\n{\n\tuiForm *f = uiForm(c);\n\n\t[f->view establishOurConstraints];\n}\n\nvoid uiFormAppend(uiForm *f, const char *label, uiControl *c, int stretchy)\n{\n\t// LONGTERM on other platforms\n\t// or at leat allow this and implicitly turn it into a spacer\n\tif (c == NULL)\n\t\tuiprivUserBug(\"You cannot add NULL to a uiForm.\");\n\t[f->view append:uiprivToNSString(label) c:c stretchy:stretchy];\n}\n\nvoid uiFormDelete(uiForm *f, int n)\n{\n\t[f->view delete:n];\n}\n\nint uiFormPadded(uiForm *f)\n{\n\treturn [f->view isPadded];\n}\n\nvoid uiFormSetPadded(uiForm *f, int padded)\n{\n\t[f->view setPadded:padded];\n}\n\nuiForm *uiNewForm(void)\n{\n\tuiForm *f;\n\n\tuiDarwinNewControl(uiForm, f);\n\n\tf->view = [[formView alloc] initWithF:f];\n\n\treturn f;\n}\n"
  },
  {
    "path": "darwin/future.m",
    "content": "// 19 may 2017\n#import \"uipriv_darwin.h\"\n\n// functions and constants FROM THE FUTURE!\n// note: for constants, dlsym() returns the address of the constant itself, as if we had done &constantName\n\n// added in OS X 10.10; we need 10.8\nCFStringRef *uiprivFUTURE_kCTFontOpenTypeFeatureTag = NULL;\nCFStringRef *uiprivFUTURE_kCTFontOpenTypeFeatureValue = NULL;\n\n// added in OS X 10.12; we need 10.8\nCFStringRef *uiprivFUTURE_kCTBackgroundColorAttributeName = NULL;\n\n// note that we treat any error as \"the symbols aren't there\" (and don't care if dlclose() failed)\nvoid uiprivLoadFutures(void)\n{\n\tvoid *handle;\n\n\t// dlsym() walks the dependency chain, so opening the current process should be sufficient\n\thandle = dlopen(NULL, RTLD_LAZY);\n\tif (handle == NULL)\n\t\treturn;\n#define GET(var, fn) *((void **) (&var)) = dlsym(handle, #fn)\n\tGET(uiprivFUTURE_kCTFontOpenTypeFeatureTag, kCTFontOpenTypeFeatureTag);\n\tGET(uiprivFUTURE_kCTFontOpenTypeFeatureValue, kCTFontOpenTypeFeatureValue);\n\tGET(uiprivFUTURE_kCTBackgroundColorAttributeName, kCTBackgroundColorAttributeName);\n\tdlclose(handle);\n}\n\n// wrappers for methods that exist in the future that we can check for with respondsToSelector:\n// keep them in one place for convenience\n\n// apparently only added in 10.9; we need 10.8\nvoid uiprivFUTURE_NSLayoutConstraint_setIdentifier(NSLayoutConstraint *constraint, NSString *identifier)\n{\n\tid cid = (id) constraint;\n\n\tif ([constraint respondsToSelector:@selector(setIdentifier:)])\n\t\t[cid setIdentifier:identifier];\n}\n\n// added in 10.11; we need 10.8\n// return whether this was done because we recreate its effects if not (see winmoveresize.m)\nBOOL uiprivFUTURE_NSWindow_performWindowDragWithEvent(NSWindow *w, NSEvent *initialEvent)\n{\n\tid cw = (id) w;\n\n\tif ([w respondsToSelector:@selector(performWindowDragWithEvent:)]) {\n\t\t[cw performWindowDragWithEvent:initialEvent];\n\t\treturn YES;\n\t}\n\treturn NO;\n}\n"
  },
  {
    "path": "darwin/graphemes.m",
    "content": "// 3 december 2016\n#import \"uipriv_darwin.h\"\n#import \"attrstr.h\"\n\n// CFStringGetRangeOfComposedCharactersAtIndex() is the function for grapheme clusters\n// 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\n\nint uiprivGraphemesTakesUTF16(void)\n{\n\treturn 1;\n}\n\nuiprivGraphemes *uiprivNewGraphemes(void *s, size_t len)\n{\n\tuiprivGraphemes *g;\n\tUniChar *str = (UniChar *) s;\n\tCFStringRef cfstr;\n\tsize_t ppos, gpos;\n\tCFRange range;\n\tsize_t i;\n\n\tg = uiprivNew(uiprivGraphemes);\n\n\tcfstr = CFStringCreateWithCharactersNoCopy(NULL, str, len, kCFAllocatorNull);\n\tif (cfstr == NULL) {\n\t\t// TODO\n\t}\n\n\t// first figure out how many graphemes there are\n\tg->len = 0;\n\tppos = 0;\n\twhile (ppos < len) {\n\t\trange = CFStringGetRangeOfComposedCharactersAtIndex(cfstr, ppos);\n\t\tg->len++;\n\t\tppos = range.location + range.length;\n\t}\n\n\tg->pointsToGraphemes = (size_t *) uiprivAlloc((len + 1) * sizeof (size_t), \"size_t[] (graphemes)\");\n\tg->graphemesToPoints = (size_t *) uiprivAlloc((g->len + 1) * sizeof (size_t), \"size_t[] (graphemes)\");\n\n\t// now calculate everything\n\t// fortunately due to the use of CFRange we can do this in one loop trivially!\n\tppos = 0;\n\tgpos = 0;\n\twhile (ppos < len) {\n\t\trange = CFStringGetRangeOfComposedCharactersAtIndex(cfstr, ppos);\n\t\tfor (i = 0; i < range.length; i++)\n\t\t\tg->pointsToGraphemes[range.location + i] = gpos;\n\t\tg->graphemesToPoints[gpos] = range.location;\n\t\tgpos++;\n\t\tppos = range.location + range.length;\n\t}\n\t// and set the last one\n\tg->pointsToGraphemes[ppos] = gpos;\n\tg->graphemesToPoints[gpos] = ppos;\n\n\tCFRelease(cfstr);\n\treturn g;\n}\n"
  },
  {
    "path": "darwin/grid.m",
    "content": "// 11 june 2016\n#import \"uipriv_darwin.h\"\n\n// TODO the assorted test doesn't work right at all\n\n@interface gridChild : NSView\n@property uiControl *c;\n@property int left;\n@property int top;\n@property int xspan;\n@property int yspan;\n@property int hexpand;\n@property uiAlign halign;\n@property int vexpand;\n@property uiAlign valign;\n\n@property (strong) NSLayoutConstraint *leadingc;\n@property (strong) NSLayoutConstraint *topc;\n@property (strong) NSLayoutConstraint *trailingc;\n@property (strong) NSLayoutConstraint *bottomc;\n@property (strong) NSLayoutConstraint *xcenterc;\n@property (strong) NSLayoutConstraint *ycenterc;\n\n@property NSLayoutPriority oldHorzHuggingPri;\n@property NSLayoutPriority oldVertHuggingPri;\n- (void)setC:(uiControl *)c grid:(uiGrid *)g;\n- (void)onDestroy;\n- (NSView *)view;\n@end\n\n@interface gridView : NSView {\n\tuiGrid *g;\n\tNSMutableArray *children;\n\tint padded;\n\n\tNSMutableArray *edges;\n\tNSMutableArray *inBetweens;\n\n\tNSMutableArray *emptyCellViews;\n}\n- (id)initWithG:(uiGrid *)gg;\n- (void)onDestroy;\n- (void)removeOurConstraints;\n- (void)syncEnableStates:(int)enabled;\n- (CGFloat)paddingAmount;\n- (void)establishOurConstraints;\n- (void)append:(gridChild *)gc;\n- (void)insert:(gridChild *)gc after:(uiControl *)c at:(uiAt)at;\n- (int)isPadded;\n- (void)setPadded:(int)p;\n- (BOOL)hugsTrailing;\n- (BOOL)hugsBottom;\n- (int)nhexpand;\n- (int)nvexpand;\n@end\n\nstruct uiGrid {\n\tuiDarwinControl c;\n\tgridView *view;\n};\n\n@implementation gridChild\n\n- (void)setC:(uiControl *)c grid:(uiGrid *)g\n{\n\tself.c = c;\n\tself.oldHorzHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(self.c), NSLayoutConstraintOrientationHorizontal);\n\tself.oldVertHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(self.c), NSLayoutConstraintOrientationVertical);\n\n\tuiControlSetParent(self.c, uiControl(g));\n\tuiDarwinControlSetSuperview(uiDarwinControl(self.c), self);\n\tuiDarwinControlSyncEnableState(uiDarwinControl(self.c), uiControlEnabledToUser(uiControl(g)));\n\n\tif (self.halign == uiAlignStart || self.halign == uiAlignFill) {\n\t\tself.leadingc = uiprivMkConstraint(self, NSLayoutAttributeLeading,\n\t\t\tNSLayoutRelationEqual,\n\t\t\t[self view], NSLayoutAttributeLeading,\n\t\t\t1, 0,\n\t\t\t@\"uiGrid child horizontal alignment start constraint\");\n\t\t[self addConstraint:self.leadingc];\n\t}\n\tif (self.halign == uiAlignCenter) {\n\t\tself.xcenterc = uiprivMkConstraint(self, NSLayoutAttributeCenterX,\n\t\t\tNSLayoutRelationEqual,\n\t\t\t[self view], NSLayoutAttributeCenterX,\n\t\t\t1, 0,\n\t\t\t@\"uiGrid child horizontal alignment center constraint\");\n\t\t[self addConstraint:self.xcenterc];\n\t}\n\tif (self.halign == uiAlignEnd || self.halign == uiAlignFill) {\n\t\tself.trailingc = uiprivMkConstraint(self, NSLayoutAttributeTrailing,\n\t\t\tNSLayoutRelationEqual,\n\t\t\t[self view], NSLayoutAttributeTrailing,\n\t\t\t1, 0,\n\t\t\t@\"uiGrid child horizontal alignment end constraint\");\n\t\t[self addConstraint:self.trailingc];\n\t}\n\n\tif (self.valign == uiAlignStart || self.valign == uiAlignFill) {\n\t\tself.topc = uiprivMkConstraint(self, NSLayoutAttributeTop,\n\t\t\tNSLayoutRelationEqual,\n\t\t\t[self view], NSLayoutAttributeTop,\n\t\t\t1, 0,\n\t\t\t@\"uiGrid child vertical alignment start constraint\");\n\t\t[self addConstraint:self.topc];\n\t}\n\tif (self.valign == uiAlignCenter) {\n\t\tself.ycenterc = uiprivMkConstraint(self, NSLayoutAttributeCenterY,\n\t\t\tNSLayoutRelationEqual,\n\t\t\t[self view], NSLayoutAttributeCenterY,\n\t\t\t1, 0,\n\t\t\t@\"uiGrid child vertical alignment center constraint\");\n\t\t[self addConstraint:self.ycenterc];\n\t}\n\tif (self.valign == uiAlignEnd || self.valign == uiAlignFill) {\n\t\tself.bottomc = uiprivMkConstraint(self, NSLayoutAttributeBottom,\n\t\t\tNSLayoutRelationEqual,\n\t\t\t[self view], NSLayoutAttributeBottom,\n\t\t\t1, 0,\n\t\t\t@\"uiGrid child vertical alignment end constraint\");\n\t\t[self addConstraint:self.bottomc];\n\t}\n}\n\n- (void)onDestroy\n{\n\tif (self.leadingc != nil) {\n\t\t[self removeConstraint:self.leadingc];\n\t\tself.leadingc = nil;\n\t}\n\tif (self.topc != nil) {\n\t\t[self removeConstraint:self.topc];\n\t\tself.topc = nil;\n\t}\n\tif (self.trailingc != nil) {\n\t\t[self removeConstraint:self.trailingc];\n\t\tself.trailingc = nil;\n\t}\n\tif (self.bottomc != nil) {\n\t\t[self removeConstraint:self.bottomc];\n\t\tself.bottomc = nil;\n\t}\n\tif (self.xcenterc != nil) {\n\t\t[self removeConstraint:self.xcenterc];\n\t\tself.xcenterc = nil;\n\t}\n\tif (self.ycenterc != nil) {\n\t\t[self removeConstraint:self.ycenterc];\n\t\tself.ycenterc = nil;\n\t}\n\n\tuiControlSetParent(self.c, NULL);\n\tuiDarwinControlSetSuperview(uiDarwinControl(self.c), nil);\n\tuiDarwinControlSetHuggingPriority(uiDarwinControl(self.c), self.oldHorzHuggingPri, NSLayoutConstraintOrientationHorizontal);\n\tuiDarwinControlSetHuggingPriority(uiDarwinControl(self.c), self.oldVertHuggingPri, NSLayoutConstraintOrientationVertical);\n}\n\n- (NSView *)view\n{\n\treturn (NSView *) uiControlHandle(self.c);\n}\n\n@end\n\n@implementation gridView\n\n- (id)initWithG:(uiGrid *)gg\n{\n\tself = [super initWithFrame:NSZeroRect];\n\tif (self != nil) {\n\t\tself->g = gg;\n\t\tself->padded = 0;\n\t\tself->children = [NSMutableArray new];\n\n\t\tself->edges = [NSMutableArray new];\n\t\tself->inBetweens = [NSMutableArray new];\n\n\t\tself->emptyCellViews = [NSMutableArray new];\n\t}\n\treturn self;\n}\n\n- (void)onDestroy\n{\n\tgridChild *gc;\n\n\t[self removeOurConstraints];\n\t[self->edges release];\n\t[self->inBetweens release];\n\n\t[self->emptyCellViews release];\n\n\tfor (gc in self->children) {\n\t\t[gc onDestroy];\n\t\tuiControlDestroy(gc.c);\n\t\t[gc removeFromSuperview];\n\t}\n\t[self->children release];\n}\n\n- (void)removeOurConstraints\n{\n\tNSView *v;\n\n\tif ([self->edges count] != 0) {\n\t\t[self removeConstraints:self->edges];\n\t\t[self->edges removeAllObjects];\n\t}\n\tif ([self->inBetweens count] != 0) {\n\t\t[self removeConstraints:self->inBetweens];\n\t\t[self->inBetweens removeAllObjects];\n\t}\n\n\tfor (v in self->emptyCellViews)\n\t\t[v removeFromSuperview];\n\t[self->emptyCellViews removeAllObjects];\n}\n\n- (void)syncEnableStates:(int)enabled\n{\n\tgridChild *gc;\n\n\tfor (gc in self->children)\n\t\tuiDarwinControlSyncEnableState(uiDarwinControl(gc.c), enabled);\n}\n\n- (CGFloat)paddingAmount\n{\n\tif (!self->padded)\n\t\treturn 0.0;\n\treturn uiDarwinPaddingAmount(NULL);\n}\n\n// LONGTERM stop early if all controls are hidden\n- (void)establishOurConstraints\n{\n\tgridChild *gc;\n\tCGFloat padding;\n\tint xmin, ymin;\n\tint xmax, ymax;\n\tint xcount, ycount;\n\tBOOL first;\n\tint **gg;\n\tNSView ***gv;\n\tBOOL **gspan;\n\tint x, y;\n\tint i;\n\tNSLayoutConstraint *c;\n\tint firstx, firsty;\n\tBOOL *hexpand, *vexpand;\n\tBOOL doit;\n\tBOOL onlyEmptyAndSpanning;\n\n\t[self removeOurConstraints];\n\tif ([self->children count] == 0)\n\t\treturn;\n\tpadding = [self paddingAmount];\n\n\t// first, figure out the minimum and maximum row and column numbers\n\t// ignore hidden controls\n\tfirst = YES;\n\tfor (gc in self->children) {\n\t\t// 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\n\t\tif (!uiControlVisible(gc.c))\n\t\t\tcontinue;\n\t\tif (first) {\n\t\t\txmin = gc.left;\n\t\t\tymin = gc.top;\n\t\t\txmax = gc.left + gc.xspan;\n\t\t\tymax = gc.top + gc.yspan;\n\t\t\tfirst = NO;\n\t\t\tcontinue;\n\t\t}\n\t\tif (xmin > gc.left)\n\t\t\txmin = gc.left;\n\t\tif (ymin > gc.top)\n\t\t\tymin = gc.top;\n\t\tif (xmax < (gc.left + gc.xspan))\n\t\t\txmax = gc.left + gc.xspan;\n\t\tif (ymax < (gc.top + gc.yspan))\n\t\t\tymax = gc.top + gc.yspan;\n\t}\n\tif (first != NO)\t\t// the entire grid is hidden; do nothing\n\t\treturn;\n\txcount = xmax - xmin;\n\tycount = ymax - ymin;\n\n\t// now build a topological map of the grid gg[y][x]\n\t// also figure out which cells contain spanned views so they can be ignored later\n\t// treat hidden controls by keeping the indices -1\n\tgg = (int **) uiprivAlloc(ycount * sizeof (int *), \"int[][]\");\n\tgspan = (BOOL **) uiprivAlloc(ycount * sizeof (BOOL *), \"BOOL[][]\");\n\tfor (y = 0; y < ycount; y++) {\n\t\tgg[y] = (int *) uiprivAlloc(xcount * sizeof (int), \"int[]\");\n\t\tgspan[y] = (BOOL *) uiprivAlloc(xcount * sizeof (BOOL), \"BOOL[]\");\n\t\tfor (x = 0; x < xcount; x++)\n\t\t\tgg[y][x] = -1;\t\t// empty\n\t}\n\tfor (i = 0; i < [self->children count]; i++) {\n\t\tgc = (gridChild *) [self->children objectAtIndex:i];\n\t\tif (!uiControlVisible(gc.c))\n\t\t\tcontinue;\n\t\tfor (y = gc.top; y < gc.top + gc.yspan; y++)\n\t\t\tfor (x = gc.left; x < gc.left + gc.xspan; x++) {\n\t\t\t\tgg[y - ymin][x - xmin] = i;\n\t\t\t\tif (x != gc.left || y != gc.top)\n\t\t\t\t\tgspan[y - ymin][x - xmin] = YES;\n\t\t\t}\n\t}\n\n\t// 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\n\tfor (y = 0; y < ycount; y++) {\n\t\tonlyEmptyAndSpanning = YES;\n\t\tfor (x = 0; x < xcount; x++)\n\t\t\tif (gg[y][x] != -1) {\n\t\t\t\tgc = (gridChild *) [self->children objectAtIndex:gg[y][x]];\n\t\t\t\tif (gc.yspan == 1 || gc.top - ymin == y) {\n\t\t\t\t\tonlyEmptyAndSpanning = NO;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\tif (onlyEmptyAndSpanning)\n\t\t\tfor (x = 0; x < xcount; x++) {\n\t\t\t\tgg[y][x] = gg[y - 1][x];\n\t\t\t\tgspan[y][x] = YES;\n\t\t\t}\n\t}\n\tfor (x = 0; x < xcount; x++) {\n\t\tonlyEmptyAndSpanning = YES;\n\t\tfor (y = 0; y < ycount; y++)\n\t\t\tif (gg[y][x] != -1) {\n\t\t\t\tgc = (gridChild *) [self->children objectAtIndex:gg[y][x]];\n\t\t\t\tif (gc.xspan == 1 || gc.left - xmin == x) {\n\t\t\t\t\tonlyEmptyAndSpanning = NO;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\tif (onlyEmptyAndSpanning)\n\t\t\tfor (y = 0; y < ycount; y++) {\n\t\t\t\tgg[y][x] = gg[y][x - 1];\n\t\t\t\tgspan[y][x] = YES;\n\t\t\t}\n\t}\n\n\t// now build a topological map of the grid's views gv[y][x]\n\t// for any empty cell, create a dummy view\n\tgv = (NSView ***) uiprivAlloc(ycount * sizeof (NSView **), \"NSView *[][]\");\n\tfor (y = 0; y < ycount; y++) {\n\t\tgv[y] = (NSView **) uiprivAlloc(xcount * sizeof (NSView *), \"NSView *[]\");\n\t\tfor (x = 0; x < xcount; x++)\n\t\t\tif (gg[y][x] == -1) {\n\t\t\t\tgv[y][x] = [[NSView alloc] initWithFrame:NSZeroRect];\n\t\t\t\t[gv[y][x] setTranslatesAutoresizingMaskIntoConstraints:NO];\n\t\t\t\t[self addSubview:gv[y][x]];\n\t\t\t\t[self->emptyCellViews addObject:gv[y][x]];\n\t\t\t} else {\n\t\t\t\tgc = (gridChild *) [self->children objectAtIndex:gg[y][x]];\n\t\t\t\tgv[y][x] = gc;\n\t\t\t}\n\t}\n\n\t// now figure out which rows and columns really expand\n\thexpand = (BOOL *) uiprivAlloc(xcount * sizeof (BOOL), \"BOOL[]\");\n\tvexpand = (BOOL *) uiprivAlloc(ycount * sizeof (BOOL), \"BOOL[]\");\n\t// first, which don't span\n\tfor (gc in self->children) {\n\t\tif (!uiControlVisible(gc.c))\n\t\t\tcontinue;\n\t\tif (gc.hexpand && gc.xspan == 1)\n\t\t\thexpand[gc.left - xmin] = YES;\n\t\tif (gc.vexpand && gc.yspan == 1)\n\t\t\tvexpand[gc.top - ymin] = YES;\n\t}\n\t// second, which do span\n\t// the way we handle this is simple: if none of the spanned rows/columns expand, make all rows/columns expand\n\tfor (gc in self->children) {\n\t\tif (!uiControlVisible(gc.c))\n\t\t\tcontinue;\n\t\tif (gc.hexpand && gc.xspan != 1) {\n\t\t\tdoit = YES;\n\t\t\tfor (x = gc.left; x < gc.left + gc.xspan; x++)\n\t\t\t\tif (hexpand[x - xmin]) {\n\t\t\t\t\tdoit = NO;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tif (doit)\n\t\t\t\tfor (x = gc.left; x < gc.left + gc.xspan; x++)\n\t\t\t\t\thexpand[x - xmin] = YES;\n\t\t}\n\t\tif (gc.vexpand && gc.yspan != 1) {\n\t\t\tdoit = YES;\n\t\t\tfor (y = gc.top; y < gc.top + gc.yspan; y++)\n\t\t\t\tif (vexpand[y - ymin]) {\n\t\t\t\t\tdoit = NO;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tif (doit)\n\t\t\t\tfor (y = gc.top; y < gc.top + gc.yspan; y++)\n\t\t\t\t\tvexpand[y - ymin] = YES;\n\t\t}\n\t}\n\n\t// now establish all the edge constraints\n\t// leading and trailing edges\n\tfor (y = 0; y < ycount; y++) {\n\t\tc = uiprivMkConstraint(self, NSLayoutAttributeLeading,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tgv[y][0], NSLayoutAttributeLeading,\n\t\t\t1, 0,\n\t\t\t@\"uiGrid leading edge constraint\");\n\t\t[self addConstraint:c];\n\t\t[self->edges addObject:c];\n\t\tc = uiprivMkConstraint(self, NSLayoutAttributeTrailing,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tgv[y][xcount - 1], NSLayoutAttributeTrailing,\n\t\t\t1, 0,\n\t\t\t@\"uiGrid trailing edge constraint\");\n\t\t[self addConstraint:c];\n\t\t[self->edges addObject:c];\n\t}\n\t// top and bottom edges\n\tfor (x = 0; x < xcount; x++) {\n\t\tc = uiprivMkConstraint(self, NSLayoutAttributeTop,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tgv[0][x], NSLayoutAttributeTop,\n\t\t\t1, 0,\n\t\t\t@\"uiGrid top edge constraint\");\n\t\t[self addConstraint:c];\n\t\t[self->edges addObject:c];\n\t\tc = uiprivMkConstraint(self, NSLayoutAttributeBottom,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tgv[ycount - 1][x], NSLayoutAttributeBottom,\n\t\t\t1, 0,\n\t\t\t@\"uiGrid bottom edge constraint\");\n\t\t[self addConstraint:c];\n\t\t[self->edges addObject:c];\n\t}\n\n\t// now align leading and top edges\n\t// do NOT align spanning cells!\n\tfor (x = 0; x < xcount; x++) {\n\t\tfor (y = 0; y < ycount; y++)\n\t\t\tif (!gspan[y][x])\n\t\t\t\tbreak;\n\t\tfirsty = y;\n\t\tfor (y++; y < ycount; y++) {\n\t\t\tif (gspan[y][x])\n\t\t\t\tcontinue;\n\t\t\tc = uiprivMkConstraint(gv[firsty][x], NSLayoutAttributeLeading,\n\t\t\t\tNSLayoutRelationEqual,\n\t\t\t\tgv[y][x], NSLayoutAttributeLeading,\n\t\t\t\t1, 0,\n\t\t\t\t@\"uiGrid column leading constraint\");\n\t\t\t[self addConstraint:c];\n\t\t\t[self->edges addObject:c];\n\t\t}\n\t}\n\tfor (y = 0; y < ycount; y++) {\n\t\tfor (x = 0; x < xcount; x++)\n\t\t\tif (!gspan[y][x])\n\t\t\t\tbreak;\n\t\tfirstx = x;\n\t\tfor (x++; x < xcount; x++) {\n\t\t\tif (gspan[y][x])\n\t\t\t\tcontinue;\n\t\t\tc = uiprivMkConstraint(gv[y][firstx], NSLayoutAttributeTop,\n\t\t\t\tNSLayoutRelationEqual,\n\t\t\t\tgv[y][x], NSLayoutAttributeTop,\n\t\t\t\t1, 0,\n\t\t\t\t@\"uiGrid row top constraint\");\n\t\t\t[self addConstraint:c];\n\t\t\t[self->edges addObject:c];\n\t\t}\n\t}\n\n\t// now string adjacent views together\n\tfor (y = 0; y < ycount; y++)\n\t\tfor (x = 1; x < xcount; x++)\n\t\t\tif (gv[y][x - 1] != gv[y][x]) {\n\t\t\t\tc = uiprivMkConstraint(gv[y][x - 1], NSLayoutAttributeTrailing,\n\t\t\t\t\tNSLayoutRelationEqual,\n\t\t\t\t\tgv[y][x], NSLayoutAttributeLeading,\n\t\t\t\t\t1, -padding,\n\t\t\t\t\t@\"uiGrid internal horizontal constraint\");\n\t\t\t\t[self addConstraint:c];\n\t\t\t\t[self->inBetweens addObject:c];\n\t\t\t}\n\tfor (x = 0; x < xcount; x++)\n\t\tfor (y = 1; y < ycount; y++)\n\t\t\tif (gv[y - 1][x] != gv[y][x]) {\n\t\t\t\tc = uiprivMkConstraint(gv[y - 1][x], NSLayoutAttributeBottom,\n\t\t\t\t\tNSLayoutRelationEqual,\n\t\t\t\t\tgv[y][x], NSLayoutAttributeTop,\n\t\t\t\t\t1, -padding,\n\t\t\t\t\t@\"uiGrid internal vertical constraint\");\n\t\t\t\t[self addConstraint:c];\n\t\t\t\t[self->inBetweens addObject:c];\n\t\t\t}\n\n\t// now set priorities for all widgets that expand or not\n\t// if a cell is in an expanding row, OR If it spans, then it must be willing to stretch\n\t// otherwise, it tries not to\n\t// note we don't use NSLayoutPriorityRequired as that will cause things to squish when they shouldn't\n\tfor (gc in self->children) {\n\t\tNSLayoutPriority priority;\n\n\t\tif (!uiControlVisible(gc.c))\n\t\t\tcontinue;\n\t\tif (hexpand[gc.left - xmin] || gc.xspan != 1)\n\t\t\tpriority = NSLayoutPriorityDefaultLow;\n\t\telse\n\t\t\tpriority = NSLayoutPriorityDefaultHigh;\n\t\tuiDarwinControlSetHuggingPriority(uiDarwinControl(gc.c), priority, NSLayoutConstraintOrientationHorizontal);\n\t\t// same for vertical direction\n\t\tif (vexpand[gc.top - ymin] || gc.yspan != 1)\n\t\t\tpriority = NSLayoutPriorityDefaultLow;\n\t\telse\n\t\t\tpriority = NSLayoutPriorityDefaultHigh;\n\t\tuiDarwinControlSetHuggingPriority(uiDarwinControl(gc.c), priority, NSLayoutConstraintOrientationVertical);\n\t}\n\n\t// TODO make all expanding rows/columns the same height/width\n\n\t// and finally clean up\n\tuiprivFree(hexpand);\n\tuiprivFree(vexpand);\n\tfor (y = 0; y < ycount; y++) {\n\t\tuiprivFree(gg[y]);\n\t\tuiprivFree(gv[y]);\n\t\tuiprivFree(gspan[y]);\n\t}\n\tuiprivFree(gg);\n\tuiprivFree(gv);\n\tuiprivFree(gspan);\n}\n\n- (void)append:(gridChild *)gc\n{\n\tBOOL update;\n\tint oldnh, oldnv;\n\n\t[gc setTranslatesAutoresizingMaskIntoConstraints:NO];\n\t[self addSubview:gc];\n\n\t// no need to set priority here; that's done in establishOurConstraints\n\n\toldnh = [self nhexpand];\n\toldnv = [self nvexpand];\n\t[self->children addObject:gc];\n\n\t[self establishOurConstraints];\n\tupdate = NO;\n\tif (gc.hexpand)\n\t\tif (oldnh == 0)\n\t\t\tupdate = YES;\n\tif (gc.vexpand)\n\t\tif (oldnv == 0)\n\t\t\tupdate = YES;\n\tif (update)\n\t\tuiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->g));\n\n\t[gc release];\t\t// we don't need the initial reference now\n}\n\n- (void)insert:(gridChild *)gc after:(uiControl *)c at:(uiAt)at\n{\n\tgridChild *other;\n\tBOOL found;\n\n\tfound = NO;\n\tfor (other in self->children)\n\t\tif (other.c == c) {\n\t\t\tfound = YES;\n\t\t\tbreak;\n\t\t}\n\tif (!found)\n\t\tuiprivUserBug(\"Existing control %p is not in grid %p; you cannot add other controls next to it\", c, self->g);\n\n\tswitch (at) {\n\tcase uiAtLeading:\n\t\tgc.left = other.left - gc.xspan;\n\t\tgc.top = other.top;\n\t\tbreak;\n\tcase uiAtTop:\n\t\tgc.left = other.left;\n\t\tgc.top = other.top - gc.yspan;\n\t\tbreak;\n\tcase uiAtTrailing:\n\t\tgc.left = other.left + other.xspan;\n\t\tgc.top = other.top;\n\t\tbreak;\n\tcase uiAtBottom:\n\t\tgc.left = other.left;\n\t\tgc.top = other.top + other.yspan;\n\t\tbreak;\n\t// TODO add error checks to ALL enums\n\t}\n\n\t[self append:gc];\n}\n\n- (int)isPadded\n{\n\treturn self->padded;\n}\n\n- (void)setPadded:(int)p\n{\n\tCGFloat padding;\n\tNSLayoutConstraint *c;\n\n#if 0 /* TODO */\ndispatch_after(\ndispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC),\ndispatch_get_main_queue(),\n^{ [[self window] visualizeConstraints:[self constraints]]; }\n);\n#endif\n\tself->padded = p;\n\tpadding = [self paddingAmount];\n\tfor (c in self->inBetweens)\n\t\tswitch ([c firstAttribute]) {\n\t\tcase NSLayoutAttributeLeading:\n\t\tcase NSLayoutAttributeTop:\n\t\t\t[c setConstant:padding];\n\t\t\tbreak;\n\t\tcase NSLayoutAttributeTrailing:\n\t\tcase NSLayoutAttributeBottom:\n\t\t\t[c setConstant:-padding];\n\t\t\tbreak;\n\t\t}\n}\n\n- (BOOL)hugsTrailing\n{\n\t// only hug if we have horizontally expanding\n\treturn [self nhexpand] != 0;\n}\n\n- (BOOL)hugsBottom\n{\n\t// only hug if we have vertically expanding\n\treturn [self nvexpand] != 0;\n}\n\n- (int)nhexpand\n{\n\tgridChild *gc;\n\tint n;\n\n\tn = 0;\n\tfor (gc in self->children) {\n\t\tif (!uiControlVisible(gc.c))\n\t\t\tcontinue;\n\t\tif (gc.hexpand)\n\t\t\tn++;\n\t}\n\treturn n;\n}\n\n- (int)nvexpand\n{\n\tgridChild *gc;\n\tint n;\n\n\tn = 0;\n\tfor (gc in self->children) {\n\t\tif (!uiControlVisible(gc.c))\n\t\t\tcontinue;\n\t\tif (gc.vexpand)\n\t\t\tn++;\n\t}\n\treturn n;\n}\n\n@end\n\nstatic void uiGridDestroy(uiControl *c)\n{\n\tuiGrid *g = uiGrid(c);\n\n\t[g->view onDestroy];\n\t[g->view release];\n\tuiFreeControl(uiControl(g));\n}\n\nuiDarwinControlDefaultHandle(uiGrid, view)\nuiDarwinControlDefaultParent(uiGrid, view)\nuiDarwinControlDefaultSetParent(uiGrid, view)\nuiDarwinControlDefaultToplevel(uiGrid, view)\nuiDarwinControlDefaultVisible(uiGrid, view)\nuiDarwinControlDefaultShow(uiGrid, view)\nuiDarwinControlDefaultHide(uiGrid, view)\nuiDarwinControlDefaultEnabled(uiGrid, view)\nuiDarwinControlDefaultEnable(uiGrid, view)\nuiDarwinControlDefaultDisable(uiGrid, view)\n\nstatic void uiGridSyncEnableState(uiDarwinControl *c, int enabled)\n{\n\tuiGrid *g = uiGrid(c);\n\n\tif (uiDarwinShouldStopSyncEnableState(uiDarwinControl(g), enabled))\n\t\treturn;\n\t[g->view syncEnableStates:enabled];\n}\n\nuiDarwinControlDefaultSetSuperview(uiGrid, view)\n\nstatic BOOL uiGridHugsTrailingEdge(uiDarwinControl *c)\n{\n\tuiGrid *g = uiGrid(c);\n\n\treturn [g->view hugsTrailing];\n}\n\nstatic BOOL uiGridHugsBottom(uiDarwinControl *c)\n{\n\tuiGrid *g = uiGrid(c);\n\n\treturn [g->view hugsBottom];\n}\n\nstatic void uiGridChildEdgeHuggingChanged(uiDarwinControl *c)\n{\n\tuiGrid *g = uiGrid(c);\n\n\t[g->view establishOurConstraints];\n}\n\nuiDarwinControlDefaultHuggingPriority(uiGrid, view)\nuiDarwinControlDefaultSetHuggingPriority(uiGrid, view)\n\nstatic void uiGridChildVisibilityChanged(uiDarwinControl *c)\n{\n\tuiGrid *g = uiGrid(c);\n\n\t[g->view establishOurConstraints];\n}\n\nstatic gridChild *toChild(uiControl *c, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign, uiGrid *g)\n{\n\tgridChild *gc;\n\n\tif (xspan < 0)\n\t\tuiprivUserBug(\"You cannot have a negative xspan in a uiGrid cell.\");\n\tif (yspan < 0)\n\t\tuiprivUserBug(\"You cannot have a negative yspan in a uiGrid cell.\");\n\tgc = [gridChild new];\n\tgc.xspan = xspan;\n\tgc.yspan = yspan;\n\tgc.hexpand = hexpand;\n\tgc.halign = halign;\n\tgc.vexpand = vexpand;\n\tgc.valign = valign;\n\t[gc setC:c grid:g];\n\treturn gc;\n}\n\nvoid uiGridAppend(uiGrid *g, uiControl *c, int left, int top, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign)\n{\n\tgridChild *gc;\n\n\t// LONGTERM on other platforms\n\t// or at leat allow this and implicitly turn it into a spacer\n\tif (c == NULL)\n\t\tuiprivUserBug(\"You cannot add NULL to a uiGrid.\");\n\tgc = toChild(c, xspan, yspan, hexpand, halign, vexpand, valign, g);\n\tgc.left = left;\n\tgc.top = top;\n\t[g->view append:gc];\n}\n\nvoid uiGridInsertAt(uiGrid *g, uiControl *c, uiControl *existing, uiAt at, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign)\n{\n\tgridChild *gc;\n\n\tgc = toChild(c, xspan, yspan, hexpand, halign, vexpand, valign, g);\n\t[g->view insert:gc after:existing at:at];\n}\n\nint uiGridPadded(uiGrid *g)\n{\n\treturn [g->view isPadded];\n}\n\nvoid uiGridSetPadded(uiGrid *g, int padded)\n{\n\t[g->view setPadded:padded];\n}\n\nuiGrid *uiNewGrid(void)\n{\n\tuiGrid *g;\n\n\tuiDarwinNewControl(uiGrid, g);\n\n\tg->view = [[gridView alloc] initWithG:g];\n\n\treturn g;\n}\n"
  },
  {
    "path": "darwin/group.m",
    "content": "// 14 august 2015\n#import \"uipriv_darwin.h\"\n\nstruct uiGroup {\n\tuiDarwinControl c;\n\tNSBox *box;\n\tuiControl *child;\n\tNSLayoutPriority oldHorzHuggingPri;\n\tNSLayoutPriority oldVertHuggingPri;\n\tint margined;\n\tuiprivSingleChildConstraints constraints;\n\tNSLayoutPriority horzHuggingPri;\n\tNSLayoutPriority vertHuggingPri;\n};\n\nstatic void removeConstraints(uiGroup *g)\n{\n\t// set to contentView instead of to the box itself, otherwise we get clipping underneath the label\n\tuiprivSingleChildConstraintsRemove(&(g->constraints), [g->box contentView]);\n}\n\nstatic void uiGroupDestroy(uiControl *c)\n{\n\tuiGroup *g = uiGroup(c);\n\n\tremoveConstraints(g);\n\tif (g->child != NULL) {\n\t\tuiControlSetParent(g->child, NULL);\n\t\tuiDarwinControlSetSuperview(uiDarwinControl(g->child), nil);\n\t\tuiControlDestroy(g->child);\n\t}\n\t[g->box release];\n\tuiFreeControl(uiControl(g));\n}\n\nuiDarwinControlDefaultHandle(uiGroup, box)\nuiDarwinControlDefaultParent(uiGroup, box)\nuiDarwinControlDefaultSetParent(uiGroup, box)\nuiDarwinControlDefaultToplevel(uiGroup, box)\nuiDarwinControlDefaultVisible(uiGroup, box)\nuiDarwinControlDefaultShow(uiGroup, box)\nuiDarwinControlDefaultHide(uiGroup, box)\nuiDarwinControlDefaultEnabled(uiGroup, box)\nuiDarwinControlDefaultEnable(uiGroup, box)\nuiDarwinControlDefaultDisable(uiGroup, box)\n\nstatic void uiGroupSyncEnableState(uiDarwinControl *c, int enabled)\n{\n\tuiGroup *g = uiGroup(c);\n\n\tif (uiDarwinShouldStopSyncEnableState(uiDarwinControl(g), enabled))\n\t\treturn;\n\tif (g->child != NULL)\n\t\tuiDarwinControlSyncEnableState(uiDarwinControl(g->child), enabled);\n}\n\nuiDarwinControlDefaultSetSuperview(uiGroup, box)\n\nstatic void groupRelayout(uiGroup *g)\n{\n\tNSView *childView;\n\n\tremoveConstraints(g);\n\tif (g->child == NULL)\n\t\treturn;\n\tchildView = (NSView *) uiControlHandle(g->child);\n\tuiprivSingleChildConstraintsEstablish(&(g->constraints),\n\t\t[g->box contentView], childView,\n\t\tuiDarwinControlHugsTrailingEdge(uiDarwinControl(g->child)),\n\t\tuiDarwinControlHugsBottom(uiDarwinControl(g->child)),\n\t\tg->margined,\n\t\t@\"uiGroup\");\n\t// needed for some very rare drawing errors...\n\tuiprivJiggleViewLayout(g->box);\n}\n\n// TODO rename these since I'm starting to get confused by what they mean by hugging\nBOOL uiGroupHugsTrailingEdge(uiDarwinControl *c)\n{\n\tuiGroup *g = uiGroup(c);\n\n\t// TODO make a function?\n\treturn g->horzHuggingPri < NSLayoutPriorityWindowSizeStayPut;\n}\n\nBOOL uiGroupHugsBottom(uiDarwinControl *c)\n{\n\tuiGroup *g = uiGroup(c);\n\n\treturn g->vertHuggingPri < NSLayoutPriorityWindowSizeStayPut;\n}\n\nstatic void uiGroupChildEdgeHuggingChanged(uiDarwinControl *c)\n{\n\tuiGroup *g = uiGroup(c);\n\n\tgroupRelayout(g);\n}\n\nstatic NSLayoutPriority uiGroupHuggingPriority(uiDarwinControl *c, NSLayoutConstraintOrientation orientation)\n{\n\tuiGroup *g = uiGroup(c);\n\n\tif (orientation == NSLayoutConstraintOrientationHorizontal)\n\t\treturn g->horzHuggingPri;\n\treturn g->vertHuggingPri;\n}\n\nstatic void uiGroupSetHuggingPriority(uiDarwinControl *c, NSLayoutPriority priority, NSLayoutConstraintOrientation orientation)\n{\n\tuiGroup *g = uiGroup(c);\n\n\tif (orientation == NSLayoutConstraintOrientationHorizontal)\n\t\tg->horzHuggingPri = priority;\n\telse\n\t\tg->vertHuggingPri = priority;\n\tuiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(g));\n}\n\nstatic void uiGroupChildVisibilityChanged(uiDarwinControl *c)\n{\n\tuiGroup *g = uiGroup(c);\n\n\tgroupRelayout(g);\n}\n\nchar *uiGroupTitle(uiGroup *g)\n{\n\treturn uiDarwinNSStringToText([g->box title]);\n}\n\nvoid uiGroupSetTitle(uiGroup *g, const char *title)\n{\n\t[g->box setTitle:uiprivToNSString(title)];\n}\n\nvoid uiGroupSetChild(uiGroup *g, uiControl *child)\n{\n\tNSView *childView;\n\n\tif (g->child != NULL) {\n\t\tremoveConstraints(g);\n\t\tuiDarwinControlSetHuggingPriority(uiDarwinControl(g->child), g->oldHorzHuggingPri, NSLayoutConstraintOrientationHorizontal);\n\t\tuiDarwinControlSetHuggingPriority(uiDarwinControl(g->child), g->oldVertHuggingPri, NSLayoutConstraintOrientationVertical);\n\t\tuiControlSetParent(g->child, NULL);\n\t\tuiDarwinControlSetSuperview(uiDarwinControl(g->child), nil);\n\t}\n\tg->child = child;\n\tif (g->child != NULL) {\n\t\tchildView = (NSView *) uiControlHandle(g->child);\n\t\tuiControlSetParent(g->child, uiControl(g));\n\t\tuiDarwinControlSetSuperview(uiDarwinControl(g->child), [g->box contentView]);\n\t\tuiDarwinControlSyncEnableState(uiDarwinControl(g->child), uiControlEnabledToUser(uiControl(g)));\n\t\t// don't hug, just in case we're a stretchy group\n\t\tg->oldHorzHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(g->child), NSLayoutConstraintOrientationHorizontal);\n\t\tg->oldVertHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(g->child), NSLayoutConstraintOrientationVertical);\n\t\tuiDarwinControlSetHuggingPriority(uiDarwinControl(g->child), NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationHorizontal);\n\t\tuiDarwinControlSetHuggingPriority(uiDarwinControl(g->child), NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationVertical);\n\t}\n\tgroupRelayout(g);\n}\n\nint uiGroupMargined(uiGroup *g)\n{\n\treturn g->margined;\n}\n\nvoid uiGroupSetMargined(uiGroup *g, int margined)\n{\n\tg->margined = margined;\n\tuiprivSingleChildConstraintsSetMargined(&(g->constraints), g->margined);\n}\n\nuiGroup *uiNewGroup(const char *title)\n{\n\tuiGroup *g;\n\n\tuiDarwinNewControl(uiGroup, g);\n\n\tg->box = [[NSBox alloc] initWithFrame:NSZeroRect];\n\t[g->box setTitle:uiprivToNSString(title)];\n\t[g->box setBoxType:NSBoxPrimary];\n\t[g->box setBorderType:NSLineBorder];\n\t[g->box setTransparent:NO];\n\t[g->box setTitlePosition:NSAtTop];\n\t// we can't use uiDarwinSetControlFont() because the selector is different\n\t[g->box setTitleFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]];\n\n\t// default to low hugging to not hug edges\n\tg->horzHuggingPri = NSLayoutPriorityDefaultLow;\n\tg->vertHuggingPri = NSLayoutPriorityDefaultLow;\n\n\treturn g;\n}\n"
  },
  {
    "path": "darwin/image.m",
    "content": "// 25 june 2016\n#import \"uipriv_darwin.h\"\n\nstruct uiImage {\n\tNSImage *i;\n\tNSSize size;\n};\n\nuiImage *uiNewImage(double width, double height)\n{\n\tuiImage *i;\n\n\ti = uiprivNew(uiImage);\n\ti->size = NSMakeSize(width, height);\n\ti->i = [[NSImage alloc] initWithSize:i->size];\n\treturn i;\n}\n\nvoid uiFreeImage(uiImage *i)\n{\n\t[i->i release];\n\tuiprivFree(i);\n}\n\nvoid uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int byteStride)\n{\n\tNSBitmapImageRep *repCalibrated, *repsRGB;\n\tint x, y;\n\tuint8_t *pix, *data;\n\tNSInteger realStride;\n\n\trepCalibrated = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL\n\t\tpixelsWide:pixelWidth\n\t\tpixelsHigh:pixelHeight\n\t\tbitsPerSample:8\n\t\tsamplesPerPixel:4\n\t\thasAlpha:YES\n\t\tisPlanar:NO\n\t\tcolorSpaceName:NSCalibratedRGBColorSpace\n\t\tbitmapFormat:0\n\t\tbytesPerRow:0\n\t\tbitsPerPixel:32];\n\n\t// Apple doesn't explicitly document this, but we apparently need to use native system endian for the data :|\n\t// TODO split this into a utility routine?\n\t// TODO find proper documentation\n\t// 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\n\tpix = (uint8_t *) pixels;\n\tdata = (uint8_t *) [repCalibrated bitmapData];\n\trealStride = [repCalibrated bytesPerRow];\n\tfor (y = 0; y < pixelHeight; y++) {\n\t\tfor (x = 0; x < pixelWidth * 4; x += 4) {\n\t\t\tunion {\n\t\t\t\tuint32_t v32;\n\t\t\t\tuint8_t v8[4];\n\t\t\t} v;\n\n\t\t\tv.v32 = ((uint32_t) (pix[x + 3])) << 24;\n\t\t\tv.v32 |= ((uint32_t) (pix[x + 2])) << 16;\n\t\t\tv.v32 |= ((uint32_t) (pix[x + 1])) << 8;\n\t\t\tv.v32 |= ((uint32_t) (pix[x]));\n\t\t\tdata[x] = v.v8[0];\n\t\t\tdata[x + 1] = v.v8[1];\n\t\t\tdata[x + 2] = v.v8[2];\n\t\t\tdata[x + 3] = v.v8[3];\n\t\t}\n\t\tpix += byteStride;\n\t\tdata += realStride;\n\t}\n\n\t// we can't call the constructor with this, but we can retag (NOT convert)\n\trepsRGB = [repCalibrated bitmapImageRepByRetaggingWithColorSpace:[NSColorSpace sRGBColorSpace]];\n\n\t[i->i addRepresentation:repsRGB];\n\t[repsRGB setSize:i->size];\n\t// don't release repsRGB; it may be equivalent to repCalibrated\n\t// do release repCalibrated though; NSImage has a ref to either it or to repsRGB\n\t[repCalibrated release];\n}\n\nNSImage *uiprivImageNSImage(uiImage *i)\n{\n\treturn i->i;\n}\n"
  },
  {
    "path": "darwin/label.m",
    "content": "// 14 august 2015\n#import \"uipriv_darwin.h\"\n\nstruct uiLabel {\n\tuiDarwinControl c;\n\tNSTextField *textfield;\n};\n\nuiDarwinControlAllDefaults(uiLabel, textfield)\n\nchar *uiLabelText(uiLabel *l)\n{\n\treturn uiDarwinNSStringToText([l->textfield stringValue]);\n}\n\nvoid uiLabelSetText(uiLabel *l, const char *text)\n{\n\t[l->textfield setStringValue:uiprivToNSString(text)];\n}\n\nNSTextField *uiprivNewLabel(NSString *str)\n{\n\tNSTextField *tf;\n\n\ttf = [[NSTextField alloc] initWithFrame:NSZeroRect];\n\t[tf setStringValue:str];\n\t[tf setEditable:NO];\n\t[tf setSelectable:NO];\n\t[tf setDrawsBackground:NO];\n\tuiprivFinishNewTextField(tf, NO);\n\treturn tf;\n}\n\nuiLabel *uiNewLabel(const char *text)\n{\n\tuiLabel *l;\n\n\tuiDarwinNewControl(uiLabel, l);\n\n\tl->textfield = uiprivNewLabel(uiprivToNSString(text));\n\n\treturn l;\n}\n"
  },
  {
    "path": "darwin/main.m",
    "content": "// 6 april 2015\n#import \"uipriv_darwin.h\"\n#import \"attrstr.h\"\n\nstatic BOOL canQuit = NO;\nstatic NSAutoreleasePool *globalPool;\nstatic uiprivApplicationClass *app;\nstatic uiprivAppDelegate *delegate;\n\nstatic BOOL (^isRunning)(void);\nstatic BOOL stepsIsRunning;\n\n@implementation uiprivApplicationClass\n\n- (void)sendEvent:(NSEvent *)e\n{\n\tif (uiprivSendAreaEvents(e) != 0)\n\t\treturn;\n\t[super sendEvent:e];\n}\n\n// NSColorPanel always sends changeColor: to the first responder regardless of whether there's a target set on it\n// we can override it here (see colorbutton.m)\n// thanks to mikeash in irc.freenode.net/#macdev for informing me this is how the first responder chain is initiated\n// it turns out NSFontManager also sends changeFont: through this; let's inhibit that here too (see fontbutton.m)\n- (BOOL)sendAction:(SEL)sel to:(id)to from:(id)from\n{\n\tif (uiprivColorButtonInhibitSendAction(sel, from, to))\n\t\treturn NO;\n\tif (uiprivFontButtonInhibitSendAction(sel, from, to))\n\t\treturn NO;\n\treturn [super sendAction:sel to:to from:from];\n}\n\n// likewise, NSFontManager also sends NSFontPanelValidation messages to the first responder, however it does NOT use sendAction:from:to:!\n// instead, it uses this one (thanks swillits in irc.freenode.net/#macdev)\n// we also need to override it (see fontbutton.m)\n- (id)targetForAction:(SEL)sel to:(id)to from:(id)from\n{\n\tid override;\n\n\tif (uiprivFontButtonOverrideTargetForAction(sel, from, to, &override))\n\t\treturn override;\n\treturn [super targetForAction:sel to:to from:from];\n}\n\n// hey look! we're overriding terminate:!\n// we're going to make sure we can go back to main() whether Cocoa likes it or not!\n// and just how are we going to do that, hm?\n// (note: this is called after applicationShouldTerminate:)\n- (void)terminate:(id)sender\n{\n\t// yes that's right folks: DO ABSOLUTELY NOTHING.\n\t// the magic is [NSApp run] will just... stop.\n\n\t// well let's not do nothing; let's actually quit our graceful way\n\tNSEvent *e;\n\n\tif (!canQuit)\n\t\tuiprivImplBug(\"call to [NSApp terminate:] when not ready to terminate; definitely contact andlabs\");\n\n\t[uiprivNSApp() stop:uiprivNSApp()];\n\t// stop: won't register until another event has passed; let's synthesize one\n\te = [NSEvent otherEventWithType:NSApplicationDefined\n\t\tlocation:NSZeroPoint\n\t\tmodifierFlags:0\n\t\ttimestamp:[[NSProcessInfo processInfo] systemUptime]\n\t\twindowNumber:0\n\t\tcontext:[NSGraphicsContext currentContext]\n\t\tsubtype:0\n\t\tdata1:0\n\t\tdata2:0];\n\t[uiprivNSApp() postEvent:e atStart:NO];\t\t// 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)\n\n\t// and in case uiMainSteps() was called\n\tstepsIsRunning = NO;\n}\n\n@end\n\n@implementation uiprivAppDelegate\n\n- (void)dealloc\n{\n\t// Apple docs: \"Don't Use Accessor Methods in Initializer Methods and dealloc\"\n\t[_menuManager release];\n\t[super dealloc];\n}\n\n- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)app\n{\n\t// for debugging\n\tNSLog(@\"in applicationShouldTerminate:\");\n\tif (uiprivShouldQuit()) {\n\t\tcanQuit = YES;\n\t\t// this will call terminate:, which is the same as uiQuit()\n\t\treturn NSTerminateNow;\n\t}\n\treturn NSTerminateCancel;\n}\n\n- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app\n{\n\treturn NO;\n}\n\n@end\n\nuiInitOptions uiprivOptions;\n\nconst char *uiInit(uiInitOptions *o)\n{\n\t@autoreleasepool {\n\t\tuiprivOptions = *o;\n\t\tapp = [[uiprivApplicationClass sharedApplication] retain];\n\t\t// 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!\n\t\t// see https://github.com/andlabs/ui/issues/6\n\t\t[uiprivNSApp() setActivationPolicy:NSApplicationActivationPolicyRegular];\n\t\tdelegate = [uiprivAppDelegate new];\n\t\t[uiprivNSApp() setDelegate:delegate];\n\n\t\tuiprivInitAlloc();\n\t\tuiprivLoadFutures();\n\t\tuiprivLoadUndocumented();\n\n\t\t// always do this so we always have an application menu\n\t\tuiprivAppDelegate().menuManager = [[uiprivMenuManager new] autorelease];\n\t\t[uiprivNSApp() setMainMenu:[uiprivAppDelegate().menuManager makeMenubar]];\n\n\t\tuiprivSetupFontPanel();\n\n\t\tuiprivInitUnderlineColors();\n\t}\n\n\tglobalPool = [[NSAutoreleasePool alloc] init];\n\n\treturn NULL;\n}\n\nvoid uiUninit(void)\n{\n\tif (!globalPool)\n\t\tuiprivUserBug(\"You must call uiInit() first!\");\n\t[globalPool release];\n\n\t@autoreleasepool {\n\t\tuiprivUninitUnderlineColors();\n\t\t[delegate release];\n\t\t[uiprivNSApp() setDelegate:nil];\n\t\t[app release];\n\t\tuiprivUninitAlloc();\n\t}\n}\n\nvoid uiFreeInitError(const char *err)\n{\n}\n\nvoid uiMain(void)\n{\n\tisRunning = ^{\n\t\treturn [uiprivNSApp() isRunning];\n\t};\n\t[uiprivNSApp() run];\n}\n\nvoid uiMainSteps(void)\n{\n\t// SDL does this and it seems to be necessary for the menubar to work (see #182)\n\t[uiprivNSApp() finishLaunching];\n\tisRunning = ^{\n\t\treturn stepsIsRunning;\n\t};\n\tstepsIsRunning = YES;\n}\n\nint uiMainStep(int wait)\n{\n\tuiprivNextEventArgs nea;\n\n\tnea.mask = NSAnyEventMask;\n\n\t// ProPuke did this in his original PR requesting this\n\t// I'm not sure if this will work, but I assume it will...\n\tnea.duration = [NSDate distantPast];\n\tif (wait)\t\t// but this is normal so it will work\n\t\tnea.duration = [NSDate distantFuture];\n\n\tnea.mode = NSDefaultRunLoopMode;\n\tnea.dequeue = YES;\n\n\treturn uiprivMainStep(&nea, ^(NSEvent *e) {\n\t\treturn NO;\n\t});\n}\n\n// see also:\n// - http://www.cocoawithlove.com/2009/01/demystifying-nsapplication-by.html\n// - https://github.com/gnustep/gui/blob/master/Source/NSApplication.m\nint uiprivMainStep(uiprivNextEventArgs *nea, BOOL (^interceptEvent)(NSEvent *e))\n{\n\tNSDate *expire;\n\tNSEvent *e;\n\tNSEventType type;\n\n\t@autoreleasepool {\n\t\tif (!isRunning())\n\t\t\treturn 0;\n\n\t\te = [uiprivNSApp() nextEventMatchingMask:nea->mask\n\t\t\tuntilDate:nea->duration\n\t\t\tinMode:nea->mode\n\t\t\tdequeue:nea->dequeue];\n\t\tif (e == nil)\n\t\t\treturn 1;\n\n\t\ttype = [e type];\n\t\tif (!interceptEvent(e))\n\t\t\t[uiprivNSApp() sendEvent:e];\n\t\t[uiprivNSApp() updateWindows];\n\n\t\t// GNUstep does this\n\t\t// it also updates the Services menu but there doesn't seem to be a public API for that so\n\t\tif (type != NSPeriodic && type != NSMouseMoved)\n\t\t\t[[uiprivNSApp() mainMenu] update];\n\n\t\treturn 1;\n\t}\n}\n\nvoid uiQuit(void)\n{\n\tcanQuit = YES;\n\t[uiprivNSApp() terminate:uiprivNSApp()];\n}\n\n// thanks to mikeash in irc.freenode.net/#macdev for suggesting the use of Grand Central Dispatch for this\n// LONGTERM will dispatch_get_main_queue() break after _CFRunLoopSetCurrent()?\nvoid uiQueueMain(void (*f)(void *data), void *data)\n{\n\t// dispatch_get_main_queue() is a serial queue so it will not execute multiple uiQueueMain() functions concurrently\n\t// the signature of f matches dispatch_function_t\n\tdispatch_async_f(dispatch_get_main_queue(), data, f);\n}\n\n@interface uiprivTimerDelegate : NSObject {\n        int (*f)(void *data);\n        void *data;\n}\n- (id)initWithCallback:(int (*)(void *))callback data:(void *)callbackData;\n- (void)doTimer:(NSTimer *)timer;\n@end\n\n@implementation uiprivTimerDelegate\n\n- (id)initWithCallback:(int (*)(void *))callback data:(void *)callbackData\n{\n        self = [super init];\n        if (self) {\n                self->f = callback;\n                self->data = callbackData;\n        }\n        return self;\n}\n\n- (void)doTimer:(NSTimer *)timer\n{\n        if (!(*(self->f))(self->data))\n                [timer invalidate];\n}\n\n@end\n\nvoid uiTimer(int milliseconds, int (*f)(void *data), void *data)\n{\n        uiprivTimerDelegate *delegate;\n\n        delegate = [[uiprivTimerDelegate alloc] initWithCallback:f data:data];\n        [NSTimer scheduledTimerWithTimeInterval:(milliseconds / 1000.0)\n                target:delegate\n                selector:@selector(doTimer:)\n                userInfo:nil\n                repeats:YES];\n        [delegate release];\n}\n\n// TODO figure out the best way to clean the above up in uiUninit(), if it's even necessary\n// TODO that means figure out if timers can still fire without the main loop\n"
  },
  {
    "path": "darwin/map.m",
    "content": "// 17 august 2015\n#import \"uipriv_darwin.h\"\n\n// unfortunately NSMutableDictionary copies its keys, meaning we can't use it for pointers\n// hence, this file\n// 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\nstruct uiprivMap {\n\tNSMapTable *m;\n};\n\nuiprivMap *uiprivNewMap(void)\n{\n\tuiprivMap *m;\n\n\tm = uiprivNew(uiprivMap);\n\tm->m = [[NSMapTable alloc] initWithKeyOptions:(NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality)\n\t\tvalueOptions:(NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality)\n\t\tcapacity:0];\n\treturn m;\n}\n\nvoid uiprivMapDestroy(uiprivMap *m)\n{\n\tif ([m->m count] != 0)\n\t\tuiprivImplBug(\"attempt to destroy map with items inside\");\n\t[m->m release];\n\tuiprivFree(m);\n}\n\nvoid *uiprivMapGet(uiprivMap *m, void *key)\n{\n\treturn NSMapGet(m->m, key);\n}\n\nvoid uiprivMapSet(uiprivMap *m, void *key, void *value)\n{\n\tNSMapInsert(m->m, key, value);\n}\n\nvoid uiprivMapDelete(uiprivMap *m, void *key)\n{\n\tNSMapRemove(m->m, key);\n}\n\nvoid uiprivMapWalk(uiprivMap *m, void (*f)(void *key, void *value))\n{\n\tNSMapEnumerator e;\n\tvoid *k, *v;\n\n\te = NSEnumerateMapTable(m->m);\n\tk = NULL;\n\tv = NULL;\n\twhile (NSNextMapEnumeratorPair(&e, &k, &v))\n\t\tf(k, v);\n\tNSEndMapTableEnumeration(&e);\n}\n\nvoid uiprivMapReset(uiprivMap *m)\n{\n\tNSResetMapTable(m->m);\n}\n"
  },
  {
    "path": "darwin/menu.m",
    "content": "// 28 april 2015\n#import \"uipriv_darwin.h\"\n\nstatic NSMutableArray *menus = nil;\nstatic BOOL menusFinalized = NO;\n\nstruct uiMenu {\n\tNSMenu *menu;\n\tNSMenuItem *item;\n\tNSMutableArray *items;\n};\n\nstruct uiMenuItem {\n\tNSMenuItem *item;\n\tint type;\n\tBOOL disabled;\n\tvoid (*onClicked)(uiMenuItem *, uiWindow *, void *);\n\tvoid *onClickedData;\n};\n\nenum {\n\ttypeRegular,\n\ttypeCheckbox,\n\ttypeQuit,\n\ttypePreferences,\n\ttypeAbout,\n\ttypeSeparator,\n};\n\nstatic void mapItemReleaser(void *key, void *value)\n{\n\tuiMenuItem *item;\n \n\titem = (uiMenuItem *) value;\n\t[item->item release];\n}\n\n@implementation uiprivMenuManager\n\n- (id)init\n{\n\tself = [super init];\n\tif (self) {\n\t\tself->items = uiprivNewMap();\n\t\tself->hasQuit = NO;\n\t\tself->hasPreferences = NO;\n\t\tself->hasAbout = NO;\n\t}\n\treturn self;\n}\n\n- (void)dealloc\n{\n\tuiprivMapWalk(self->items, mapItemReleaser);\n\tuiprivMapReset(self->items);\n\tuiprivMapDestroy(self->items);\n\tuiprivUninitMenus();\n\t[super dealloc];\n}\n\n- (IBAction)onClicked:(id)sender\n{\n\tuiMenuItem *item;\n\n\titem = (uiMenuItem *) uiprivMapGet(self->items, sender);\n\tif (item->type == typeCheckbox)\n\t\tuiMenuItemSetChecked(item, !uiMenuItemChecked(item));\n\t// use the key window as the source of the menu event; it's the active window\n\t(*(item->onClicked))(item, uiprivWindowFromNSWindow([uiprivNSApp() keyWindow]), item->onClickedData);\n}\n\n- (IBAction)onQuitClicked:(id)sender\n{\n\tif (uiprivShouldQuit())\n\t\tuiQuit();\n}\n\n- (void)register:(NSMenuItem *)item to:(uiMenuItem *)smi\n{\n\tswitch (smi->type) {\n\tcase typeQuit:\n\t\tif (self->hasQuit)\n\t\t\tuiprivUserBug(\"You can't have multiple Quit menu items in one program.\");\n\t\tself->hasQuit = YES;\n\t\tbreak;\n\tcase typePreferences:\n\t\tif (self->hasPreferences)\n\t\t\tuiprivUserBug(\"You can't have multiple Preferences menu items in one program.\");\n\t\tself->hasPreferences = YES;\n\t\tbreak;\n\tcase typeAbout:\n\t\tif (self->hasAbout)\n\t\t\tuiprivUserBug(\"You can't have multiple About menu items in one program.\");\n\t\tself->hasAbout = YES;\n\t\tbreak;\n\t}\n\tuiprivMapSet(self->items, item, smi);\n}\n\n// on OS X there are two ways to handle menu items being enabled or disabled: automatically and manually\n// unfortunately, the application menu requires automatic menu handling for the Hide, Hide Others, and Show All items to work correctly\n// therefore, we have to handle enabling of the other options ourselves\n- (BOOL)validateMenuItem:(NSMenuItem *)item\n{\n\tuiMenuItem *smi;\n\n\t// disable the special items if they aren't present\n\tif (item == self.quitItem && !self->hasQuit)\n\t\treturn NO;\n\tif (item == self.preferencesItem && !self->hasPreferences)\n\t\treturn NO;\n\tif (item == self.aboutItem && !self->hasAbout)\n\t\treturn NO;\n\t// then poll the item's enabled/disabled state\n\tsmi = (uiMenuItem *) uiprivMapGet(self->items, item);\n\treturn !smi->disabled;\n}\n\n// Cocoa constructs the default application menu by hand for each program; that's what MainMenu.[nx]ib does\n- (void)buildApplicationMenu:(NSMenu *)menubar\n{\n\tNSString *appName;\n\tNSMenuItem *appMenuItem;\n\tNSMenu *appMenu;\n\tNSMenuItem *item;\n\tNSString *title;\n\tNSMenu *servicesMenu;\n\n\t// note: no need to call setAppleMenu: on this anymore; see https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/#X10_6Notes\n\tappName = [[NSProcessInfo processInfo] processName];\n\tappMenuItem = [[[NSMenuItem alloc] initWithTitle:appName action:NULL keyEquivalent:@\"\"] autorelease];\n\tappMenu = [[[NSMenu alloc] initWithTitle:appName] autorelease];\n\t[appMenuItem setSubmenu:appMenu];\n\t[menubar addItem:appMenuItem];\n\n\t// first is About\n\ttitle = [@\"About \" stringByAppendingString:appName];\n\titem = [[[NSMenuItem alloc] initWithTitle:title action:@selector(onClicked:) keyEquivalent:@\"\"] autorelease];\n\t[item setTarget:self];\n\t[appMenu addItem:item];\n\tself.aboutItem = item;\n\n\t[appMenu addItem:[NSMenuItem separatorItem]];\n\n\t// next is Preferences\n\titem = [[[NSMenuItem alloc] initWithTitle:@\"Preferences…\" action:@selector(onClicked:) keyEquivalent:@\",\"] autorelease];\n\t[item setTarget:self];\n\t[appMenu addItem:item];\n\tself.preferencesItem = item;\n\n\t[appMenu addItem:[NSMenuItem separatorItem]];\n\n\t// next is Services\n\titem = [[[NSMenuItem alloc] initWithTitle:@\"Services\" action:NULL keyEquivalent:@\"\"] autorelease];\n\tservicesMenu = [[[NSMenu alloc] initWithTitle:@\"Services\"] autorelease];\n\t[item setSubmenu:servicesMenu];\n\t[uiprivNSApp() setServicesMenu:servicesMenu];\n\t[appMenu addItem:item];\n\n\t[appMenu addItem:[NSMenuItem separatorItem]];\n\n\t// next are the three hiding options\n\ttitle = [@\"Hide \" stringByAppendingString:appName];\n\titem = [[[NSMenuItem alloc] initWithTitle:title action:@selector(hide:) keyEquivalent:@\"h\"] autorelease];\n\t// the .xib file says they go to -1 (\"First Responder\", which sounds wrong...)\n\t// to do that, we simply leave the target as nil\n\t[appMenu addItem:item];\n\titem = [[[NSMenuItem alloc] initWithTitle:@\"Hide Others\" action:@selector(hideOtherApplications:) keyEquivalent:@\"h\"] autorelease];\n\t[item setKeyEquivalentModifierMask:(NSAlternateKeyMask | NSCommandKeyMask)];\n\t[appMenu addItem:item];\n\titem = [[[NSMenuItem alloc] initWithTitle:@\"Show All\" action:@selector(unhideAllApplications:) keyEquivalent:@\"\"] autorelease];\n\t[appMenu addItem:item];\n\n\t[appMenu addItem:[NSMenuItem separatorItem]];\n\n\t// and finally Quit\n\t// DON'T use @selector(terminate:) as the action; we handle termination ourselves\n\ttitle = [@\"Quit \" stringByAppendingString:appName];\n\titem = [[[NSMenuItem alloc] initWithTitle:title action:@selector(onQuitClicked:) keyEquivalent:@\"q\"] autorelease];\n\t[item setTarget:self];\n\t[appMenu addItem:item];\n\tself.quitItem = item;\n}\n\n- (NSMenu *)makeMenubar\n{\n\tNSMenu *menubar;\n\n\tmenubar = [[[NSMenu alloc] initWithTitle:@\"\"] autorelease];\n\t[self buildApplicationMenu:menubar];\n\treturn menubar;\n}\n\n@end\n\nstatic void defaultOnClicked(uiMenuItem *item, uiWindow *w, void *data)\n{\n\t// do nothing\n}\n\nvoid uiMenuItemEnable(uiMenuItem *item)\n{\n\titem->disabled = NO;\n\t// we don't need to explicitly update the menus here; they'll be updated the next time they're opened (thanks mikeash in irc.freenode.net/#macdev)\n}\n\nvoid uiMenuItemDisable(uiMenuItem *item)\n{\n\titem->disabled = YES;\n}\n\nvoid uiMenuItemOnClicked(uiMenuItem *item, void (*f)(uiMenuItem *, uiWindow *, void *), void *data)\n{\n\tif (item->type == typeQuit)\n\t\tuiprivUserBug(\"You can't call uiMenuItemOnClicked() on a Quit item; use uiOnShouldQuit() instead.\");\n\titem->onClicked = f;\n\titem->onClickedData = data;\n}\n\nint uiMenuItemChecked(uiMenuItem *item)\n{\n\treturn [item->item state] != NSOffState;\n}\n\nvoid uiMenuItemSetChecked(uiMenuItem *item, int checked)\n{\n\tNSInteger state;\n\n\tstate = NSOffState;\n\tif ([item->item state] == NSOffState)\n\t\tstate = NSOnState;\n\t[item->item setState:state];\n}\n\nstatic uiMenuItem *newItem(uiMenu *m, int type, const char *name)\n{\n\t@autoreleasepool {\n\n\tuiMenuItem *item;\n\n\tif (menusFinalized)\n\t\tuiprivUserBug(\"You can't create a new menu item after menus have been finalized.\");\n\n\titem = uiprivNew(uiMenuItem);\n\n\titem->type = type;\n\tswitch (item->type) {\n\tcase typeQuit:\n\t\titem->item = [uiprivAppDelegate().menuManager.quitItem retain];\n\t\tbreak;\n\tcase typePreferences:\n\t\titem->item = [uiprivAppDelegate().menuManager.preferencesItem retain];\n\t\tbreak;\n\tcase typeAbout:\n\t\titem->item = [uiprivAppDelegate().menuManager.aboutItem retain];\n\t\tbreak;\n\tcase typeSeparator:\n\t\titem->item = [[NSMenuItem separatorItem] retain];\n\t\t[m->menu addItem:item->item];\n\t\tbreak;\n\tdefault:\n\t\titem->item = [[NSMenuItem alloc] initWithTitle:uiprivToNSString(name) action:@selector(onClicked:) keyEquivalent:@\"\"];\n\t\t[item->item setTarget:uiprivAppDelegate().menuManager];\n\t\t[m->menu addItem:item->item];\n\t\tbreak;\n\t}\n\n\t[uiprivAppDelegate().menuManager register:item->item to:item];\n\titem->onClicked = defaultOnClicked;\n\n\t[m->items addObject:[NSValue valueWithPointer:item]];\n\n\treturn item;\n\n\t} // @autoreleasepool\n}\n\nuiMenuItem *uiMenuAppendItem(uiMenu *m, const char *name)\n{\n\treturn newItem(m, typeRegular, name);\n}\n\nuiMenuItem *uiMenuAppendCheckItem(uiMenu *m, const char *name)\n{\n\treturn newItem(m, typeCheckbox, name);\n}\n\nuiMenuItem *uiMenuAppendQuitItem(uiMenu *m)\n{\n\t// duplicate check is in the register:to: selector\n\treturn newItem(m, typeQuit, NULL);\n}\n\nuiMenuItem *uiMenuAppendPreferencesItem(uiMenu *m)\n{\n\t// duplicate check is in the register:to: selector\n\treturn newItem(m, typePreferences, NULL);\n}\n\nuiMenuItem *uiMenuAppendAboutItem(uiMenu *m)\n{\n\t// duplicate check is in the register:to: selector\n\treturn newItem(m, typeAbout, NULL);\n}\n\nvoid uiMenuAppendSeparator(uiMenu *m)\n{\n\tnewItem(m, typeSeparator, NULL);\n}\n\nuiMenu *uiNewMenu(const char *name)\n{\n\t@autoreleasepool {\n\n\tuiMenu *m;\n\n\tif (menusFinalized)\n\t\tuiprivUserBug(\"You can't create a new menu after menus have been finalized.\");\n\tif (menus == nil)\n\t\tmenus = [NSMutableArray new];\n\n\tm = uiprivNew(uiMenu);\n\n\tm->menu = [[NSMenu alloc] initWithTitle:uiprivToNSString(name)];\n\t// use automatic menu item enabling for all menus for consistency's sake\n\n\tm->item = [[NSMenuItem alloc] initWithTitle:uiprivToNSString(name) action:NULL keyEquivalent:@\"\"];\n\t[m->item setSubmenu:m->menu];\n\n\tm->items = [NSMutableArray new];\n\n\t[[uiprivNSApp() mainMenu] addItem:m->item];\n\n\t[menus addObject:[NSValue valueWithPointer:m]];\n\n\treturn m;\n\n\t} // @autoreleasepool\n}\n\nvoid uiprivFinalizeMenus(void)\n{\n\tmenusFinalized = YES;\n}\n\nvoid uiprivUninitMenus(void)\n{\n\tif (menus == NULL)\n\t\treturn;\n\t[menus enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop) {\n\t\tNSValue *v;\n\t\tuiMenu *m;\n\n\t\tv = (NSValue *) obj;\n\t\tm = (uiMenu *) [v pointerValue];\n\t\t[m->items enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop) {\n\t\t\tNSValue *v;\n\t\t\tuiMenuItem *mi;\n\n\t\t\tv = (NSValue *) obj;\n\t\t\tmi = (uiMenuItem *) [v pointerValue];\n\t\t\tuiprivFree(mi);\n\t\t}];\n\t\t[m->items release];\n\t\tuiprivFree(m);\n\t}];\n\t[menus release];\n}\n"
  },
  {
    "path": "darwin/meson.build",
    "content": "# 23 march 2019\n\nlibui_sources += [\n\t'darwin/aat.m',\n\t'darwin/alloc.m',\n\t'darwin/area.m',\n\t'darwin/areaevents.m',\n\t'darwin/attrstr.m',\n\t'darwin/autolayout.m',\n\t'darwin/box.m',\n\t'darwin/button.m',\n\t'darwin/checkbox.m',\n\t'darwin/colorbutton.m',\n\t'darwin/combobox.m',\n\t'darwin/control.m',\n\t'darwin/datetimepicker.m',\n\t'darwin/debug.m',\n\t'darwin/draw.m',\n\t'darwin/drawtext.m',\n\t'darwin/editablecombo.m',\n\t'darwin/entry.m',\n\t'darwin/fontbutton.m',\n\t'darwin/fontmatch.m',\n\t'darwin/fonttraits.m',\n\t'darwin/fontvariation.m',\n\t'darwin/form.m',\n\t'darwin/future.m',\n\t'darwin/graphemes.m',\n\t'darwin/grid.m',\n\t'darwin/group.m',\n\t'darwin/image.m',\n\t'darwin/label.m',\n\t'darwin/main.m',\n\t'darwin/map.m',\n\t'darwin/menu.m',\n\t'darwin/multilineentry.m',\n\t'darwin/opentype.m',\n\t'darwin/progressbar.m',\n\t'darwin/radiobuttons.m',\n\t'darwin/scrollview.m',\n\t'darwin/separator.m',\n\t'darwin/slider.m',\n\t'darwin/spinbox.m',\n\t'darwin/stddialogs.m',\n\t'darwin/tab.m',\n\t'darwin/table.m',\n\t'darwin/tablecolumn.m',\n\t'darwin/text.m',\n\t'darwin/undocumented.m',\n\t'darwin/util.m',\n\t'darwin/window.m',\n\t'darwin/winmoveresize.m',\n]\n\nlibui_deps += [\n\tmeson.get_compiler('objc').find_library('objc',\n\t\trequired: true),\n\tdependency('appleframeworks',\n\t\tmodules: ['Foundation', 'AppKit'],\n\t\trequired: true),\n]\nlibui_soversion = 'A'\n# the / is required by some older versions of OS X\nlibui_rpath = '@executable_path/'\n"
  },
  {
    "path": "darwin/multilineentry.m",
    "content": "// 8 december 2015\n#import \"uipriv_darwin.h\"\n\n// NSTextView has no intrinsic content size by default, which wreaks havoc on a pure-Auto Layout system\n// we'll have to take over to get it to work\n// 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\n@interface intrinsicSizeTextView : NSTextView {\n\tuiMultilineEntry *libui_e;\n}\n- (id)initWithFrame:(NSRect)r e:(uiMultilineEntry *)e;\n@end\n\nstruct uiMultilineEntry {\n\tuiDarwinControl c;\n\tNSScrollView *sv;\n\tintrinsicSizeTextView *tv;\n\tuiprivScrollViewData *d;\n\tvoid (*onChanged)(uiMultilineEntry *, void *);\n\tvoid *onChangedData;\n\tBOOL changing;\n};\n\n@implementation intrinsicSizeTextView\n\n- (id)initWithFrame:(NSRect)r e:(uiMultilineEntry *)e\n{\n\tself = [super initWithFrame:r];\n\tif (self)\n\t\tself->libui_e = e;\n\treturn self;\n}\n\n- (NSSize)intrinsicContentSize\n{\n\tNSTextContainer *textContainer;\n\tNSLayoutManager *layoutManager;\n\tNSRect rect;\n\n\ttextContainer = [self textContainer];\n\tlayoutManager = [self layoutManager];\n\t[layoutManager ensureLayoutForTextContainer:textContainer];\n\trect = [layoutManager usedRectForTextContainer:textContainer];\n\treturn rect.size;\n}\n\n- (void)didChangeText\n{\n\t[super didChangeText];\n\t[self invalidateIntrinsicContentSize];\n\tif (!self->libui_e->changing)\n\t\t(*(self->libui_e->onChanged))(self->libui_e, self->libui_e->onChangedData);\n}\n\n@end\n\nuiDarwinControlAllDefaultsExceptDestroy(uiMultilineEntry, sv)\n\nstatic void uiMultilineEntryDestroy(uiControl *c)\n{\n\tuiMultilineEntry *e = uiMultilineEntry(c);\n\n\tuiprivScrollViewFreeData(e->sv, e->d);\n\t[e->tv release];\n\t[e->sv release];\n\tuiFreeControl(uiControl(e));\n}\n\nstatic void defaultOnChanged(uiMultilineEntry *e, void *data)\n{\n\t// do nothing\n}\n\nchar *uiMultilineEntryText(uiMultilineEntry *e)\n{\n\treturn uiDarwinNSStringToText([e->tv string]);\n}\n\nvoid uiMultilineEntrySetText(uiMultilineEntry *e, const char *text)\n{\n\t[[e->tv textStorage] replaceCharactersInRange:NSMakeRange(0, [[e->tv string] length])\n\t\twithString:uiprivToNSString(text)];\n\t// must be called explicitly according to the documentation of shouldChangeTextInRange:replacementString:\n\te->changing = YES;\n\t[e->tv didChangeText];\n\te->changing = NO;\n}\n\n// TODO scroll to end?\nvoid uiMultilineEntryAppend(uiMultilineEntry *e, const char *text)\n{\n\t[[e->tv textStorage] replaceCharactersInRange:NSMakeRange([[e->tv string] length], 0)\n\t\twithString:uiprivToNSString(text)];\n\te->changing = YES;\n\t[e->tv didChangeText];\n\te->changing = NO;\n}\n\nvoid uiMultilineEntryOnChanged(uiMultilineEntry *e, void (*f)(uiMultilineEntry *e, void *data), void *data)\n{\n\te->onChanged = f;\n\te->onChangedData = data;\n}\n\nint uiMultilineEntryReadOnly(uiMultilineEntry *e)\n{\n\treturn [e->tv isEditable] == NO;\n}\n\nvoid uiMultilineEntrySetReadOnly(uiMultilineEntry *e, int readonly)\n{\n\tBOOL editable;\n\n\teditable = YES;\n\tif (readonly)\n\t\teditable = NO;\n\t[e->tv setEditable:editable];\n}\n\nstatic uiMultilineEntry *finishMultilineEntry(BOOL hscroll)\n{\n\tuiMultilineEntry *e;\n\tNSFont *font;\n\tuiprivScrollViewCreateParams p;\n\n\tuiDarwinNewControl(uiMultilineEntry, e);\n\n\te->tv = [[intrinsicSizeTextView alloc] initWithFrame:NSZeroRect e:e];\n\n\t// verified against Interface Builder for a sufficiently customized text view\n\n\t// NSText properties:\n\t// this is what Interface Builder sets the background color to\n\t[e->tv setBackgroundColor:[NSColor colorWithCalibratedWhite:1.0 alpha:1.0]];\n\t[e->tv setDrawsBackground:YES];\n\t[e->tv setEditable:YES];\n\t[e->tv setSelectable:YES];\n\t[e->tv setFieldEditor:NO];\n\t[e->tv setRichText:NO];\n\t[e->tv setImportsGraphics:NO];\n\t[e->tv setUsesFontPanel:NO];\n\t[e->tv setRulerVisible:NO];\n\t// we'll handle font last\n\t// while setAlignment: has been around since 10.0, the named constant \"NSTextAlignmentNatural\" seems to have only been introduced in 10.11\n#define ourNSTextAlignmentNatural 4\n\t[e->tv setAlignment:ourNSTextAlignmentNatural];\n\t// textColor is set to nil, just keep the dfault\n\t[e->tv setBaseWritingDirection:NSWritingDirectionNatural];\n\t[e->tv setHorizontallyResizable:NO];\n\t[e->tv setVerticallyResizable:YES];\n\n\t// NSTextView properties:\n\t[e->tv setAllowsDocumentBackgroundColorChange:NO];\n\t[e->tv setAllowsUndo:YES];\n\t// default paragraph style is nil; keep default\n\t[e->tv setAllowsImageEditing:NO];\n\t[e->tv setAutomaticQuoteSubstitutionEnabled:NO];\n\t[e->tv setAutomaticLinkDetectionEnabled:NO];\n\t[e->tv setDisplaysLinkToolTips:YES];\n\t[e->tv setUsesRuler:NO];\n\t[e->tv setUsesInspectorBar:NO];\n\t[e->tv setSelectionGranularity:NSSelectByCharacter];\n\t// there is a dedicated named insertion point color but oh well\n\t[e->tv setInsertionPointColor:[NSColor controlTextColor]];\n\t// typing attributes is nil; keep default (we change it below for fonts though)\n\t[e->tv setSmartInsertDeleteEnabled:NO];\n\t[e->tv setContinuousSpellCheckingEnabled:NO];\n\t[e->tv setGrammarCheckingEnabled:NO];\n\t[e->tv setUsesFindPanel:YES];\n\t[e->tv setEnabledTextCheckingTypes:0];\n\t[e->tv setAutomaticDashSubstitutionEnabled:NO];\n\t[e->tv setAutomaticDataDetectionEnabled:NO];\n\t[e->tv setAutomaticSpellingCorrectionEnabled:NO];\n\t[e->tv setAutomaticTextReplacementEnabled:NO];\n\t[e->tv setUsesFindBar:NO];\n\t[e->tv setIncrementalSearchingEnabled:NO];\n\n\t// NSTextContainer properties:\n\t[[e->tv textContainer] setWidthTracksTextView:YES];\n\t[[e->tv textContainer] setHeightTracksTextView:NO];\n\n\t// NSLayoutManager properties:\n\t[[e->tv layoutManager] setAllowsNonContiguousLayout:YES];\n\n\t// now just to be safe; this will do some of the above but whatever\n\tuiprivDisableAutocorrect(e->tv);\n\n\t// see https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/TextUILayer/Tasks/TextInScrollView.html\n\t// notice we don't use the Auto Layout code; see scrollview.m for more details\n\t[e->tv setMaxSize:NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX)];\n\t[e->tv setVerticallyResizable:YES];\n\t[e->tv setHorizontallyResizable:hscroll];\n\tif (hscroll) {\n\t\t[e->tv setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];\n\t\t[[e->tv textContainer] setWidthTracksTextView:NO];\n\t} else {\n\t\t[e->tv setAutoresizingMask:NSViewWidthSizable];\n\t\t[[e->tv textContainer] setWidthTracksTextView:YES];\n\t}\n\t[[e->tv textContainer] setContainerSize:NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX)];\n\n\t// don't use uiDarwinSetControlFont() directly; we have to do a little extra work to set the font\n\tfont = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]];\n\t[e->tv setTypingAttributes:[NSDictionary\n\t\tdictionaryWithObject:font\n\t\tforKey:NSFontAttributeName]];\n\t// e->tv font from Interface Builder is nil, but setFont:nil throws an exception\n\t// let's just set it to the standard control font anyway, just to be safe\n\t[e->tv setFont:font];\n\n\tmemset(&p, 0, sizeof (uiprivScrollViewCreateParams));\n\tp.DocumentView = e->tv;\n\t// this is what Interface Builder sets it to\n\tp.BackgroundColor = [NSColor colorWithCalibratedWhite:1.0 alpha:1.0];\n\tp.DrawsBackground = YES;\n\tp.Bordered = YES;\n\tp.HScroll = hscroll;\n\tp.VScroll = YES;\n\te->sv = uiprivMkScrollView(&p, &(e->d));\n\n\tuiMultilineEntryOnChanged(e, defaultOnChanged, NULL);\n\n\treturn e;\n}\n\nuiMultilineEntry *uiNewMultilineEntry(void)\n{\n\treturn finishMultilineEntry(NO);\n}\n\nuiMultilineEntry *uiNewNonWrappingMultilineEntry(void)\n{\n\treturn finishMultilineEntry(YES);\n}\n"
  },
  {
    "path": "darwin/opentype.m",
    "content": "// 11 may 2017\n#import \"uipriv_darwin.h\"\n#import \"attrstr.h\"\n\nstruct addCTFeatureEntryParams {\n\tCFMutableArrayRef array;\n\tconst void *tagKey;\n\tBOOL tagIsNumber;\n\tCFNumberType tagType;\n\tconst void *tagValue;\n\tconst void *valueKey;\n\tCFNumberType valueType;\n\tconst void *valueValue;\n};\n\nstatic void addCTFeatureEntry(struct addCTFeatureEntryParams *p)\n{\n\tCFDictionaryRef featureDict;\n\tCFNumberRef tagNum, valueNum;\n\tconst void *keys[2], *values[2];\n\n\tkeys[0] = p->tagKey;\n\ttagNum = NULL;\n\tvalues[0] = p->tagValue;\n\tif (p->tagIsNumber) {\n\t\ttagNum = CFNumberCreate(NULL, p->tagType, p->tagValue);\n\t\tvalues[0] = tagNum;\n\t}\n\n\tkeys[1] = p->valueKey;\n\tvalueNum = CFNumberCreate(NULL, p->valueType, p->valueValue);\n\tvalues[1] = valueNum;\n\n\tfeatureDict = CFDictionaryCreate(NULL,\n\t\tkeys, values, 2,\n\t\t// TODO are these correct?\n\t\t&kCFCopyStringDictionaryKeyCallBacks,\n\t\t&kCFTypeDictionaryValueCallBacks);\n\tif (featureDict == NULL) {\n\t\t// TODO\n\t}\n\tCFArrayAppendValue(p->array, featureDict);\n\n\tCFRelease(featureDict);\n\tCFRelease(valueNum);\n\tif (p->tagIsNumber)\n\t\tCFRelease(tagNum);\n}\n\nstatic uiForEach otfArrayForEachAAT(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value, void *data)\n{\n\t__block struct addCTFeatureEntryParams p;\n\n\tp.array = (CFMutableArrayRef) data;\n\tp.tagIsNumber = YES;\n\tuiprivOpenTypeToAAT(a, b, c, d, value, ^(uint16_t type, uint16_t selector) {\n\t\tp.tagKey = kCTFontFeatureTypeIdentifierKey;\n\t\tp.tagType = kCFNumberSInt16Type;\n\t\tp.tagValue = (const SInt16 *) (&type);\n\t\tp.valueKey = kCTFontFeatureSelectorIdentifierKey;\n\t\tp.valueType = kCFNumberSInt16Type;\n\t\tp.valueValue = (const SInt16 *) (&selector);\n\t\taddCTFeatureEntry(&p);\n\t});\n\treturn uiForEachContinue;\n}\n\n// TODO find out which fonts differ in AAT small caps and test them with this\nstatic uiForEach otfArrayForEachOT(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value, void *data)\n{\n\tstruct addCTFeatureEntryParams p;\n\tchar tagcstr[5];\n\tCFStringRef tagstr;\n\n\tp.array = (CFMutableArrayRef) data;\n\n\tp.tagKey = *uiprivFUTURE_kCTFontOpenTypeFeatureTag;\n\tp.tagIsNumber = NO;\n\ttagcstr[0] = a;\n\ttagcstr[1] = b;\n\ttagcstr[2] = c;\n\ttagcstr[3] = d;\n\ttagcstr[4] = '\\0';\n\ttagstr = CFStringCreateWithCString(NULL, tagcstr, kCFStringEncodingUTF8);\n\tif (tagstr == NULL) {\n\t\t// TODO\n\t}\n\tp.tagValue = tagstr;\n\n\tp.valueKey = *uiprivFUTURE_kCTFontOpenTypeFeatureValue;\n\tp.valueType = kCFNumberSInt32Type;\n\tp.valueValue = (const SInt32 *) (&value);\n\taddCTFeatureEntry(&p);\n\n\tCFRelease(tagstr);\n\treturn uiForEachContinue;\n}\n\nCFArrayRef uiprivOpenTypeFeaturesToCTFeatures(const uiOpenTypeFeatures *otf)\n{\n\tCFMutableArrayRef array;\n\tuiOpenTypeFeaturesForEachFunc f;\n\n\tarray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);\n\tif (array == NULL) {\n\t\t// TODO\n\t}\n\tf = otfArrayForEachAAT;\n\tif (uiprivFUTURE_kCTFontOpenTypeFeatureTag != NULL && uiprivFUTURE_kCTFontOpenTypeFeatureValue != NULL)\n\t\tf = otfArrayForEachOT;\n\tuiOpenTypeFeaturesForEach(otf, f, array);\n\treturn array;\n}\n"
  },
  {
    "path": "darwin/progressbar.m",
    "content": "// 14 august 2015\n#import \"uipriv_darwin.h\"\n\n// NSProgressIndicator has no intrinsic width by default; use the default width in Interface Builder\n#define progressIndicatorWidth 100\n\n@interface intrinsicWidthNSProgressIndicator : NSProgressIndicator\n@end\n\n@implementation intrinsicWidthNSProgressIndicator\n\n- (NSSize)intrinsicContentSize\n{\n\tNSSize s;\n\n\ts = [super intrinsicContentSize];\n\ts.width = progressIndicatorWidth;\n\treturn s;\n}\n\n@end\n\nstruct uiProgressBar {\n\tuiDarwinControl c;\n\tNSProgressIndicator *pi;\n};\n\nuiDarwinControlAllDefaults(uiProgressBar, pi)\n\nint uiProgressBarValue(uiProgressBar *p)\n{\n\tif ([p->pi isIndeterminate])\n\t\treturn -1;\n\treturn [p->pi doubleValue];\n}\n\nvoid uiProgressBarSetValue(uiProgressBar *p, int value)\n{\n\tif (value == -1) {\n\t\t[p->pi setIndeterminate:YES];\n\t\t[p->pi startAnimation:p->pi];\n\t\treturn;\n\t}\n\n\tif ([p->pi isIndeterminate]) {\n\t\t[p->pi setIndeterminate:NO];\n\t\t[p->pi stopAnimation:p->pi];\n\t}\n\n\tif (value < 0 || value > 100)\n\t\tuiprivUserBug(\"Value %d out of range for a uiProgressBar.\", value);\n\n\t// on 10.8 there's an animation when the progress bar increases, just like with Aero\n\tif (value == 100) {\n\t\t[p->pi setMaxValue:101];\n\t\t[p->pi setDoubleValue:101];\n\t\t[p->pi setDoubleValue:100];\n\t\t[p->pi setMaxValue:100];\n\t\treturn;\n\t}\n\t[p->pi setDoubleValue:((double) (value + 1))];\n\t[p->pi setDoubleValue:((double) value)];\n}\n\nuiProgressBar *uiNewProgressBar(void)\n{\n\tuiProgressBar *p;\n\n\tuiDarwinNewControl(uiProgressBar, p);\n\n\tp->pi = [[intrinsicWidthNSProgressIndicator alloc] initWithFrame:NSZeroRect];\n\t[p->pi setControlSize:NSRegularControlSize];\n\t[p->pi setBezeled:YES];\n\t[p->pi setStyle:NSProgressIndicatorBarStyle];\n\t[p->pi setIndeterminate:NO];\n\n\treturn p;\n}\n"
  },
  {
    "path": "darwin/radiobuttons.m",
    "content": "// 14 august 2015\n#import \"uipriv_darwin.h\"\n\n// TODO resizing the controlgallery vertically causes the third button to still resize :|\n\n// 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.\n// This is documented on the NSMatrix page, but the rest of the OS X documentation says to still use NSMatrix.\n// NSMatrix has weird quirks anyway...\n\n// LONGTERM 6 units of spacing between buttons, as suggested by Interface Builder?\n\n@interface radioButtonsDelegate : NSObject {\n\tuiRadioButtons *libui_r;\n}\n- (id)initWithR:(uiRadioButtons *)r;\n- (IBAction)onClicked:(id)sender;\n@end\n\nstruct uiRadioButtons {\n\tuiDarwinControl c;\n\tNSView *view;\n\tNSMutableArray *buttons;\n\tNSMutableArray *constraints;\n\tNSLayoutConstraint *lastv;\n\tradioButtonsDelegate *delegate;\n\tvoid (*onSelected)(uiRadioButtons *, void *);\n\tvoid *onSelectedData;\n};\n\n@implementation radioButtonsDelegate\n\n- (id)initWithR:(uiRadioButtons *)r\n{\n\tself = [super init];\n\tif (self)\n\t\tself->libui_r = r;\n\treturn self;\n}\n\n- (IBAction)onClicked:(id)sender\n{\n\tuiRadioButtons *r = self->libui_r;\n\n\t(*(r->onSelected))(r, r->onSelectedData);\n}\n\n@end\n\nuiDarwinControlAllDefaultsExceptDestroy(uiRadioButtons, view)\n\nstatic void defaultOnSelected(uiRadioButtons *r, void *data)\n{\n\t// do nothing\n}\n\nstatic void uiRadioButtonsDestroy(uiControl *c)\n{\n\tuiRadioButtons *r = uiRadioButtons(c);\n\tNSButton *b;\n\n\t// drop the constraints\n\t[r->view removeConstraints:r->constraints];\n\t[r->constraints release];\n\tif (r->lastv != nil)\n\t\t[r->lastv release];\n\t// destroy the buttons\n\tfor (b in r->buttons) {\n\t\t[b setTarget:nil];\n\t\t[b removeFromSuperview];\n\t}\n\t[r->buttons release];\n\t// destroy the delegate\n\t[r->delegate release];\n\t// and destroy ourselves\n\t[r->view release];\n\tuiFreeControl(uiControl(r));\n}\n\nstatic NSButton *buttonAt(uiRadioButtons *r, int n)\n{\n\treturn (NSButton *) [r->buttons objectAtIndex:n];\n}\n\nvoid uiRadioButtonsAppend(uiRadioButtons *r, const char *text)\n{\n\tNSButton *b, *b2;\n\tNSLayoutConstraint *constraint;\n\n\tb = [[NSButton alloc] initWithFrame:NSZeroRect];\n\t[b setTitle:uiprivToNSString(text)];\n\t[b setButtonType:NSRadioButton];\n\t// doesn't seem to have an associated bezel style\n\t[b setBordered:NO];\n\t[b setTransparent:NO];\n\tuiDarwinSetControlFont(b, NSRegularControlSize);\n\t[b setTranslatesAutoresizingMaskIntoConstraints:NO];\n\n\t[b setTarget:r->delegate];\n\t[b setAction:@selector(onClicked:)];\n\n\t[r->buttons addObject:b];\n\t[r->view addSubview:b];\n\n\t// pin horizontally to the edges of the superview\n\tconstraint = uiprivMkConstraint(b, NSLayoutAttributeLeading,\n\t\tNSLayoutRelationEqual,\n\t\tr->view, NSLayoutAttributeLeading,\n\t\t1, 0,\n\t\t@\"uiRadioButtons button leading constraint\");\n\t[r->view addConstraint:constraint];\n\t[r->constraints addObject:constraint];\n\tconstraint = uiprivMkConstraint(b, NSLayoutAttributeTrailing,\n\t\tNSLayoutRelationEqual,\n\t\tr->view, NSLayoutAttributeTrailing,\n\t\t1, 0,\n\t\t@\"uiRadioButtons button trailing constraint\");\n\t[r->view addConstraint:constraint];\n\t[r->constraints addObject:constraint];\n\n\t// if this is the first view, pin it to the top\n\t// otherwise pin to the bottom of the last\n\tif ([r->buttons count] == 1)\n\t\tconstraint = uiprivMkConstraint(b, NSLayoutAttributeTop,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tr->view, NSLayoutAttributeTop,\n\t\t\t1, 0,\n\t\t\t@\"uiRadioButtons first button top constraint\");\n\telse {\n\t\tb2 = buttonAt(r, [r->buttons count] - 2);\n\t\tconstraint = uiprivMkConstraint(b, NSLayoutAttributeTop,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tb2, NSLayoutAttributeBottom,\n\t\t\t1, 0,\n\t\t\t@\"uiRadioButtons non-first button top constraint\");\n\t}\n\t[r->view addConstraint:constraint];\n\t[r->constraints addObject:constraint];\n\n\t// if there is a previous bottom constraint, remove it\n\tif (r->lastv != nil) {\n\t\t[r->view removeConstraint:r->lastv];\n\t\t[r->constraints removeObject:r->lastv];\n\t\t[r->lastv release];\n\t}\n\n\t// and make the new bottom constraint\n\tr->lastv = uiprivMkConstraint(b, NSLayoutAttributeBottom,\n\t\tNSLayoutRelationEqual,\n\t\tr->view, NSLayoutAttributeBottom,\n\t\t1, 0,\n\t\t@\"uiRadioButtons last button bottom constraint\");\n\t[r->view addConstraint:r->lastv];\n\t[r->constraints addObject:r->lastv];\n\t[r->lastv retain];\n}\n\nint uiRadioButtonsSelected(uiRadioButtons *r)\n{\n\tNSButton *b;\n\tNSUInteger i;\n\n\tfor (i = 0; i < [r->buttons count]; i++) {\n\t\tb = (NSButton *) [r->buttons objectAtIndex:i];\n\t\tif ([b state] == NSOnState)\n\t\t\treturn i;\n\t}\n\treturn -1;\n}\n\nvoid uiRadioButtonsSetSelected(uiRadioButtons *r, int n)\n{\n\tNSButton *b;\n\tNSInteger state;\n\n\tstate = NSOnState;\n\tif (n == -1) {\n\t\tn = uiRadioButtonsSelected(r);\n\t\tif (n == -1)\t\t// from nothing to nothing; do nothing\n\t\t\treturn;\n\t\tstate = NSOffState;\n\t}\n\tb = (NSButton *) [r->buttons objectAtIndex:n];\n\t[b setState:state];\n}\n\nvoid uiRadioButtonsOnSelected(uiRadioButtons *r, void (*f)(uiRadioButtons *, void *), void *data)\n{\n\tr->onSelected = f;\n\tr->onSelectedData = data;\n}\n\nuiRadioButtons *uiNewRadioButtons(void)\n{\n\tuiRadioButtons *r;\n\n\tuiDarwinNewControl(uiRadioButtons, r);\n\n\tr->view = [[NSView alloc] initWithFrame:NSZeroRect];\n\tr->buttons = [NSMutableArray new];\n\tr->constraints = [NSMutableArray new];\n\n\tr->delegate = [[radioButtonsDelegate alloc] initWithR:r];\n\n\tuiRadioButtonsOnSelected(r, defaultOnSelected, NULL);\n\n\treturn r;\n}\n"
  },
  {
    "path": "darwin/scrollview.m",
    "content": "// 27 may 2016\n#include \"uipriv_darwin.h\"\n\n// 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\n// TODO do the same with uiGroup and uiTab?\n\nstruct uiprivScrollViewData {\n\tBOOL hscroll;\n\tBOOL vscroll;\n};\n\nNSScrollView *uiprivMkScrollView(uiprivScrollViewCreateParams *p, uiprivScrollViewData **dout)\n{\n\tNSScrollView *sv;\n\tNSBorderType border;\n\tuiprivScrollViewData *d;\n\n\tsv = [[NSScrollView alloc] initWithFrame:NSZeroRect];\n\tif (p->BackgroundColor != nil)\n\t\t[sv setBackgroundColor:p->BackgroundColor];\n\t[sv setDrawsBackground:p->DrawsBackground];\n\tborder = NSNoBorder;\n\tif (p->Bordered)\n\t\tborder = NSBezelBorder;\n\t// document view seems to set the cursor properly\n\t[sv setBorderType:border];\n\t[sv setAutohidesScrollers:YES];\n\t[sv setHasHorizontalRuler:NO];\n\t[sv setHasVerticalRuler:NO];\n\t[sv setRulersVisible:NO];\n\t[sv setScrollerKnobStyle:NSScrollerKnobStyleDefault];\n\t// the scroller style is documented as being set by default for us\n\t// LONGTERM verify line and page for programmatically created NSTableView\n\t[sv setScrollsDynamically:YES];\n\t[sv setFindBarPosition:NSScrollViewFindBarPositionAboveContent];\n\t[sv setUsesPredominantAxisScrolling:NO];\n\t[sv setHorizontalScrollElasticity:NSScrollElasticityAutomatic];\n\t[sv setVerticalScrollElasticity:NSScrollElasticityAutomatic];\n\t[sv setAllowsMagnification:NO];\n\n\t[sv setDocumentView:p->DocumentView];\n\td = uiprivNew(uiprivScrollViewData);\n\tuiprivScrollViewSetScrolling(sv, d, p->HScroll, p->VScroll);\n\n\t*dout = d;\n\treturn sv;\n}\n\n// 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\nvoid uiprivScrollViewSetScrolling(NSScrollView *sv, uiprivScrollViewData *d, BOOL hscroll, BOOL vscroll)\n{\n\td->hscroll = hscroll;\n\t[sv setHasHorizontalScroller:d->hscroll];\n\td->vscroll = vscroll;\n\t[sv setHasVerticalScroller:d->vscroll];\n}\n\nvoid uiprivScrollViewFreeData(NSScrollView *sv, uiprivScrollViewData *d)\n{\n\tuiprivFree(d);\n}\n"
  },
  {
    "path": "darwin/separator.m",
    "content": "// 14 august 2015\n#import \"uipriv_darwin.h\"\n\n// TODO make this intrinsic\n#define separatorWidth 96\n#define separatorHeight 96\n\nstruct uiSeparator {\n\tuiDarwinControl c;\n\tNSBox *box;\n};\n\nuiDarwinControlAllDefaults(uiSeparator, box)\n\nuiSeparator *uiNewHorizontalSeparator(void)\n{\n\tuiSeparator *s;\n\n\tuiDarwinNewControl(uiSeparator, s);\n\n\t// make the initial width >= initial height to force horizontal\n\ts->box = [[NSBox alloc] initWithFrame:NSMakeRect(0, 0, 100, 1)];\n\t[s->box setBoxType:NSBoxSeparator];\n\t[s->box setBorderType:NSGrooveBorder];\n\t[s->box setTransparent:NO];\n\t[s->box setTitlePosition:NSNoTitle];\n\n\treturn s;\n}\n\nuiSeparator *uiNewVerticalSeparator(void)\n{\n\tuiSeparator *s;\n\n\tuiDarwinNewControl(uiSeparator, s);\n\n\t// make the initial height >= initial width to force vertical\n\ts->box = [[NSBox alloc] initWithFrame:NSMakeRect(0, 0, 1, 100)];\n\t[s->box setBoxType:NSBoxSeparator];\n\t[s->box setBorderType:NSGrooveBorder];\n\t[s->box setTransparent:NO];\n\t[s->box setTitlePosition:NSNoTitle];\n\n\treturn s;\n}\n"
  },
  {
    "path": "darwin/sierra.h",
    "content": "// 2 june 2017\n\n// The OS X 10.12 SDK introduces a number of new names for\n// existing constants to align the naming conventions of\n// Objective-C and Swift (particularly in AppKit).\n// \n// Unfortunately, in a baffling move, instead of using the existing\n// AvailabilityMacros.h method of marking things deprecated, they\n// rewrote the relevant constants in ways that make\n// source-compatible building much more annoying:\n// \n// - The replacement names are now the only names in the enum\n// \tor define sets they used to be in.\n// - The old names are provided as static const variables, which\n// \tmeans any code that used the old names in a switch case now\n// \tspit out a compiler warning in strict C99 mode (TODO and in C++ mode?).\n// - The old names are marked with new deprecated-symbol\n// \tmacros that are *not* compatible with the AvailabilityMacros.h\n// \tmacros, meaning their deprecation warnings still come\n// \tthrough. (It should be noted that AvailabilityMacros.h was still\n// \tupdated for 10.12 regardless, hence our #ifdef below.)\n// \n// As far as I can gather, these facts are not documented *at all*, so\n// in the meantime, other open-source projects just use their own\n// #defines to maintain source compatibility, either by making the\n// new names available everywhere or the old ones un-deprecated.\n// We choose the latter.\n// TODO file a radar on the issue (after determining C++ compatibility) so this can be pinned down once and for all\n// TODO after that, link my stackoverflow question here too\n// TODO make sure this #ifdef does actually work on older systems\n\n#ifdef MAC_OS_X_VERSION_10_12\n\n#define NSControlKeyMask NSEventModifierFlagControl\n#define NSAlternateKeyMask NSEventModifierFlagOption\n#define NSShiftKeyMask NSEventModifierFlagShift\n#define NSCommandKeyMask NSEventModifierFlagCommand\n\n#define NSLeftMouseDown NSEventTypeLeftMouseDown\n#define NSRightMouseDown NSEventTypeRightMouseDown\n#define NSOtherMouseDown NSEventTypeOtherMouseDown\n#define NSLeftMouseUp NSEventTypeLeftMouseUp\n#define NSRightMouseUp NSEventTypeRightMouseUp\n#define NSOtherMouseUp NSEventTypeOtherMouseUp\n#define NSLeftMouseDragged NSEventTypeLeftMouseDragged\n#define NSRightMouseDragged NSEventTypeRightMouseDragged\n#define NSOtherMouseDragged NSEventTypeOtherMouseDragged\n#define NSKeyDown NSEventTypeKeyDown\n#define NSKeyUp NSEventTypeKeyUp\n#define NSFlagsChanged NSEventTypeFlagsChanged\n#define NSApplicationDefined NSEventTypeApplicationDefined\n#define NSPeriodic NSEventTypePeriodic\n#define NSMouseMoved NSEventTypeMouseMoved\n\n#define NSRegularControlSize NSControlSizeRegular\n#define NSSmallControlSize NSControlSizeSmall\n\n#define NSAnyEventMask NSEventMaskAny\n#define NSLeftMouseDraggedMask NSEventMaskLeftMouseDragged\n#define NSLeftMouseUpMask NSEventMaskLeftMouseUp\n\n#define NSTickMarkAbove NSTickMarkPositionAbove\n\n#define NSLinearSlider NSSliderTypeLinear\n\n#define NSInformationalAlertStyle NSAlertStyleInformational\n#define NSCriticalAlertStyle NSAlertStyleCritical\n\n#define NSBorderlessWindowMask NSWindowStyleMaskBorderless\n#define NSTitledWindowMask NSWindowStyleMaskTitled\n#define NSClosableWindowMask NSWindowStyleMaskClosable\n#define NSMiniaturizableWindowMask NSWindowStyleMaskMiniaturizable\n#define NSResizableWindowMask NSWindowStyleMaskResizable\n\n#endif\n\n// 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]\n\n// TODO https://developer.apple.com/library/content/releasenotes/Miscellaneous/RN-Foundation-OSX10.12/\n"
  },
  {
    "path": "darwin/slider.m",
    "content": "// 14 august 2015\n#import \"uipriv_darwin.h\"\n\n// Horizontal sliders have no intrinsic width; we'll use the default Interface Builder width for them.\n// This will also be used for the initial frame size, to ensure the slider is always horizontal (see below).\n#define sliderWidth 92\n\n@interface libui_intrinsicWidthNSSlider : NSSlider\n@end\n\n@implementation libui_intrinsicWidthNSSlider\n\n- (NSSize)intrinsicContentSize\n{\n\tNSSize s;\n\n\ts = [super intrinsicContentSize];\n\ts.width = sliderWidth;\n\treturn s;\n}\n\n@end\n\nstruct uiSlider {\n\tuiDarwinControl c;\n\tNSSlider *slider;\n\tvoid (*onChanged)(uiSlider *, void *);\n\tvoid *onChangedData;\n};\n\n@interface sliderDelegateClass : NSObject {\n\tuiprivMap *sliders;\n}\n- (IBAction)onChanged:(id)sender;\n- (void)registerSlider:(uiSlider *)b;\n- (void)unregisterSlider:(uiSlider *)b;\n@end\n\n@implementation sliderDelegateClass\n\n- (id)init\n{\n\tself = [super init];\n\tif (self)\n\t\tself->sliders = uiprivNewMap();\n\treturn self;\n}\n\n- (void)dealloc\n{\n\tuiprivMapDestroy(self->sliders);\n\t[super dealloc];\n}\n\n- (IBAction)onChanged:(id)sender\n{\n\tuiSlider *s;\n\n\ts = (uiSlider *) uiprivMapGet(self->sliders, sender);\n\t(*(s->onChanged))(s, s->onChangedData);\n}\n\n- (void)registerSlider:(uiSlider *)s\n{\n\tuiprivMapSet(self->sliders, s->slider, s);\n\t[s->slider setTarget:self];\n\t[s->slider setAction:@selector(onChanged:)];\n}\n\n- (void)unregisterSlider:(uiSlider *)s\n{\n\t[s->slider setTarget:nil];\n\tuiprivMapDelete(self->sliders, s->slider);\n}\n\n@end\n\nstatic sliderDelegateClass *sliderDelegate = nil;\n\nuiDarwinControlAllDefaultsExceptDestroy(uiSlider, slider)\n\nstatic void uiSliderDestroy(uiControl *c)\n{\n\tuiSlider *s = uiSlider(c);\n\n\t[sliderDelegate unregisterSlider:s];\n\t[s->slider release];\n\tuiFreeControl(uiControl(s));\n}\n\nint uiSliderValue(uiSlider *s)\n{\n\treturn [s->slider integerValue];\n}\n\nvoid uiSliderSetValue(uiSlider *s, int value)\n{\n\t[s->slider setIntegerValue:value];\n}\n\nvoid uiSliderOnChanged(uiSlider *s, void (*f)(uiSlider *, void *), void *data)\n{\n\ts->onChanged = f;\n\ts->onChangedData = data;\n}\n\nstatic void defaultOnChanged(uiSlider *s, void *data)\n{\n\t// do nothing\n}\n\nuiSlider *uiNewSlider(int min, int max)\n{\n\tuiSlider *s;\n\tNSSliderCell *cell;\n\tint temp;\n\n\tif (min >= max) {\n\t\ttemp = min;\n\t\tmin = max;\n\t\tmax = temp;\n\t}\n\n\tuiDarwinNewControl(uiSlider, s);\n\n\t// a horizontal slider is defined as one where the width > height, not by a flag\n\t// to be safe, don't use NSZeroRect, but make it horizontal from the get-go\n\ts->slider = [[libui_intrinsicWidthNSSlider alloc]\n\t\tinitWithFrame:NSMakeRect(0, 0, sliderWidth, 2)];\n\t[s->slider setMinValue:min];\n\t[s->slider setMaxValue:max];\n\t[s->slider setAllowsTickMarkValuesOnly:NO];\n\t[s->slider setNumberOfTickMarks:0];\n\t[s->slider setTickMarkPosition:NSTickMarkAbove];\n\n\tcell = (NSSliderCell *) [s->slider cell];\n\t[cell setSliderType:NSLinearSlider];\n\n\tif (sliderDelegate == nil) {\n\t\tsliderDelegate = [[sliderDelegateClass new] autorelease];\n\t\t[uiprivDelegates addObject:sliderDelegate];\n\t}\n\t[sliderDelegate registerSlider:s];\n\tuiSliderOnChanged(s, defaultOnChanged, NULL);\n\n\treturn s;\n}\n"
  },
  {
    "path": "darwin/spinbox.m",
    "content": "// 14 august 2015\n#import \"uipriv_darwin.h\"\n\n@interface libui_spinbox : NSView<NSTextFieldDelegate> {\n\tNSTextField *tf;\n\tNSNumberFormatter *formatter;\n\tNSStepper *stepper;\n\n\tNSInteger value;\n\tNSInteger minimum;\n\tNSInteger maximum;\n\n\tuiSpinbox *spinbox;\n}\n- (id)initWithFrame:(NSRect)r spinbox:(uiSpinbox *)sb;\n// see https://github.com/andlabs/ui/issues/82\n- (NSInteger)libui_value;\n- (void)libui_setValue:(NSInteger)val;\n- (void)setMinimum:(NSInteger)min;\n- (void)setMaximum:(NSInteger)max;\n- (IBAction)stepperClicked:(id)sender;\n- (void)controlTextDidChange:(NSNotification *)note;\n@end\n\nstruct uiSpinbox {\n\tuiDarwinControl c;\n\tlibui_spinbox *spinbox;\n\tvoid (*onChanged)(uiSpinbox *, void *);\n\tvoid *onChangedData;\n};\n\n// yes folks, this varies by operating system! woo!\n// 10.10 started drawing the NSStepper one point too low, so we have to fix it up conditionally\n// TODO test this; we'll probably have to substitute 10_9\nstatic CGFloat stepperYDelta(void)\n{\n\t// via https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKit/\n\tif (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_9)\n\t\treturn 0;\n\treturn -1;\n}\n\n@implementation libui_spinbox\n\n- (id)initWithFrame:(NSRect)r spinbox:(uiSpinbox *)sb\n{\n\tself = [super initWithFrame:r];\n\tif (self) {\n\t\tself->tf = uiprivNewEditableTextField();\n\t\t[self->tf setTranslatesAutoresizingMaskIntoConstraints:NO];\n\n\t\tself->formatter = [NSNumberFormatter new];\n\t\t[self->formatter setFormatterBehavior:NSNumberFormatterBehavior10_4];\n\t\t[self->formatter setLocalizesFormat:NO];\n\t\t[self->formatter setUsesGroupingSeparator:NO];\n\t\t[self->formatter setHasThousandSeparators:NO];\n\t\t[self->formatter setAllowsFloats:NO];\n\t\t[self->tf setFormatter:self->formatter];\n\n\t\tself->stepper = [[NSStepper alloc] initWithFrame:NSZeroRect];\n\t\t[self->stepper setIncrement:1];\n\t\t[self->stepper setValueWraps:NO];\n\t\t[self->stepper setAutorepeat:YES];              // hold mouse button to step repeatedly\n\t\t[self->stepper setTranslatesAutoresizingMaskIntoConstraints:NO];\n\n\t\t[self->tf setDelegate:self];\n\t\t[self->stepper setTarget:self];\n\t\t[self->stepper setAction:@selector(stepperClicked:)];\n\n\t\t[self addSubview:self->tf];\n\t\t[self addSubview:self->stepper];\n\n\t\t[self addConstraint:uiprivMkConstraint(self->tf, NSLayoutAttributeLeading,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tself, NSLayoutAttributeLeading,\n\t\t\t1, 0,\n\t\t\t@\"uiSpinbox left edge\")];\n\t\t[self addConstraint:uiprivMkConstraint(self->stepper, NSLayoutAttributeTrailing,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tself, NSLayoutAttributeTrailing,\n\t\t\t1, 0,\n\t\t\t@\"uiSpinbox right edge\")];\n\t\t[self addConstraint:uiprivMkConstraint(self->tf, NSLayoutAttributeTop,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tself, NSLayoutAttributeTop,\n\t\t\t1, 0,\n\t\t\t@\"uiSpinbox top edge text field\")];\n\t\t[self addConstraint:uiprivMkConstraint(self->tf, NSLayoutAttributeBottom,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tself, NSLayoutAttributeBottom,\n\t\t\t1, 0,\n\t\t\t@\"uiSpinbox bottom edge text field\")];\n\t\t[self addConstraint:uiprivMkConstraint(self->stepper, NSLayoutAttributeTop,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tself, NSLayoutAttributeTop,\n\t\t\t1, stepperYDelta(),\n\t\t\t@\"uiSpinbox top edge stepper\")];\n\t\t[self addConstraint:uiprivMkConstraint(self->stepper, NSLayoutAttributeBottom,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tself, NSLayoutAttributeBottom,\n\t\t\t1, stepperYDelta(),\n\t\t\t@\"uiSpinbox bottom edge stepper\")];\n\t\t[self addConstraint:uiprivMkConstraint(self->tf, NSLayoutAttributeTrailing,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tself->stepper, NSLayoutAttributeLeading,\n\t\t\t1, -3,\t\t// arbitrary amount; good enough visually (and it seems to match NSDatePicker too, at least on 10.11, which is even better)\n\t\t\t@\"uiSpinbox space between text field and stepper\")];\n\n\t\tself->spinbox = sb;\n\t}\n\treturn self;\n}\n\n- (void)dealloc\n{\n\t[self->tf setDelegate:nil];\n\t[self->tf removeFromSuperview];\n\t[self->tf release];\n\t[self->formatter release];\n\t[self->stepper setTarget:nil];\n\t[self->stepper removeFromSuperview];\n\t[self->stepper release];\n\t[super dealloc];\n}\n\n- (NSInteger)libui_value\n{\n\treturn self->value;\n}\n\n- (void)libui_setValue:(NSInteger)val\n{\n\tself->value = val;\n\tif (self->value < self->minimum)\n\t\tself->value = self->minimum;\n\tif (self->value > self->maximum)\n\t\tself->value = self->maximum;\n\t[self->tf setIntegerValue:self->value];\n\t[self->stepper setIntegerValue:self->value];\n}\n\n- (void)setMinimum:(NSInteger)min\n{\n\tself->minimum = min;\n\t[self->formatter setMinimum:[NSNumber numberWithInteger:self->minimum]];\n\t[self->stepper setMinValue:((double) (self->minimum))];\n}\n\n- (void)setMaximum:(NSInteger)max\n{\n\tself->maximum = max;\n\t[self->formatter setMaximum:[NSNumber numberWithInteger:self->maximum]];\n\t[self->stepper setMaxValue:((double) (self->maximum))];\n}\n\n- (IBAction)stepperClicked:(id)sender\n{\n\t[self libui_setValue:[self->stepper integerValue]];\n\t(*(self->spinbox->onChanged))(self->spinbox, self->spinbox->onChangedData);\n}\n\n- (void)controlTextDidChange:(NSNotification *)note\n{\n\t[self libui_setValue:[self->tf integerValue]];\n\t(*(self->spinbox->onChanged))(self->spinbox, self->spinbox->onChangedData);\n}\n\n@end\n\nuiDarwinControlAllDefaults(uiSpinbox, spinbox)\n\nint uiSpinboxValue(uiSpinbox *s)\n{\n\treturn [s->spinbox libui_value];\n}\n\nvoid uiSpinboxSetValue(uiSpinbox *s, int value)\n{\n\t[s->spinbox libui_setValue:value];\n}\n\nvoid uiSpinboxOnChanged(uiSpinbox *s, void (*f)(uiSpinbox *, void *), void *data)\n{\n\ts->onChanged = f;\n\ts->onChangedData = data;\n}\n\nstatic void defaultOnChanged(uiSpinbox *s, void *data)\n{\n\t// do nothing\n}\n\nuiSpinbox *uiNewSpinbox(int min, int max)\n{\n\tuiSpinbox *s;\n\tint temp;\n\n\tif (min >= max) {\n\t\ttemp = min;\n\t\tmin = max;\n\t\tmax = temp;\n\t}\n\n\tuiDarwinNewControl(uiSpinbox, s);\n\n\ts->spinbox = [[libui_spinbox alloc] initWithFrame:NSZeroRect spinbox:s];\n\t[s->spinbox setMinimum:min];\n\t[s->spinbox setMaximum:max];\n\t[s->spinbox libui_setValue:min];\n\n\tuiSpinboxOnChanged(s, defaultOnChanged, NULL);\n\n\treturn s;\n}\n"
  },
  {
    "path": "darwin/stddialogs.m",
    "content": "// 26 june 2015\n#import \"uipriv_darwin.h\"\n\n// LONGTERM restructure this whole file\n// LONGTERM explicitly document this works as we want\n// LONGTERM note that font and color buttons also do this\n\n#define windowWindow(w) ((NSWindow *) uiControlHandle(uiControl(w)))\n\n// source of code modal logic: http://stackoverflow.com/questions/604768/wait-for-nsalert-beginsheetmodalforwindow\n\n// note: whether extensions are actually shown depends on a user setting in Finder; we can't control it here\nstatic void setupSavePanel(NSSavePanel *s)\n{\n\t[s setCanCreateDirectories:YES];\n\t[s setShowsHiddenFiles:YES];\n\t[s setExtensionHidden:NO];\n\t[s setCanSelectHiddenExtension:NO];\n\t[s setTreatsFilePackagesAsDirectories:YES];\n}\n\nstatic char *runSavePanel(NSWindow *parent, NSSavePanel *s)\n{\n\tchar *filename;\n\n\t[s beginSheetModalForWindow:parent completionHandler:^(NSInteger result) {\n\t\t[uiprivNSApp() stopModalWithCode:result];\n\t}];\n\tif ([uiprivNSApp() runModalForWindow:s] != NSFileHandlingPanelOKButton)\n\t\treturn NULL;\n\tfilename = uiDarwinNSStringToText([[s URL] path]);\n\treturn filename;\n}\n\nchar *uiOpenFile(uiWindow *parent)\n{\n\tNSOpenPanel *o;\n\n\to = [NSOpenPanel openPanel];\n\t[o setCanChooseFiles:YES];\n\t[o setCanChooseDirectories:NO];\n\t[o setResolvesAliases:NO];\n\t[o setAllowsMultipleSelection:NO];\n\tsetupSavePanel(o);\n\t// panel is autoreleased\n\treturn runSavePanel(windowWindow(parent), o);\n}\n\nchar *uiSaveFile(uiWindow *parent)\n{\n\tNSSavePanel *s;\n\n\ts = [NSSavePanel savePanel];\n\tsetupSavePanel(s);\n\t// panel is autoreleased\n\treturn runSavePanel(windowWindow(parent), s);\n}\n\n// I would use a completion handler for NSAlert as well, but alas NSAlert's are 10.9 and higher only\n@interface libuiCodeModalAlertPanel : NSObject {\n\tNSAlert *panel;\n\tNSWindow *parent;\n}\n- (id)initWithPanel:(NSAlert *)p parent:(NSWindow *)w;\n- (NSInteger)run;\n- (void)panelEnded:(NSAlert *)panel result:(NSInteger)result data:(void *)data;\n@end\n\n@implementation libuiCodeModalAlertPanel\n\n- (id)initWithPanel:(NSAlert *)p parent:(NSWindow *)w\n{\n\tself = [super init];\n\tif (self) {\n\t\tself->panel = p;\n\t\tself->parent = w;\n\t}\n\treturn self;\n}\n\n- (NSInteger)run\n{\n\t[self->panel beginSheetModalForWindow:self->parent\n\t\tmodalDelegate:self\n\t\tdidEndSelector:@selector(panelEnded:result:data:)\n\t\tcontextInfo:NULL];\n\treturn [uiprivNSApp() runModalForWindow:[self->panel window]];\n}\n\n- (void)panelEnded:(NSAlert *)panel result:(NSInteger)result data:(void *)data\n{\n\t[uiprivNSApp() stopModalWithCode:result];\n}\n\n@end\n\nstatic void msgbox(NSWindow *parent, const char *title, const char *description, NSAlertStyle style)\n{\n\tNSAlert *a;\n\tlibuiCodeModalAlertPanel *cm;\n\n\ta = [NSAlert new];\n\t[a setAlertStyle:style];\n\t[a setShowsHelp:NO];\n\t[a setShowsSuppressionButton:NO];\n\t[a setMessageText:uiprivToNSString(title)];\n\t[a setInformativeText:uiprivToNSString(description)];\n\t[a addButtonWithTitle:@\"OK\"];\n\tcm = [[libuiCodeModalAlertPanel alloc] initWithPanel:a parent:parent];\n\t[cm run];\n\t[cm release];\n\t[a release];\n}\n\nvoid uiMsgBox(uiWindow *parent, const char *title, const char *description)\n{\n\tmsgbox(windowWindow(parent), title, description, NSInformationalAlertStyle);\n}\n\nvoid uiMsgBoxError(uiWindow *parent, const char *title, const char *description)\n{\n\tmsgbox(windowWindow(parent), title, description, NSCriticalAlertStyle);\n}\n"
  },
  {
    "path": "darwin/tab.m",
    "content": "// 15 august 2015\n#import \"uipriv_darwin.h\"\n\n// TODO need to jiggle on tab change too (second page disabled tab label initially ambiguous)\n\n@interface tabPage : NSObject {\n\tuiprivSingleChildConstraints constraints;\n\tint margined;\n\tNSView *view;\t\t// the NSTabViewItem view itself\n\tNSObject *pageID;\n}\n@property uiControl *c;\n@property NSLayoutPriority oldHorzHuggingPri;\n@property NSLayoutPriority oldVertHuggingPri;\n- (id)initWithView:(NSView *)v pageID:(NSObject *)o;\n- (NSView *)childView;\n- (void)establishChildConstraints;\n- (void)removeChildConstraints;\n- (int)isMargined;\n- (void)setMargined:(int)m;\n@end\n\nstruct uiTab {\n\tuiDarwinControl c;\n\tNSTabView *tabview;\n\tNSMutableArray *pages;\n\tNSLayoutPriority horzHuggingPri;\n\tNSLayoutPriority vertHuggingPri;\n};\n\n@implementation tabPage\n\n- (id)initWithView:(NSView *)v pageID:(NSObject *)o\n{\n\tself = [super init];\n\tif (self != nil) {\n\t\tself->view = [v retain];\n\t\tself->pageID = [o retain];\n\t}\n\treturn self;\n}\n\n- (void)dealloc\n{\n\t[self removeChildConstraints];\n\t[self->view release];\n\t[self->pageID release];\n\t[super dealloc];\n}\n\n- (NSView *)childView\n{\n\treturn (NSView *) uiControlHandle(self.c);\n}\n\n- (void)establishChildConstraints\n{\n\t[self removeChildConstraints];\n\tif (self.c == NULL)\n\t\treturn;\n\tuiprivSingleChildConstraintsEstablish(&(self->constraints),\n\t\tself->view, [self childView],\n\t\tuiDarwinControlHugsTrailingEdge(uiDarwinControl(self.c)),\n\t\tuiDarwinControlHugsBottom(uiDarwinControl(self.c)),\n\t\tself->margined,\n\t\t@\"uiTab page\");\n}\n\n- (void)removeChildConstraints\n{\n\tuiprivSingleChildConstraintsRemove(&(self->constraints), self->view);\n}\n\n- (int)isMargined\n{\n\treturn self->margined;\n}\n\n- (void)setMargined:(int)m\n{\n\tself->margined = m;\n\tuiprivSingleChildConstraintsSetMargined(&(self->constraints), self->margined);\n}\n\n@end\n\nstatic void uiTabDestroy(uiControl *c)\n{\n\tuiTab *t = uiTab(c);\n\ttabPage *page;\n\n\t// first remove all tab pages so we can destroy all the children\n\twhile ([t->tabview numberOfTabViewItems] != 0)\n\t\t[t->tabview removeTabViewItem:[t->tabview tabViewItemAtIndex:0]];\n\t// then destroy all the children\n\tfor (page in t->pages) {\n\t\t[page removeChildConstraints];\n\t\tuiControlSetParent(page.c, NULL);\n\t\tuiDarwinControlSetSuperview(uiDarwinControl(page.c), nil);\n\t\tuiControlDestroy(page.c);\n\t}\n\t// and finally destroy ourselves\n\t[t->pages release];\n\t[t->tabview release];\n\tuiFreeControl(uiControl(t));\n}\n\nuiDarwinControlDefaultHandle(uiTab, tabview)\nuiDarwinControlDefaultParent(uiTab, tabview)\nuiDarwinControlDefaultSetParent(uiTab, tabview)\nuiDarwinControlDefaultToplevel(uiTab, tabview)\nuiDarwinControlDefaultVisible(uiTab, tabview)\nuiDarwinControlDefaultShow(uiTab, tabview)\nuiDarwinControlDefaultHide(uiTab, tabview)\nuiDarwinControlDefaultEnabled(uiTab, tabview)\nuiDarwinControlDefaultEnable(uiTab, tabview)\nuiDarwinControlDefaultDisable(uiTab, tabview)\n\nstatic void uiTabSyncEnableState(uiDarwinControl *c, int enabled)\n{\n\tuiTab *t = uiTab(c);\n\ttabPage *page;\n\n\tif (uiDarwinShouldStopSyncEnableState(uiDarwinControl(t), enabled))\n\t\treturn;\n\tfor (page in t->pages)\n\t\tuiDarwinControlSyncEnableState(uiDarwinControl(page.c), enabled);\n}\n\nuiDarwinControlDefaultSetSuperview(uiTab, tabview)\n\nstatic void tabRelayout(uiTab *t)\n{\n\ttabPage *page;\n\n\tfor (page in t->pages)\n\t\t[page establishChildConstraints];\n\t// and this gets rid of some weird issues with regards to box alignment\n\tuiprivJiggleViewLayout(t->tabview);\n}\n\nBOOL uiTabHugsTrailingEdge(uiDarwinControl *c)\n{\n\tuiTab *t = uiTab(c);\n\n\treturn t->horzHuggingPri < NSLayoutPriorityWindowSizeStayPut;\n}\n\nBOOL uiTabHugsBottom(uiDarwinControl *c)\n{\n\tuiTab *t = uiTab(c);\n\n\treturn t->vertHuggingPri < NSLayoutPriorityWindowSizeStayPut;\n}\n\nstatic void uiTabChildEdgeHuggingChanged(uiDarwinControl *c)\n{\n\tuiTab *t = uiTab(c);\n\n\ttabRelayout(t);\n}\n\nstatic NSLayoutPriority uiTabHuggingPriority(uiDarwinControl *c, NSLayoutConstraintOrientation orientation)\n{\n\tuiTab *t = uiTab(c);\n\n\tif (orientation == NSLayoutConstraintOrientationHorizontal)\n\t\treturn t->horzHuggingPri;\n\treturn t->vertHuggingPri;\n}\n\nstatic void uiTabSetHuggingPriority(uiDarwinControl *c, NSLayoutPriority priority, NSLayoutConstraintOrientation orientation)\n{\n\tuiTab *t = uiTab(c);\n\n\tif (orientation == NSLayoutConstraintOrientationHorizontal)\n\t\tt->horzHuggingPri = priority;\n\telse\n\t\tt->vertHuggingPri = priority;\n\tuiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(t));\n}\n\nstatic void uiTabChildVisibilityChanged(uiDarwinControl *c)\n{\n\tuiTab *t = uiTab(c);\n\n\ttabRelayout(t);\n}\n\nvoid uiTabAppend(uiTab *t, const char *name, uiControl *child)\n{\n\tuiTabInsertAt(t, name, [t->pages count], child);\n}\n\nvoid uiTabInsertAt(uiTab *t, const char *name, int n, uiControl *child)\n{\n\ttabPage *page;\n\tNSView *view;\n\tNSTabViewItem *i;\n\tNSObject *pageID;\n\n\tuiControlSetParent(child, uiControl(t));\n\n\tview = [[[NSView alloc] initWithFrame:NSZeroRect] autorelease];\n\t// note: if we turn off the autoresizing mask, nothing shows up\n\tuiDarwinControlSetSuperview(uiDarwinControl(child), view);\n\tuiDarwinControlSyncEnableState(uiDarwinControl(child), uiControlEnabledToUser(uiControl(t)));\n\n\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\n\tpageID = [NSObject new];\n\tpage = [[[tabPage alloc] initWithView:view pageID:pageID] autorelease];\n\tpage.c = child;\n\n\t// don't hug, just in case we're a stretchy tab\n\tpage.oldHorzHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(page.c), NSLayoutConstraintOrientationHorizontal);\n\tpage.oldVertHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(page.c), NSLayoutConstraintOrientationVertical);\n\tuiDarwinControlSetHuggingPriority(uiDarwinControl(page.c), NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationHorizontal);\n\tuiDarwinControlSetHuggingPriority(uiDarwinControl(page.c), NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationVertical);\n\n\t[t->pages insertObject:page atIndex:n];\n\n\ti = [[[NSTabViewItem alloc] initWithIdentifier:pageID] autorelease];\n\t[i setLabel:uiprivToNSString(name)];\n\t[i setView:view];\n\t[t->tabview insertTabViewItem:i atIndex:n];\n\n\ttabRelayout(t);\n}\n\nvoid uiTabDelete(uiTab *t, int n)\n{\n\ttabPage *page;\n\tuiControl *child;\n\tNSTabViewItem *i;\n\n\tpage = (tabPage *) [t->pages objectAtIndex:n];\n\n\tuiDarwinControlSetHuggingPriority(uiDarwinControl(page.c), page.oldHorzHuggingPri, NSLayoutConstraintOrientationHorizontal);\n\tuiDarwinControlSetHuggingPriority(uiDarwinControl(page.c), page.oldVertHuggingPri, NSLayoutConstraintOrientationVertical);\n\n\tchild = page.c;\n\t[page removeChildConstraints];\n\t[t->pages removeObjectAtIndex:n];\n\n\tuiControlSetParent(child, NULL);\n\tuiDarwinControlSetSuperview(uiDarwinControl(child), nil);\n\n\ti = [t->tabview tabViewItemAtIndex:n];\n\t[t->tabview removeTabViewItem:i];\n\n\ttabRelayout(t);\n}\n\nint uiTabNumPages(uiTab *t)\n{\n\treturn [t->pages count];\n}\n\nint uiTabMargined(uiTab *t, int n)\n{\n\ttabPage *page;\n\n\tpage = (tabPage *) [t->pages objectAtIndex:n];\n\treturn [page isMargined];\n}\n\nvoid uiTabSetMargined(uiTab *t, int n, int margined)\n{\n\ttabPage *page;\n\n\tpage = (tabPage *) [t->pages objectAtIndex:n];\n\t[page setMargined:margined];\n}\n\nuiTab *uiNewTab(void)\n{\n\tuiTab *t;\n\n\tuiDarwinNewControl(uiTab, t);\n\n\tt->tabview = [[NSTabView alloc] initWithFrame:NSZeroRect];\n\t// also good for NSTabView (same selector and everything)\n\tuiDarwinSetControlFont((NSControl *) (t->tabview), NSRegularControlSize);\n\n\tt->pages = [NSMutableArray new];\n\n\t// default to low hugging to not hug edges\n\tt->horzHuggingPri = NSLayoutPriorityDefaultLow;\n\tt->vertHuggingPri = NSLayoutPriorityDefaultLow;\n\n\treturn t;\n}\n"
  },
  {
    "path": "darwin/table.h",
    "content": "// 3 june 2018\n#import \"../common/table.h\"\n\n// table.m\n// TODO get rid of forward declaration\n@class uiprivTableModel;\nstruct uiTableModel {\n\tuiTableModelHandler *mh;\n\tuiprivTableModel *m;\n\tNSMutableArray *tables;\n};\nstruct uiTable {\n\tuiDarwinControl c;\n\tNSScrollView *sv;\n\tNSTableView *tv;\n\tuiprivScrollViewData *d;\n\tint backgroundColumn;\n\tuiTableModel *m;\n};\n\n// tablecolumn.m\n@interface uiprivTableCellView : NSTableCellView\n- (void)uiprivUpdate:(NSInteger)row;\n@end\n@interface uiprivTableColumn : NSTableColumn\n- (uiprivTableCellView *)uiprivMakeCellView;\n@end\n"
  },
  {
    "path": "darwin/table.m",
    "content": "// 3 june 2018\n#import \"uipriv_darwin.h\"\n#import \"table.h\"\n\n// TODO is the initial scroll position still wrong?\n\n@interface uiprivTableModel : NSObject<NSTableViewDataSource, NSTableViewDelegate> {\n\tuiTableModel *m;\n}\n- (id)initWithModel:(uiTableModel *)model;\n@end\n\n// TODO we really need to clean up the sharing of the table and model variables...\n@interface uiprivTableView : NSTableView {\n\tuiTable *uiprivT;\n\tuiTableModel *uiprivM;\n}\n- (id)initWithFrame:(NSRect)r uiprivT:(uiTable *)t uiprivM:(uiTableModel *)m;\n@end\n\n@implementation uiprivTableView\n\n- (id)initWithFrame:(NSRect)r uiprivT:(uiTable *)t uiprivM:(uiTableModel *)m\n{\n\tself = [super initWithFrame:r];\n\tif (self) {\n\t\tself->uiprivT = t;\n\t\tself->uiprivM = m;\n\t}\n\treturn self;\n}\n\n// TODO is this correct for overflow scrolling?\nstatic void setBackgroundColor(uiprivTableView *t, NSTableRowView *rv, NSInteger row)\n{\n\tNSColor *color;\n\tdouble r, g, b, a;\n\n\tif (t->uiprivT->backgroundColumn == -1)\n\t\treturn;\t\t// let Cocoa do its default thing\n\tif (uiprivTableModelColorIfProvided(t->uiprivM, row, t->uiprivT->backgroundColumn, &r, &g, &b, &a))\n\t\tcolor = [NSColor colorWithSRGBRed:r green:g blue:b alpha:a];\n\telse {\n\t\tNSArray *colors;\n\t\tNSInteger index;\n\n\t\t// this usage is primarily a guess; hopefully it is correct for the non-two color case... (TODO)\n\t\t// 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)\n\t\tcolors = [NSColor controlAlternatingRowBackgroundColors];\n\t\tindex = row % [colors count];\n\t\tcolor = (NSColor *) [colors objectAtIndex:index];\n\t}\n\t[rv setBackgroundColor:color];\n\t// color is autoreleased in all cases\n}\n\n@end\n\n@implementation uiprivTableModel\n\n- (id)initWithModel:(uiTableModel *)model\n{\n\tself = [super init];\n\tif (self)\n\t\tself->m = model;\n\treturn self;\n}\n\n- (NSInteger)numberOfRowsInTableView:(NSTableView *)tv\n{\n\treturn uiprivTableModelNumRows(self->m);\n}\n\n - (NSView *)tableView:(NSTableView *)tv viewForTableColumn:(NSTableColumn *)cc row:(NSInteger)row\n{\n\tuiprivTableColumn *c = (uiprivTableColumn *) cc;\n\tuiprivTableCellView *cv;\n\n\tcv = (uiprivTableCellView *) [tv makeViewWithIdentifier:[c identifier] owner:self];\n\tif (cv == nil)\n\t\tcv = [c uiprivMakeCellView];\n\t[cv uiprivUpdate:row];\n\treturn cv;\n}\n\n- (void)tableView:(NSTableView *)tv didAddRowView:(NSTableRowView *)rv forRow:(NSInteger)row\n{\n\tsetBackgroundColor((uiprivTableView *) tv, rv, row);\n}\n\n@end\n\nuiTableModel *uiNewTableModel(uiTableModelHandler *mh)\n{\n\tuiTableModel *m;\n\n\tm = uiprivNew(uiTableModel);\n\tm->mh = mh;\n\tm->m = [[uiprivTableModel alloc] initWithModel:m];\n\tm->tables = [NSMutableArray new];\n\treturn m;\n}\n\nvoid uiFreeTableModel(uiTableModel *m)\n{\n\tif ([m->tables count] != 0)\n\t\tuiprivUserBug(\"You cannot free a uiTableModel while uiTables are using it.\");\n\t[m->tables release];\n\t[m->m release];\n\tuiprivFree(m);\n}\n\nvoid uiTableModelRowInserted(uiTableModel *m, int newIndex)\n{\n\tNSTableView *tv;\n\tNSIndexSet *set;\n\n\tset = [NSIndexSet indexSetWithIndex:newIndex];\n\tfor (tv in m->tables)\n\t\t[tv insertRowsAtIndexes:set withAnimation:NSTableViewAnimationEffectNone];\n\t// set is autoreleased\n}\n\nvoid uiTableModelRowChanged(uiTableModel *m, int index)\n{\n\tuiprivTableView *tv;\n\tNSTableRowView *rv;\n\tNSUInteger i, n;\n\tuiprivTableCellView *cv;\n\n\tfor (tv in m->tables) {\n\t\trv = [tv rowViewAtRow:index makeIfNecessary:NO];\n\t\tif (rv != nil)\n\t\t\tsetBackgroundColor(tv, rv, index);\n\t\tn = [[tv tableColumns] count];\n\t\tfor (i = 0; i < n; i++) {\n\t\t\tcv = (uiprivTableCellView *) [tv viewAtColumn:i row:index makeIfNecessary:NO];\n\t\t\tif (cv != nil)\n\t\t\t\t[cv uiprivUpdate:index];\n\t\t}\n\t}\n}\n\nvoid uiTableModelRowDeleted(uiTableModel *m, int oldIndex)\n{\n\tNSTableView *tv;\n\tNSIndexSet *set;\n\n\tset = [NSIndexSet indexSetWithIndex:oldIndex];\n\tfor (tv in m->tables)\n\t\t[tv removeRowsAtIndexes:set withAnimation:NSTableViewAnimationEffectNone];\n\t// set is autoreleased\n}\n\nuiTableModelHandler *uiprivTableModelHandler(uiTableModel *m)\n{\n\treturn m->mh;\n}\n\nuiDarwinControlAllDefaultsExceptDestroy(uiTable, sv)\n\nstatic void uiTableDestroy(uiControl *c)\n{\n\tuiTable *t = uiTable(c);\n\n\t[t->m->tables removeObject:t->tv];\n\tuiprivScrollViewFreeData(t->sv, t->d);\n\t[t->tv release];\n\t[t->sv release];\n\tuiFreeControl(uiControl(t));\n}\n\nuiTable *uiNewTable(uiTableParams *p)\n{\n\tuiTable *t;\n\tuiprivScrollViewCreateParams sp;\n\n\tuiDarwinNewControl(uiTable, t);\n\tt->m = p->Model;\n\tt->backgroundColumn = p->RowBackgroundColorModelColumn;\n\n\tt->tv = [[uiprivTableView alloc] initWithFrame:NSZeroRect uiprivT:t uiprivM:t->m];\n\n\t[t->tv setDataSource:t->m->m];\n\t[t->tv setDelegate:t->m->m];\n\t[t->tv reloadData];\n\t[t->m->tables addObject:t->tv];\n\n\t// TODO is this sufficient?\n\t[t->tv setAllowsColumnReordering:NO];\n\t[t->tv setAllowsColumnResizing:YES];\n\t[t->tv setAllowsMultipleSelection:NO];\n\t[t->tv setAllowsEmptySelection:YES];\n\t[t->tv setAllowsColumnSelection:NO];\n\t[t->tv setUsesAlternatingRowBackgroundColors:YES];\n\t[t->tv setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleRegular];\n\t[t->tv setGridStyleMask:NSTableViewGridNone];\n\t[t->tv setAllowsTypeSelect:YES];\n\t// TODO floatsGroupRows — do we even allow group rows?\n\n\tmemset(&sp, 0, sizeof (uiprivScrollViewCreateParams));\n\tsp.DocumentView = t->tv;\n\t// this is what Interface Builder sets it to\n\t// TODO verify\n\tsp.BackgroundColor = [NSColor colorWithCalibratedWhite:1.0 alpha:1.0];\n\tsp.DrawsBackground = YES;\n\tsp.Bordered = YES;\n\tsp.HScroll = YES;\n\tsp.VScroll = YES;\n\tt->sv = uiprivMkScrollView(&sp, &(t->d));\n\n\t// TODO WHY DOES THIS REMOVE ALL GRAPHICAL GLITCHES?\n\t// 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)\n\t// I don't get layer-backing... am I supposed to layer-back EVERYTHING manually? I need to check Interface Builder again...\n\t[t->sv setWantsLayer:YES];\n\n\treturn t;\n}\n"
  },
  {
    "path": "darwin/tablecolumn.m",
    "content": "// 3 june 2018\n#import \"uipriv_darwin.h\"\n#import \"table.h\"\n\n// values from interface builder\n#define textColumnLeading 2\n#define textColumnTrailing 2\n#define imageColumnLeading 3\n#define imageTextColumnLeading 7\n#define checkboxTextColumnLeading 0\n// these aren't provided by IB; let's just choose one\n#define checkboxColumnLeading imageColumnLeading\n#define progressBarColumnLeading imageColumnLeading\n#define progressBarColumnTrailing progressBarColumnLeading\n#define buttonColumnLeading imageColumnLeading\n#define buttonColumnTrailing buttonColumnLeading\n\n@implementation uiprivTableCellView\n\n- (void)uiprivUpdate:(NSInteger)row\n{\n\t[self doesNotRecognizeSelector:_cmd];\n}\n\n@end\n\n@implementation uiprivTableColumn\n\n- (uiprivTableCellView *)uiprivMakeCellView\n{\n\t[self doesNotRecognizeSelector:_cmd];\n\treturn nil;\t\t\t// appease compiler\n}\n\n@end\n\nstruct textColumnCreateParams {\n\tuiTable *t;\n\tuiTableModel *m;\n\n\tBOOL makeTextField;\n\tint textModelColumn;\n\tint textEditableModelColumn;\n\tuiTableTextColumnOptionalParams textParams;\n\n\tBOOL makeImageView;\n\tint imageModelColumn;\n\n\tBOOL makeCheckbox;\n\tint checkboxModelColumn;\n\tint checkboxEditableModelColumn;\n};\n\n@interface uiprivTextImageCheckboxTableCellView : uiprivTableCellView {\n\tuiTable *t;\n\tuiTableModel *m;\n\n\tNSTextField *tf;\n\tint textModelColumn;\n\tint textEditableModelColumn;\n\tuiTableTextColumnOptionalParams textParams;\n\n\tNSImageView *iv;\n\tint imageModelColumn;\n\n\tNSButton *cb;\n\tint checkboxModelColumn;\n\tint checkboxEditableModelColumn;\n}\n- (id)initWithFrame:(NSRect)r params:(struct textColumnCreateParams *)p;\n- (IBAction)uiprivOnTextFieldAction:(id)sender;\n- (IBAction)uiprivOnCheckboxAction:(id)sender;\n@end\n\n@implementation uiprivTextImageCheckboxTableCellView\n\n- (id)initWithFrame:(NSRect)r params:(struct textColumnCreateParams *)p\n{\n\tself = [super initWithFrame:r];\n\tif (self) {\n\t\tNSMutableArray *constraints;\n\n\t\tself->t = p->t;\n\t\tself->m = p->m;\n\t\tconstraints = [NSMutableArray new];\n\n\t\tself->tf = nil;\n\t\tif (p->makeTextField) {\n\t\t\tself->textModelColumn = p->textModelColumn;\n\t\t\tself->textEditableModelColumn = p->textEditableModelColumn;\n\t\t\tself->textParams = p->textParams;\n\n\t\t\tself->tf = uiprivNewLabel(@\"\");\n\t\t\t// TODO set wrap and ellipsize modes?\n\t\t\t[self->tf setTarget:self];\n\t\t\t[self->tf setAction:@selector(uiprivOnTextFieldAction:)];\n\t\t\t[self->tf setTranslatesAutoresizingMaskIntoConstraints:NO];\n\t\t\t[self addSubview:self->tf];\n\n\t\t\t// TODO for all three controls: set hugging and compression resistance properly\n\t\t\t[constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeLeading,\n\t\t\t\tNSLayoutRelationEqual,\n\t\t\t\tself->tf, NSLayoutAttributeLeading,\n\t\t\t\t1, -textColumnLeading,\n\t\t\t\t@\"uiTable cell text leading constraint\")];\n\t\t\t[constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeTop,\n\t\t\t\tNSLayoutRelationEqual,\n\t\t\t\tself->tf, NSLayoutAttributeTop,\n\t\t\t\t1, 0,\n\t\t\t\t@\"uiTable cell text top constraint\")];\n\t\t\t[constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeTrailing,\n\t\t\t\tNSLayoutRelationEqual,\n\t\t\t\tself->tf, NSLayoutAttributeTrailing,\n\t\t\t\t1, textColumnTrailing,\n\t\t\t\t@\"uiTable cell text trailing constraint\")];\n\t\t\t[constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeBottom,\n\t\t\t\tNSLayoutRelationEqual,\n\t\t\t\tself->tf, NSLayoutAttributeBottom,\n\t\t\t\t1, 0,\n\t\t\t\t@\"uiTable cell text bottom constraint\")];\n\t\t}\n\n\t\tself->iv = nil;\n\t\tif (p->makeImageView) {\n\t\t\tself->imageModelColumn = p->imageModelColumn;\n\n\t\t\tself->iv = [[NSImageView alloc] initWithFrame:NSZeroRect];\n\t\t\t[self->iv setImageFrameStyle:NSImageFrameNone];\n\t\t\t[self->iv setImageAlignment:NSImageAlignCenter];\n\t\t\t[self->iv setImageScaling:NSImageScaleProportionallyDown];\n\t\t\t[self->iv setAnimates:NO];\n\t\t\t[self->iv setEditable:NO];\n\t\t\t[self->iv setTranslatesAutoresizingMaskIntoConstraints:NO];\n\t\t\t[self addSubview:self->iv];\n\n\t\t\t[constraints addObject:uiprivMkConstraint(self->iv, NSLayoutAttributeWidth,\n\t\t\t\tNSLayoutRelationEqual,\n\t\t\t\tself->iv, NSLayoutAttributeHeight,\n\t\t\t\t1, 0,\n\t\t\t\t@\"uiTable image squareness constraint\")];\n\t\t\tif (self->tf != nil) {\n\t\t\t\t[constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeLeading,\n\t\t\t\t\tNSLayoutRelationEqual,\n\t\t\t\t\tself->iv, NSLayoutAttributeLeading,\n\t\t\t\t\t1, -imageColumnLeading,\n\t\t\t\t\t@\"uiTable cell image leading constraint\")];\n\t\t\t\t[constraints replaceObjectAtIndex:0\n\t\t\t\t\twithObject:uiprivMkConstraint(self->iv, NSLayoutAttributeTrailing,\n\t\t\t\t\t\tNSLayoutRelationEqual,\n\t\t\t\t\t\tself->tf, NSLayoutAttributeLeading,\n\t\t\t\t\t\t1, -imageTextColumnLeading,\n\t\t\t\t\t\t@\"uiTable cell image-text spacing constraint\")];\n\t\t\t} else\n\t\t\t\t[constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeCenterX,\n\t\t\t\t\tNSLayoutRelationEqual,\n\t\t\t\t\tself->iv, NSLayoutAttributeCenterX,\n\t\t\t\t\t1, 0,\n\t\t\t\t\t@\"uiTable cell image centering constraint\")];\n\t\t\t[constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeTop,\n\t\t\t\tNSLayoutRelationEqual,\n\t\t\t\tself->iv, NSLayoutAttributeTop,\n\t\t\t\t1, 0,\n\t\t\t\t@\"uiTable cell image top constraint\")];\n\t\t\t[constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeBottom,\n\t\t\t\tNSLayoutRelationEqual,\n\t\t\t\tself->iv, NSLayoutAttributeBottom,\n\t\t\t\t1, 0,\n\t\t\t\t@\"uiTable cell image bottom constraint\")];\n\t\t}\n\n\t\tself->cb = nil;\n\t\tif (p->makeCheckbox) {\n\t\t\tself->checkboxModelColumn = p->checkboxModelColumn;\n\t\t\tself->checkboxEditableModelColumn = p->checkboxEditableModelColumn;\n\n\t\t\tself->cb = [[NSButton alloc] initWithFrame:NSZeroRect];\n\t\t\t[self->cb setTitle:@\"\"];\n\t\t\t[self->cb setButtonType:NSSwitchButton];\n\t\t\t// doesn't seem to have an associated bezel style\n\t\t\t[self->cb setBordered:NO];\n\t\t\t[self->cb setTransparent:NO];\n\t\t\tuiDarwinSetControlFont(self->cb, NSRegularControlSize);\n\t\t\t[self->cb setTranslatesAutoresizingMaskIntoConstraints:NO];\n\t\t\t[self addSubview:self->cb];\n\n\t\t\tif (self->tf != nil) {\n\t\t\t\t[constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeLeading,\n\t\t\t\t\tNSLayoutRelationEqual,\n\t\t\t\t\tself->cb, NSLayoutAttributeLeading,\n\t\t\t\t\t1, -imageColumnLeading,\n\t\t\t\t\t@\"uiTable cell checkbox leading constraint\")];\n\t\t\t\t[constraints replaceObjectAtIndex:0\n\t\t\t\t\twithObject:uiprivMkConstraint(self->cb, NSLayoutAttributeTrailing,\n\t\t\t\t\t\tNSLayoutRelationEqual,\n\t\t\t\t\t\tself->tf, NSLayoutAttributeLeading,\n\t\t\t\t\t\t1, -imageTextColumnLeading,\n\t\t\t\t\t\t@\"uiTable cell checkbox-text spacing constraint\")];\n\t\t\t} else\n\t\t\t\t[constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeCenterX,\n\t\t\t\t\tNSLayoutRelationEqual,\n\t\t\t\t\tself->cb, NSLayoutAttributeCenterX,\n\t\t\t\t\t1, 0,\n\t\t\t\t\t@\"uiTable cell checkbox centering constraint\")];\n\t\t\t[constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeTop,\n\t\t\t\tNSLayoutRelationEqual,\n\t\t\t\tself->cb, NSLayoutAttributeTop,\n\t\t\t\t1, 0,\n\t\t\t\t@\"uiTable cell checkbox top constraint\")];\n\t\t\t[constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeBottom,\n\t\t\t\tNSLayoutRelationEqual,\n\t\t\t\tself->cb, NSLayoutAttributeBottom,\n\t\t\t\t1, 0,\n\t\t\t\t@\"uiTable cell checkbox bottom constraint\")];\n\t\t}\n\n\t\t[self addConstraints:constraints];\n\n\t\t// take advantage of NSTableCellView-provided accessibility features\n\t\tif (self->tf != nil)\n\t\t\t[self setTextField:self->tf];\n\t\tif (self->iv != nil)\n\t\t\t[self setImageView:self->iv];\n\t}\n\treturn self;\n}\n\n- (void)dealloc\n{\n\tif (self->cb != nil) {\n\t\t[self->cb release];\n\t\tself->cb = nil;\n\t}\n\tif (self->iv != nil) {\n\t\t[self->iv release];\n\t\tself->iv = nil;\n\t}\n\tif (self->tf != nil) {\n\t\t[self->tf release];\n\t\tself->tf = nil;\n\t}\n\t[super dealloc];\n}\n\n- (void)uiprivUpdate:(NSInteger)row\n{\n\tuiTableValue *value;\n\n\tif (self->tf != nil) {\n\t\tNSString *str;\n\t\tNSColor *color;\n\t\tdouble r, g, b, a;\n\n\t\tvalue = uiprivTableModelCellValue(self->m, row, self->textModelColumn);\n\t\tstr = uiprivToNSString(uiTableValueString(value));\n\t\tuiFreeTableValue(value);\n\t\t[self->tf setStringValue:str];\n\n\t\t[self->tf setEditable:uiprivTableModelCellEditable(self->m, row, self->textEditableModelColumn)];\n\n\t\tcolor = [NSColor controlTextColor];\n\t\tif (uiprivTableModelColorIfProvided(self->m, row, self->textParams.ColorModelColumn, &r, &g, &b, &a))\n\t\t\tcolor = [NSColor colorWithSRGBRed:r green:g blue:b alpha:a];\n\t\t[self->tf setTextColor:color];\n\t\t// we don't own color in ether case; don't release\n\t}\n\tif (self->iv != nil) {\n\t\tuiImage *img;\n\n\t\tvalue = uiprivTableModelCellValue(self->m, row, self->imageModelColumn);\n\t\timg = uiTableValueImage(value);\n\t\tuiFreeTableValue(value);\n\t\t[self->iv setImage:uiprivImageNSImage(img)];\n\t}\n\tif (self->cb != nil) {\n\t\tvalue = uiprivTableModelCellValue(self->m, row, self->checkboxModelColumn);\n\t\tif (uiTableValueInt(value) != 0)\n\t\t\t[self->cb setState:NSOnState];\n\t\telse\n\t\t\t[self->cb setState:NSOffState];\n\t\tuiFreeTableValue(value);\n\n\t\t[self->cb setEnabled:uiprivTableModelCellEditable(self->m, row, self->checkboxEditableModelColumn)];\n\t}\n}\n\n- (IBAction)uiprivOnTextFieldAction:(id)sender\n{\n\tNSInteger row;\n\tuiTableValue *value;\n\n\trow = [self->t->tv rowForView:self->tf];\n\tvalue = uiNewTableValueString([[self->tf stringValue] UTF8String]);\n\tuiprivTableModelSetCellValue(self->m, row, self->textModelColumn, value);\n\tuiFreeTableValue(value);\n\t// always refresh the value in case the model rejected it\n\t// TODO document that we do this, but not for the whole row (or decide to do both, or do neither...)\n\t[self uiprivUpdate:row];\n}\n\n- (IBAction)uiprivOnCheckboxAction:(id)sender\n{\n\tNSInteger row;\n\tuiTableValue *value;\n\n\trow = [self->t->tv rowForView:self->cb];\n\tvalue = uiNewTableValueInt([self->cb state] != NSOffState);\n\tuiprivTableModelSetCellValue(self->m, row, self->checkboxModelColumn, value);\n\tuiFreeTableValue(value);\n\t// always refresh the value in case the model rejected it\n\t[self uiprivUpdate:row];\n}\n\n@end\n\n@interface uiprivTextImageCheckboxTableColumn : uiprivTableColumn {\n\tstruct textColumnCreateParams params;\n}\n- (id)initWithIdentifier:(NSString *)ident params:(struct textColumnCreateParams *)p;\n@end\n\n@implementation uiprivTextImageCheckboxTableColumn\n\n- (id)initWithIdentifier:(NSString *)ident params:(struct textColumnCreateParams *)p\n{\n\tself = [super initWithIdentifier:ident];\n\tif (self)\n\t\tself->params = *p;\n\treturn self;\n}\n\n- (uiprivTableCellView *)uiprivMakeCellView\n{\n\tuiprivTableCellView *cv;\n\n\tcv = [[uiprivTextImageCheckboxTableCellView alloc] initWithFrame:NSZeroRect params:&(self->params)];\n\t[cv setIdentifier:[self identifier]];\n\treturn cv;\n}\n\n@end\n\n@interface uiprivProgressBarTableCellView : uiprivTableCellView {\n\tuiTable *t;\n\tuiTableModel *m;\n\tNSProgressIndicator *p;\n\tint modelColumn;\n}\n- (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc;\n@end\n\n@implementation uiprivProgressBarTableCellView\n\n- (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc\n{\n\tself = [super initWithFrame:r];\n\tif (self) {\n\t\tself->t = table;\n\t\tself->m = model;\n\t\tself->modelColumn = mc;\n\n\t\tself->p = [[NSProgressIndicator alloc] initWithFrame:NSZeroRect];\n\t\t[self->p setControlSize:NSRegularControlSize];\n\t\t[self->p setBezeled:YES];\n\t\t[self->p setStyle:NSProgressIndicatorBarStyle];\n\t\t[self->p setTranslatesAutoresizingMaskIntoConstraints:NO];\n\t\t[self addSubview:self->p];\n\n\t\t// TODO set hugging and compression resistance properly\n\t\t[self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeLeading,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tself->p, NSLayoutAttributeLeading,\n\t\t\t1, -progressBarColumnLeading,\n\t\t\t@\"uiTable cell progressbar leading constraint\")];\n\t\t[self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeTop,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tself->p, NSLayoutAttributeTop,\n\t\t\t1, 0,\n\t\t\t@\"uiTable cell progressbar top constraint\")];\n\t\t[self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeTrailing,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tself->p, NSLayoutAttributeTrailing,\n\t\t\t1, progressBarColumnTrailing,\n\t\t\t@\"uiTable cell progressbar trailing constraint\")];\n\t\t[self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeBottom,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tself->p, NSLayoutAttributeBottom,\n\t\t\t1, 0,\n\t\t\t@\"uiTable cell progressbar bottom constraint\")];\n\t}\n\treturn self;\n}\n\n- (void)dealloc\n{\n\t[self->p release];\n\tself->p = nil;\n\t[super dealloc];\n}\n\n- (void)uiprivUpdate:(NSInteger)row\n{\n\tuiTableValue *value;\n\tint progress;\n\n\tvalue = uiprivTableModelCellValue(self->m, row, self->modelColumn);\n\tprogress = uiTableValueInt(value);\n\tuiFreeTableValue(value);\n\tif (progress == -1) {\n\t\t[self->p setIndeterminate:YES];\n\t\t[self->p startAnimation:self->p];\n\t} else if (progress == 100) {\n\t\t[self->p setIndeterminate:NO];\n\t\t[self->p setMaxValue:101];\n\t\t[self->p setDoubleValue:101];\n\t\t[self->p setDoubleValue:100];\n\t\t[self->p setMaxValue:100];\n\t} else {\n\t\t[self->p setIndeterminate:NO];\n\t\t[self->p setDoubleValue:(progress + 1)];\n\t\t[self->p setDoubleValue:progress];\n\t}\n}\n\n@end\n\n@interface uiprivProgressBarTableColumn : uiprivTableColumn {\n\tuiTable *t;\n\t// TODO remove the need for this given t (or make t not require m, one of the two)\n\tuiTableModel *m;\n\tint modelColumn;\n}\n- (id)initWithIdentifier:(NSString *)ident table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc;\n@end\n\n@implementation uiprivProgressBarTableColumn\n\n- (id)initWithIdentifier:(NSString *)ident table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc\n{\n\tself = [super initWithIdentifier:ident];\n\tif (self) {\n\t\tself->t = table;\n\t\tself->m = model;\n\t\tself->modelColumn = mc;\n\t}\n\treturn self;\n}\n\n- (uiprivTableCellView *)uiprivMakeCellView\n{\n\tuiprivTableCellView *cv;\n\n\tcv = [[uiprivProgressBarTableCellView alloc] initWithFrame:NSZeroRect table:self->t model:self->m modelColumn:self->modelColumn];\n\t[cv setIdentifier:[self identifier]];\n\treturn cv;\n}\n\n@end\n\n@interface uiprivButtonTableCellView : uiprivTableCellView {\n\tuiTable *t;\n\tuiTableModel *m;\n\tNSButton *b;\n\tint modelColumn;\n\tint editableColumn;\n}\n- (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc editableColumn:(int)ec;\n- (IBAction)uiprivOnClicked:(id)sender;\n@end\n\n@implementation uiprivButtonTableCellView\n\n- (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc editableColumn:(int)ec\n{\n\tself = [super initWithFrame:r];\n\tif (self) {\n\t\tself->t = table;\n\t\tself->m = model;\n\t\tself->modelColumn = mc;\n\t\tself->editableColumn = ec;\n\n\t\tself->b = [[NSButton alloc] initWithFrame:NSZeroRect];\n\t\t[self->b setButtonType:NSMomentaryPushInButton];\n\t\t[self->b setBordered:YES];\n\t\t[self->b setBezelStyle:NSRoundRectBezelStyle];\n\t\tuiDarwinSetControlFont(self->b, NSRegularControlSize);\n\t\t[self->b setTarget:self];\n\t\t[self->b setAction:@selector(uiprivOnClicked:)];\n\t\t[self->b setTranslatesAutoresizingMaskIntoConstraints:NO];\n\t\t[self addSubview:self->b];\n\n\t\t// TODO set hugging and compression resistance properly\n\t\t[self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeLeading,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tself->b, NSLayoutAttributeLeading,\n\t\t\t1, -buttonColumnLeading,\n\t\t\t@\"uiTable cell button leading constraint\")];\n\t\t[self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeTop,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tself->b, NSLayoutAttributeTop,\n\t\t\t1, 0,\n\t\t\t@\"uiTable cell button top constraint\")];\n\t\t[self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeTrailing,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tself->b, NSLayoutAttributeTrailing,\n\t\t\t1, buttonColumnTrailing,\n\t\t\t@\"uiTable cell button trailing constraint\")];\n\t\t[self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeBottom,\n\t\t\tNSLayoutRelationEqual,\n\t\t\tself->b, NSLayoutAttributeBottom,\n\t\t\t1, 0,\n\t\t\t@\"uiTable cell button bottom constraint\")];\n\t}\n\treturn self;\n}\n\n- (void)dealloc\n{\n\t[self->b release];\n\tself->b = nil;\n\t[super dealloc];\n}\n\n- (void)uiprivUpdate:(NSInteger)row\n{\n\tuiTableValue *value;\n\tNSString *str;\n\n\tvalue = uiprivTableModelCellValue(self->m, row, self->modelColumn);\n\tstr = uiprivToNSString(uiTableValueString(value));\n\tuiFreeTableValue(value);\n\t[self->b setTitle:str];\n\n\t[self->b setEnabled:uiprivTableModelCellEditable(self->m, row, self->editableColumn)];\n}\n\n- (IBAction)uiprivOnClicked:(id)sender\n{\n\tNSInteger row;\n\n\trow = [self->t->tv rowForView:self->b];\n\tuiprivTableModelSetCellValue(self->m, row, self->modelColumn, NULL);\n\t// TODO document we DON'T update the cell after doing this\n\t// TODO or decide what to do instead\n}\n\n@end\n\n@interface uiprivButtonTableColumn : uiprivTableColumn {\n\tuiTable *t;\n\tuiTableModel *m;\n\tint modelColumn;\n\tint editableColumn;\n}\n- (id)initWithIdentifier:(NSString *)ident table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc editableColumn:(int)ec;\n@end\n\n@implementation uiprivButtonTableColumn\n\n- (id)initWithIdentifier:(NSString *)ident table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc editableColumn:(int)ec\n{\n\tself = [super initWithIdentifier:ident];\n\tif (self) {\n\t\tself->t = table;\n\t\tself->m = model;\n\t\tself->modelColumn = mc;\n\t\tself->editableColumn = ec;\n\t}\n\treturn self;\n}\n\n- (uiprivTableCellView *)uiprivMakeCellView\n{\n\tuiprivTableCellView *cv;\n\n\tcv = [[uiprivButtonTableCellView alloc] initWithFrame:NSZeroRect table:self->t model:self->m modelColumn:self->modelColumn editableColumn:self->editableColumn];\n\t[cv setIdentifier:[self identifier]];\n\treturn cv;\n}\n\n@end\n\nvoid uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams)\n{\n\tstruct textColumnCreateParams p;\n\tuiprivTableColumn *col;\n\tNSString *str;\n\n\tmemset(&p, 0, sizeof (struct textColumnCreateParams));\n\tp.t = t;\n\tp.m = t->m;\n\n\tp.makeTextField = YES;\n\tp.textModelColumn = textModelColumn;\n\tp.textEditableModelColumn = textEditableModelColumn;\n\tif (textParams != NULL)\n\t\tp.textParams = *textParams;\n\telse\n\t\tp.textParams = uiprivDefaultTextColumnOptionalParams;\n\n\tstr = [NSString stringWithUTF8String:name];\n\tcol = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p];\n\t[col setTitle:str];\n\t[t->tv addTableColumn:col];\n}\n\nvoid uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn)\n{\n\tstruct textColumnCreateParams p;\n\tuiprivTableColumn *col;\n\tNSString *str;\n\n\tmemset(&p, 0, sizeof (struct textColumnCreateParams));\n\tp.t = t;\n\tp.m = t->m;\n\n\tp.makeImageView = YES;\n\tp.imageModelColumn = imageModelColumn;\n\n\tstr = [NSString stringWithUTF8String:name];\n\tcol = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p];\n\t[col setTitle:str];\n\t[t->tv addTableColumn:col];\n}\n\nvoid uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams)\n{\n\tstruct textColumnCreateParams p;\n\tuiprivTableColumn *col;\n\tNSString *str;\n\n\tmemset(&p, 0, sizeof (struct textColumnCreateParams));\n\tp.t = t;\n\tp.m = t->m;\n\n\tp.makeTextField = YES;\n\tp.textModelColumn = textModelColumn;\n\tp.textEditableModelColumn = textEditableModelColumn;\n\tif (textParams != NULL)\n\t\tp.textParams = *textParams;\n\telse\n\t\tp.textParams = uiprivDefaultTextColumnOptionalParams;\n\n\tp.makeImageView = YES;\n\tp.imageModelColumn = imageModelColumn;\n\n\tstr = [NSString stringWithUTF8String:name];\n\tcol = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p];\n\t[col setTitle:str];\n\t[t->tv addTableColumn:col];\n}\n\nvoid uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn)\n{\n\tstruct textColumnCreateParams p;\n\tuiprivTableColumn *col;\n\tNSString *str;\n\n\tmemset(&p, 0, sizeof (struct textColumnCreateParams));\n\tp.t = t;\n\tp.m = t->m;\n\n\tp.makeCheckbox = YES;\n\tp.checkboxModelColumn = checkboxModelColumn;\n\tp.checkboxEditableModelColumn = checkboxEditableModelColumn;\n\n\tstr = [NSString stringWithUTF8String:name];\n\tcol = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p];\n\t[col setTitle:str];\n\t[t->tv addTableColumn:col];\n}\n\nvoid uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams)\n{\n\tstruct textColumnCreateParams p;\n\tuiprivTableColumn *col;\n\tNSString *str;\n\n\tmemset(&p, 0, sizeof (struct textColumnCreateParams));\n\tp.t = t;\n\tp.m = t->m;\n\n\tp.makeTextField = YES;\n\tp.textModelColumn = textModelColumn;\n\tp.textEditableModelColumn = textEditableModelColumn;\n\tif (textParams != NULL)\n\t\tp.textParams = *textParams;\n\telse\n\t\tp.textParams = uiprivDefaultTextColumnOptionalParams;\n\n\tp.makeCheckbox = YES;\n\tp.checkboxModelColumn = checkboxModelColumn;\n\tp.checkboxEditableModelColumn = checkboxEditableModelColumn;\n\n\tstr = [NSString stringWithUTF8String:name];\n\tcol = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p];\n\t[col setTitle:str];\n\t[t->tv addTableColumn:col];\n}\n\nvoid uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressModelColumn)\n{\n\tuiprivTableColumn *col;\n\tNSString *str;\n\n\tstr = [NSString stringWithUTF8String:name];\n\tcol = [[uiprivProgressBarTableColumn alloc] initWithIdentifier:str table:t model:t->m modelColumn:progressModelColumn];\n\t[col setTitle:str];\n\t[t->tv addTableColumn:col];\n}\n\nvoid uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonModelColumn, int buttonClickableModelColumn)\n{\n\tuiprivTableColumn *col;\n\tNSString *str;\n\n\tstr = [NSString stringWithUTF8String:name];\n\tcol = [[uiprivButtonTableColumn alloc] initWithIdentifier:str table:t model:t->m modelColumn:buttonModelColumn editableColumn:buttonClickableModelColumn];\n\t[col setTitle:str];\n\t[t->tv addTableColumn:col];\n}\n"
  },
  {
    "path": "darwin/text.m",
    "content": "// 10 april 2015\n#import \"uipriv_darwin.h\"\n\nchar *uiDarwinNSStringToText(NSString *s)\n{\n\tchar *out;\n\n\tout = strdup([s UTF8String]);\n\tif (out == NULL) {\n\t\tfprintf(stderr, \"memory exhausted in uiDarwinNSStringToText()\\n\");\n\t\tabort();\n\t}\n\treturn out;\n}\n\nvoid uiFreeText(char *s)\n{\n\tfree(s);\n}\n\nint uiprivStricmp(const char *a, const char *b)\n{\n\treturn strcasecmp(a, b);\n}\n"
  },
  {
    "path": "darwin/uipriv_darwin.h",
    "content": "// 6 january 2015\n// note: as of OS X Sierra, the -mmacosx-version-min compiler options governs deprecation warnings; keep these around anyway just in case\n#define MAC_OS_X_VERSION_MIN_REQUIRED MAC_OS_X_VERSION_10_8\n#define MAC_OS_X_VERSION_MAX_ALLOWED MAC_OS_X_VERSION_10_8\n#import <Cocoa/Cocoa.h>\n#import <dlfcn.h>\t\t// see future.m\n#import \"../ui.h\"\n#import \"../ui_darwin.h\"\n#import \"../common/uipriv.h\"\n\n// TODO should we rename the uiprivMk things or not\n// TODO what about renaming class wrapper functions that return the underlying class (like uiprivNewLabel())\n\n#if __has_feature(objc_arc)\n#error Sorry, libui cannot be compiled with ARC.\n#endif\n\n#define uiprivToNSString(str) [NSString stringWithUTF8String:(str)]\n#define uiprivFromNSString(str) [(str) UTF8String]\n\n// TODO find a better place for this\n#ifndef NSAppKitVersionNumber10_9\n#define NSAppKitVersionNumber10_9 1265\n#endif\n\n// map.m\ntypedef struct uiprivMap uiprivMap;\nextern uiprivMap *uiprivNewMap(void);\nextern void uiprivMapDestroy(uiprivMap *m);\nextern void *uiprivMapGet(uiprivMap *m, void *key);\nextern void uiprivMapSet(uiprivMap *m, void *key, void *value);\nextern void uiprivMapDelete(uiprivMap *m, void *key);\nextern void uiprivMapWalk(uiprivMap *m, void (*f)(void *key, void *value));\nextern void uiprivMapReset(uiprivMap *m);\n\n// menu.m\n@interface uiprivMenuManager : NSObject {\n\tuiprivMap *items;\n\tBOOL hasQuit;\n\tBOOL hasPreferences;\n\tBOOL hasAbout;\n}\n@property (strong) NSMenuItem *quitItem;\n@property (strong) NSMenuItem *preferencesItem;\n@property (strong) NSMenuItem *aboutItem;\n// NSMenuValidation is only informal\n- (BOOL)validateMenuItem:(NSMenuItem *)item;\n- (NSMenu *)makeMenubar;\n@end\nextern void uiprivFinalizeMenus(void);\nextern void uiprivUninitMenus(void);\n\n// main.m\n@interface uiprivApplicationClass : NSApplication\n@end\n// this is needed because NSApp is of type id, confusing clang\n#define uiprivNSApp() ((uiprivApplicationClass *) NSApp)\n@interface uiprivAppDelegate : NSObject<NSApplicationDelegate>\n@property (strong) uiprivMenuManager *menuManager;\n@end\n#define uiprivAppDelegate() ((uiprivAppDelegate *) [uiprivNSApp() delegate])\ntypedef struct uiprivNextEventArgs uiprivNextEventArgs;\nstruct uiprivNextEventArgs {\n\tNSEventMask mask;\n\tNSDate *duration;\n\t// LONGTERM no NSRunLoopMode?\n\tNSString *mode;\n\tBOOL dequeue;\n};\nextern int uiprivMainStep(uiprivNextEventArgs *nea, BOOL (^interceptEvent)(NSEvent *));\n\n// util.m\nextern void uiprivDisableAutocorrect(NSTextView *);\n\n// entry.m\nextern void uiprivFinishNewTextField(NSTextField *, BOOL);\nextern NSTextField *uiprivNewEditableTextField(void);\n\n// window.m\n@interface uiprivNSWindow : NSWindow\n- (void)uiprivDoMove:(NSEvent *)initialEvent;\n- (void)uiprivDoResize:(NSEvent *)initialEvent on:(uiWindowResizeEdge)edge;\n@end\nextern uiWindow *uiprivWindowFromNSWindow(NSWindow *);\n\n// alloc.m\nextern NSMutableArray *uiprivDelegates;\nextern void uiprivInitAlloc(void);\nextern void uiprivUninitAlloc(void);\n\n// autolayout.m\nextern NSLayoutConstraint *uiprivMkConstraint(id view1, NSLayoutAttribute attr1, NSLayoutRelation relation, id view2, NSLayoutAttribute attr2, CGFloat multiplier, CGFloat c, NSString *desc);\nextern void uiprivJiggleViewLayout(NSView *view);\ntypedef struct uiprivSingleChildConstraints uiprivSingleChildConstraints;\nstruct uiprivSingleChildConstraints {\n\tNSLayoutConstraint *leadingConstraint;\n\tNSLayoutConstraint *topConstraint;\n\tNSLayoutConstraint *trailingConstraintGreater;\n\tNSLayoutConstraint *trailingConstraintEqual;\n\tNSLayoutConstraint *bottomConstraintGreater;\n\tNSLayoutConstraint *bottomConstraintEqual;\n};\nextern void uiprivSingleChildConstraintsEstablish(uiprivSingleChildConstraints *c, NSView *contentView, NSView *childView, BOOL hugsTrailing, BOOL hugsBottom, int margined, NSString *desc);\nextern void uiprivSingleChildConstraintsRemove(uiprivSingleChildConstraints *c, NSView *cv);\nextern void uiprivSingleChildConstraintsSetMargined(uiprivSingleChildConstraints *c, int margined);\n\n// area.m\nextern int uiprivSendAreaEvents(NSEvent *);\n\n// areaevents.m\nextern BOOL uiprivFromKeycode(unsigned short keycode, uiAreaKeyEvent *ke);\nextern BOOL uiprivKeycodeModifier(unsigned short keycode, uiModifiers *mod);\n\n// draw.m\nextern uiDrawContext *uiprivDrawNewContext(CGContextRef, CGFloat);\nextern void uiprivDrawFreeContext(uiDrawContext *);\n\n// fontbutton.m\nextern BOOL uiprivFontButtonInhibitSendAction(SEL sel, id from, id to);\nextern BOOL uiprivFontButtonOverrideTargetForAction(SEL sel, id from, id to, id *override);\nextern void uiprivSetupFontPanel(void);\n\n// colorbutton.m\nextern BOOL uiprivColorButtonInhibitSendAction(SEL sel, id from, id to);\n\n// scrollview.m\ntypedef struct uiprivScrollViewCreateParams uiprivScrollViewCreateParams;\nstruct uiprivScrollViewCreateParams {\n\t// TODO MAYBE fix these identifiers\n\tNSView *DocumentView;\n\tNSColor *BackgroundColor;\n\tBOOL DrawsBackground;\n\tBOOL Bordered;\n\tBOOL HScroll;\n\tBOOL VScroll;\n};\ntypedef struct uiprivScrollViewData uiprivScrollViewData;\nextern NSScrollView *uiprivMkScrollView(uiprivScrollViewCreateParams *p, uiprivScrollViewData **dout);\nextern void uiprivScrollViewSetScrolling(NSScrollView *sv, uiprivScrollViewData *d, BOOL hscroll, BOOL vscroll);\nextern void uiprivScrollViewFreeData(NSScrollView *sv, uiprivScrollViewData *d);\n\n// label.m\nextern NSTextField *uiprivNewLabel(NSString *str);\n\n// image.m\nextern NSImage *uiprivImageNSImage(uiImage *);\n\n// winmoveresize.m\nextern void uiprivDoManualMove(NSWindow *w, NSEvent *initialEvent);\nextern void uiprivDoManualResize(NSWindow *w, NSEvent *initialEvent, uiWindowResizeEdge edge);\n\n// future.m\nextern CFStringRef *uiprivFUTURE_kCTFontOpenTypeFeatureTag;\nextern CFStringRef *uiprivFUTURE_kCTFontOpenTypeFeatureValue;\nextern CFStringRef *uiprivFUTURE_kCTBackgroundColorAttributeName;\nextern void uiprivLoadFutures(void);\nextern void uiprivFUTURE_NSLayoutConstraint_setIdentifier(NSLayoutConstraint *constraint, NSString *identifier);\nextern BOOL uiprivFUTURE_NSWindow_performWindowDragWithEvent(NSWindow *w, NSEvent *initialEvent);\n\n// undocumented.m\nextern CFStringRef uiprivUNDOC_kCTFontPreferredSubFamilyNameKey;\nextern CFStringRef uiprivUNDOC_kCTFontPreferredFamilyNameKey;\nextern void uiprivLoadUndocumented(void);\n"
  },
  {
    "path": "darwin/undocumented.m",
    "content": "// 3 november 2017\n#import \"uipriv_darwin.h\"\n\n// functions and constants FROM THE DEPTHS BELOW!\n// note: for constants, dlsym() returns the address of the constant itself, as if we had done &constantName\n// we also provide default values just in case\n\n// these values come from 10.12.6\nCFStringRef uiprivUNDOC_kCTFontPreferredSubFamilyNameKey = CFSTR(\"CTFontPreferredSubFamilyName\");\nCFStringRef uiprivUNDOC_kCTFontPreferredFamilyNameKey = CFSTR(\"CTFontPreferredFamilyName\");\n\n// note that we treat any error as \"the symbols aren't there\" (and don't care if dlclose() failed)\nvoid uiprivLoadUndocumented(void)\n{\n\tvoid *handle;\n\tCFStringRef *str;\n\n\t// dlsym() walks the dependency chain, so opening the current process should be sufficient\n\thandle = dlopen(NULL, RTLD_LAZY);\n\tif (handle == NULL)\n\t\treturn;\n#define GET(var, fn) *((void **) (&var)) = dlsym(handle, #fn)\n\tGET(str, kCTFontPreferredSubFamilyNameKey);\nNSLog(@\"get %p\", str);\n\tif (str != NULL)\n\t\tuiprivUNDOC_kCTFontPreferredSubFamilyNameKey = *str;\n\tGET(str, kCTFontPreferredFamilyNameKey);\n\tif (str != NULL)\n\t\tuiprivUNDOC_kCTFontPreferredFamilyNameKey = *str;\n\tdlclose(handle);\n}\n"
  },
  {
    "path": "darwin/util.m",
    "content": "// 7 april 2015\n#import \"uipriv_darwin.h\"\n\n// LONGTERM do we really want to do this? make it an option?\n// TODO figure out why we removed this from window.m\nvoid uiprivDisableAutocorrect(NSTextView *tv)\n{\n\t[tv setEnabledTextCheckingTypes:0];\n\t[tv setAutomaticDashSubstitutionEnabled:NO];\n\t// don't worry about automatic data detection; it won't change stringValue (thanks pretty_function in irc.freenode.net/#macdev)\n\t[tv setAutomaticSpellingCorrectionEnabled:NO];\n\t[tv setAutomaticTextReplacementEnabled:NO];\n\t[tv setAutomaticQuoteSubstitutionEnabled:NO];\n\t[tv setAutomaticLinkDetectionEnabled:NO];\n\t[tv setSmartInsertDeleteEnabled:NO];\n}\n"
  },
  {
    "path": "darwin/window.m",
    "content": "// 15 august 2015\n#import \"uipriv_darwin.h\"\n\n#define defaultStyleMask (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)\n\nstruct uiWindow {\n\tuiDarwinControl c;\n\tNSWindow *window;\n\tuiControl *child;\n\tint margined;\n\tint (*onClosing)(uiWindow *, void *);\n\tvoid *onClosingData;\n\tuiprivSingleChildConstraints constraints;\n\tvoid (*onContentSizeChanged)(uiWindow *, void *);\n\tvoid *onContentSizeChangedData;\n\tBOOL suppressSizeChanged;\n\tint fullscreen;\n\tint borderless;\n};\n\n@implementation uiprivNSWindow\n\n- (void)uiprivDoMove:(NSEvent *)initialEvent\n{\n\tuiprivDoManualMove(self, initialEvent);\n}\n\n- (void)uiprivDoResize:(NSEvent *)initialEvent on:(uiWindowResizeEdge)edge\n{\n\tuiprivDoManualResize(self, initialEvent, edge);\n}\n\n@end\n\n@interface windowDelegateClass : NSObject<NSWindowDelegate> {\n\tuiprivMap *windows;\n}\n- (BOOL)windowShouldClose:(id)sender;\n- (void)windowDidResize:(NSNotification *)note;\n- (void)windowDidEnterFullScreen:(NSNotification *)note;\n- (void)windowDidExitFullScreen:(NSNotification *)note;\n- (void)registerWindow:(uiWindow *)w;\n- (void)unregisterWindow:(uiWindow *)w;\n- (uiWindow *)lookupWindow:(NSWindow *)w;\n@end\n\n@implementation windowDelegateClass\n\n- (id)init\n{\n\tself = [super init];\n\tif (self)\n\t\tself->windows = uiprivNewMap();\n\treturn self;\n}\n\n- (void)dealloc\n{\n\tuiprivMapDestroy(self->windows);\n\t[super dealloc];\n}\n\n- (BOOL)windowShouldClose:(id)sender\n{\n\tuiWindow *w;\n\n\tw = [self lookupWindow:((NSWindow *) sender)];\n\t// w should not be NULL; we are only the delegate of registered windows\n\tif ((*(w->onClosing))(w, w->onClosingData))\n\t\tuiControlDestroy(uiControl(w));\n\treturn NO;\n}\n\n- (void)windowDidResize:(NSNotification *)note\n{\n\tuiWindow *w;\n\n\tw = [self lookupWindow:((NSWindow *) [note object])];\n\tif (!w->suppressSizeChanged)\n\t\t(*(w->onContentSizeChanged))(w, w->onContentSizeChangedData);\n}\n\n- (void)windowDidEnterFullScreen:(NSNotification *)note\n{\n\tuiWindow *w;\n\n\tw = [self lookupWindow:((NSWindow *) [note object])];\n\tif (!w->suppressSizeChanged)\n\t\tw->fullscreen = 1;\n}\n\n- (void)windowDidExitFullScreen:(NSNotification *)note\n{\n\tuiWindow *w;\n\n\tw = [self lookupWindow:((NSWindow *) [note object])];\n\tif (!w->suppressSizeChanged)\n\t\tw->fullscreen = 0;\n}\n\n- (void)registerWindow:(uiWindow *)w\n{\n\tuiprivMapSet(self->windows, w->window, w);\n\t[w->window setDelegate:self];\n}\n\n- (void)unregisterWindow:(uiWindow *)w\n{\n\t[w->window setDelegate:nil];\n\tuiprivMapDelete(self->windows, w->window);\n}\n\n- (uiWindow *)lookupWindow:(NSWindow *)w\n{\n\tuiWindow *v;\n\n\tv = uiWindow(uiprivMapGet(self->windows, w));\n\t// this CAN (and IS ALLOWED TO) return NULL, just in case we're called with some OS X-provided window as the key window\n\treturn v;\n}\n\n@end\n\nstatic windowDelegateClass *windowDelegate = nil;\n\nstatic void removeConstraints(uiWindow *w)\n{\n\tNSView *cv;\n\n\tcv = [w->window contentView];\n\tuiprivSingleChildConstraintsRemove(&(w->constraints), cv);\n}\n\nstatic void uiWindowDestroy(uiControl *c)\n{\n\tuiWindow *w = uiWindow(c);\n\n\t// hide the window\n\t[w->window orderOut:w->window];\n\tremoveConstraints(w);\n\tif (w->child != NULL) {\n\t\tuiControlSetParent(w->child, NULL);\n\t\tuiDarwinControlSetSuperview(uiDarwinControl(w->child), nil);\n\t\tuiControlDestroy(w->child);\n\t}\n\t[windowDelegate unregisterWindow:w];\n\t[w->window release];\n\tuiFreeControl(uiControl(w));\n}\n\nuiDarwinControlDefaultHandle(uiWindow, window)\n\nuiControl *uiWindowParent(uiControl *c)\n{\n\treturn NULL;\n}\n\nvoid uiWindowSetParent(uiControl *c, uiControl *parent)\n{\n\tuiUserBugCannotSetParentOnToplevel(\"uiWindow\");\n}\n\nstatic int uiWindowToplevel(uiControl *c)\n{\n\treturn 1;\n}\n\nstatic int uiWindowVisible(uiControl *c)\n{\n\tuiWindow *w = uiWindow(c);\n\n\treturn [w->window isVisible];\n}\n\nstatic void uiWindowShow(uiControl *c)\n{\n\tuiWindow *w = (uiWindow *) c;\n\n\t[w->window makeKeyAndOrderFront:w->window];\n}\n\nstatic void uiWindowHide(uiControl *c)\n{\n\tuiWindow *w = (uiWindow *) c;\n\n\t[w->window orderOut:w->window];\n}\n\nuiDarwinControlDefaultEnabled(uiWindow, window)\nuiDarwinControlDefaultEnable(uiWindow, window)\nuiDarwinControlDefaultDisable(uiWindow, window)\n\nstatic void uiWindowSyncEnableState(uiDarwinControl *c, int enabled)\n{\n\tuiWindow *w = uiWindow(c);\n\n\tif (uiDarwinShouldStopSyncEnableState(uiDarwinControl(w), enabled))\n\t\treturn;\n\tif (w->child != NULL)\n\t\tuiDarwinControlSyncEnableState(uiDarwinControl(w->child), enabled);\n}\n\nstatic void uiWindowSetSuperview(uiDarwinControl *c, NSView *superview)\n{\n\t// TODO\n}\n\nstatic void windowRelayout(uiWindow *w)\n{\n\tNSView *childView;\n\tNSView *contentView;\n\n\tremoveConstraints(w);\n\tif (w->child == NULL)\n\t\treturn;\n\tchildView = (NSView *) uiControlHandle(w->child);\n\tcontentView = [w->window contentView];\n\tuiprivSingleChildConstraintsEstablish(&(w->constraints),\n\t\tcontentView, childView,\n\t\tuiDarwinControlHugsTrailingEdge(uiDarwinControl(w->child)),\n\t\tuiDarwinControlHugsBottom(uiDarwinControl(w->child)),\n\t\tw->margined,\n\t\t@\"uiWindow\");\n}\n\nuiDarwinControlDefaultHugsTrailingEdge(uiWindow, window)\nuiDarwinControlDefaultHugsBottom(uiWindow, window)\n\nstatic void uiWindowChildEdgeHuggingChanged(uiDarwinControl *c)\n{\n\tuiWindow *w = uiWindow(c);\n\n\twindowRelayout(w);\n}\n\n// TODO\nuiDarwinControlDefaultHuggingPriority(uiWindow, window)\nuiDarwinControlDefaultSetHuggingPriority(uiWindow, window)\n// end TODO\n\nstatic void uiWindowChildVisibilityChanged(uiDarwinControl *c)\n{\n\tuiWindow *w = uiWindow(c);\n\n\twindowRelayout(w);\n}\n\nchar *uiWindowTitle(uiWindow *w)\n{\n\treturn uiDarwinNSStringToText([w->window title]);\n}\n\nvoid uiWindowSetTitle(uiWindow *w, const char *title)\n{\n\t[w->window setTitle:uiprivToNSString(title)];\n}\n\nvoid uiWindowContentSize(uiWindow *w, int *width, int *height)\n{\n\tNSRect r;\n\n\tr = [w->window contentRectForFrameRect:[w->window frame]];\n\t*width = r.size.width;\n\t*height = r.size.height;\n}\n\nvoid uiWindowSetContentSize(uiWindow *w, int width, int height)\n{\n\tw->suppressSizeChanged = YES;\n\t[w->window setContentSize:NSMakeSize(width, height)];\n\tw->suppressSizeChanged = NO;\n}\n\nint uiWindowFullscreen(uiWindow *w)\n{\n\treturn w->fullscreen;\n}\n\nvoid uiWindowSetFullscreen(uiWindow *w, int fullscreen)\n{\n\tif (w->fullscreen && fullscreen)\n\t\treturn;\n\tif (!w->fullscreen && !fullscreen)\n\t\treturn;\n\tw->fullscreen = fullscreen;\n\tif (w->fullscreen && w->borderless)\t\t// borderless doesn't play nice with fullscreen; don't toggle while borderless\n\t\treturn;\n\tw->suppressSizeChanged = YES;\n\t[w->window toggleFullScreen:w->window];\n\tw->suppressSizeChanged = NO;\n\tif (!w->fullscreen && w->borderless)\t\t// borderless doesn't play nice with fullscreen; restore borderless after removing\n\t\t[w->window setStyleMask:NSBorderlessWindowMask];\n}\n\nvoid uiWindowOnContentSizeChanged(uiWindow *w, void (*f)(uiWindow *, void *), void *data)\n{\n\tw->onContentSizeChanged = f;\n\tw->onContentSizeChangedData = data;\n}\n\nvoid uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *, void *), void *data)\n{\n\tw->onClosing = f;\n\tw->onClosingData = data;\n}\n\nint uiWindowBorderless(uiWindow *w)\n{\n\treturn w->borderless;\n}\n\nvoid uiWindowSetBorderless(uiWindow *w, int borderless)\n{\n\tw->borderless = borderless;\n\tif (w->borderless) {\n\t\t// borderless doesn't play nice with fullscreen; wait for later\n\t\tif (!w->fullscreen)\n\t\t\t[w->window setStyleMask:NSBorderlessWindowMask];\n\t} else {\n\t\t[w->window setStyleMask:defaultStyleMask];\n\t\t// borderless doesn't play nice with fullscreen; restore state\n\t\tif (w->fullscreen) {\n\t\t\tw->suppressSizeChanged = YES;\n\t\t\t[w->window toggleFullScreen:w->window];\n\t\t\tw->suppressSizeChanged = NO;\n\t\t}\n\t}\n}\n\nvoid uiWindowSetChild(uiWindow *w, uiControl *child)\n{\n\tNSView *childView;\n\n\tif (w->child != NULL) {\n\t\tchildView = (NSView *) uiControlHandle(w->child);\n\t\t[childView removeFromSuperview];\n\t\tuiControlSetParent(w->child, NULL);\n\t}\n\tw->child = child;\n\tif (w->child != NULL) {\n\t\tuiControlSetParent(w->child, uiControl(w));\n\t\tchildView = (NSView *) uiControlHandle(w->child);\n\t\tuiDarwinControlSetSuperview(uiDarwinControl(w->child), [w->window contentView]);\n\t\tuiDarwinControlSyncEnableState(uiDarwinControl(w->child), uiControlEnabledToUser(uiControl(w)));\n\t}\n\twindowRelayout(w);\n}\n\nint uiWindowMargined(uiWindow *w)\n{\n\treturn w->margined;\n}\n\nvoid uiWindowSetMargined(uiWindow *w, int margined)\n{\n\tw->margined = margined;\n\tuiprivSingleChildConstraintsSetMargined(&(w->constraints), w->margined);\n}\n\nstatic int defaultOnClosing(uiWindow *w, void *data)\n{\n\treturn 0;\n}\n\nstatic void defaultOnPositionContentSizeChanged(uiWindow *w, void *data)\n{\n\t// do nothing\n}\n\nuiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar)\n{\n\tuiWindow *w;\n\n\tuiprivFinalizeMenus();\n\n\tuiDarwinNewControl(uiWindow, w);\n\n\tw->window = [[uiprivNSWindow alloc] initWithContentRect:NSMakeRect(0, 0, (CGFloat) width, (CGFloat) height)\n\t\tstyleMask:defaultStyleMask\n\t\tbacking:NSBackingStoreBuffered\n\t\tdefer:YES];\n\t[w->window setTitle:uiprivToNSString(title)];\n\n\t// do NOT release when closed\n\t// we manually do this in uiWindowDestroy() above\n\t[w->window setReleasedWhenClosed:NO];\n\n\tif (windowDelegate == nil) {\n\t\twindowDelegate = [[windowDelegateClass new] autorelease];\n\t\t[uiprivDelegates addObject:windowDelegate];\n\t}\n\t[windowDelegate registerWindow:w];\n\tuiWindowOnClosing(w, defaultOnClosing, NULL);\n\tuiWindowOnContentSizeChanged(w, defaultOnPositionContentSizeChanged, NULL);\n\n\treturn w;\n}\n\n// utility function for menus\nuiWindow *uiprivWindowFromNSWindow(NSWindow *w)\n{\n\tif (w == nil)\n\t\treturn NULL;\n\tif (windowDelegate == nil)\t\t// no windows were created yet; we're called with some OS X-provided window\n\t\treturn NULL;\n\treturn [windowDelegate lookupWindow:w];\n}\n"
  },
  {
    "path": "darwin/winmoveresize.m",
    "content": "// 1 november 2016\n#import \"uipriv_darwin.h\"\n\n// 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\n\n// because we are changing the window frame each time the mouse moves, the successive -[NSEvent locationInWindow]s cannot be meaningfully used together\n// 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)\nstatic NSPoint makeIndependent(NSPoint p, NSWindow *w)\n{\n\tNSRect r;\n\n\tr.origin = p;\n\t// mikeash in irc.freenode.net/#macdev confirms both that any size will do and that we can safely ignore the resultant size\n\tr.size = NSZeroSize;\n\treturn [w convertRectToScreen:r].origin;\n}\n\nstruct onMoveDragParams {\n\tNSWindow *w;\n\t// using the previous point causes weird issues like the mouse seeming to fall behind the window edge... so do this instead\n\t// TODO will this make things like the menubar and dock easier too?\n\tNSRect initialFrame;\n\tNSPoint initialPoint;\n};\n\nvoid onMoveDrag(struct onMoveDragParams *p, NSEvent *e)\n{\n\tNSPoint new;\n\tNSRect frame;\n\tCGFloat offx, offy;\n\n\tnew = makeIndependent([e locationInWindow], p->w);\n\tframe = p->initialFrame;\n\n\toffx = new.x - p->initialPoint.x;\n\toffy = new.y - p->initialPoint.y;\n\tframe.origin.x += offx;\n\tframe.origin.y += offy;\n\n\t// TODO handle the menubar\n\t// TODO wait the system does this for us already?!\n\n\t[p->w setFrameOrigin:frame.origin];\n}\n\nvoid uiprivDoManualMove(NSWindow *w, NSEvent *initialEvent)\n{\n\t__block struct onMoveDragParams mdp;\n\tuiprivNextEventArgs nea;\n\tBOOL (^handleEvent)(NSEvent *e);\n\t__block BOOL done;\n\n\t// 10.11 gives us a method to handle this for us\n\t// use it if available; this lets us use the real OS dragging code, which means we can take advantage of OS features like Spaces\n\tif (uiprivFUTURE_NSWindow_performWindowDragWithEvent(w, initialEvent))\n\t\treturn;\n\n\tmdp.w = w;\n\tmdp.initialFrame = [mdp.w frame];\n\tmdp.initialPoint = makeIndependent([initialEvent locationInWindow], mdp.w);\n\n\tnea.mask = NSLeftMouseDraggedMask | NSLeftMouseUpMask;\n\tnea.duration = [NSDate distantFuture];\n\tnea.mode = NSEventTrackingRunLoopMode;\t\t// nextEventMatchingMask: docs suggest using this for manual mouse tracking\n\tnea.dequeue = YES;\n\thandleEvent = ^(NSEvent *e) {\n\t\tif ([e type] == NSLeftMouseUp) {\n\t\t\tdone = YES;\n\t\t\treturn YES;\t// do not send\n\t\t}\n\t\tonMoveDrag(&mdp, e);\n\t\treturn YES;\t\t// do not send\n\t};\n\tdone = NO;\n\twhile (uiprivMainStep(&nea, handleEvent))\n\t\tif (done)\n\t\t\tbreak;\n}\n\n// see http://stackoverflow.com/a/40352996/3408572\nstatic void minMaxAutoLayoutSizes(NSWindow *w, NSSize *min, NSSize *max)\n{\n\tNSLayoutConstraint *cw, *ch;\n\tNSView *contentView;\n\tNSRect prevFrame;\n\n\t// if adding these constraints causes the window to change size somehow, don't show it to the user and change it back afterwards\n\tNSDisableScreenUpdates();\n\tprevFrame = [w frame];\n\n\t// minimum: encourage the window to be as small as possible\n\tcontentView = [w contentView];\n\tcw = uiprivMkConstraint(contentView, NSLayoutAttributeWidth,\n\t\tNSLayoutRelationEqual,\n\t\tnil, NSLayoutAttributeNotAnAttribute,\n\t\t0, 0,\n\t\t@\"window minimum width finding constraint\");\n\t[cw setPriority:NSLayoutPriorityDragThatCanResizeWindow];\n\t[contentView addConstraint:cw];\n\tch = uiprivMkConstraint(contentView, NSLayoutAttributeHeight,\n\t\tNSLayoutRelationEqual,\n\t\tnil, NSLayoutAttributeNotAnAttribute,\n\t\t0, 0,\n\t\t@\"window minimum height finding constraint\");\n\t[ch setPriority:NSLayoutPriorityDragThatCanResizeWindow];\n\t[contentView addConstraint:ch];\n\t*min = [contentView fittingSize];\n\t[contentView removeConstraint:cw];\n\t[contentView removeConstraint:ch];\n\n\t// maximum: encourage the window to be as large as possible\n\tcontentView = [w contentView];\n\tcw = uiprivMkConstraint(contentView, NSLayoutAttributeWidth,\n\t\tNSLayoutRelationEqual,\n\t\tnil, NSLayoutAttributeNotAnAttribute,\n\t\t0, CGFLOAT_MAX,\n\t\t@\"window maximum width finding constraint\");\n\t[cw setPriority:NSLayoutPriorityDragThatCanResizeWindow];\n\t[contentView addConstraint:cw];\n\tch = uiprivMkConstraint(contentView, NSLayoutAttributeHeight,\n\t\tNSLayoutRelationEqual,\n\t\tnil, NSLayoutAttributeNotAnAttribute,\n\t\t0, CGFLOAT_MAX,\n\t\t@\"window maximum height finding constraint\");\n\t[ch setPriority:NSLayoutPriorityDragThatCanResizeWindow];\n\t[contentView addConstraint:ch];\n\t*max = [contentView fittingSize];\n\t[contentView removeConstraint:cw];\n\t[contentView removeConstraint:ch];\n\n\t[w setFrame:prevFrame display:YES];\t\t// TODO really YES?\n\tNSEnableScreenUpdates();\n}\n\nstatic void handleResizeLeft(NSRect *frame, NSPoint old, NSPoint new)\n{\n\tframe->origin.x += new.x - old.x;\n\tframe->size.width -= new.x - old.x;\n}\n\n// TODO properly handle the menubar\n// TODO wait, OS X does it for us?!\nstatic void handleResizeTop(NSRect *frame, NSPoint old, NSPoint new)\n{\n\tframe->size.height += new.y - old.y;\n}\n\nstatic void handleResizeRight(NSRect *frame, NSPoint old, NSPoint new)\n{\n\tframe->size.width += new.x - old.x;\n}\n\n\n// TODO properly handle the menubar\nstatic void handleResizeBottom(NSRect *frame, NSPoint old, NSPoint new)\n{\n\tframe->origin.y += new.y - old.y;\n\tframe->size.height -= new.y - old.y;\n}\n\nstruct onResizeDragParams {\n\tNSWindow *w;\n\t// using the previous point causes weird issues like the mouse seeming to fall behind the window edge... so do this instead\n\t// TODO will this make things like the menubar and dock easier too?\n\tNSRect initialFrame;\n\tNSPoint initialPoint;\n\tuiWindowResizeEdge edge;\n\tNSSize min;\n\tNSSize max;\n};\n\nstatic void onResizeDrag(struct onResizeDragParams *p, NSEvent *e)\n{\n\tNSPoint new;\n\tNSRect frame;\n\n\tnew = makeIndependent([e locationInWindow], p->w);\n\tframe = p->initialFrame;\n\n\t// horizontal\n\tswitch (p->edge) {\n\tcase uiWindowResizeEdgeLeft:\n\tcase uiWindowResizeEdgeTopLeft:\n\tcase uiWindowResizeEdgeBottomLeft:\n\t\thandleResizeLeft(&frame, p->initialPoint, new);\n\t\tbreak;\n\tcase uiWindowResizeEdgeRight:\n\tcase uiWindowResizeEdgeTopRight:\n\tcase uiWindowResizeEdgeBottomRight:\n\t\thandleResizeRight(&frame, p->initialPoint, new);\n\t\tbreak;\n\t}\n\t// vertical\n\tswitch (p->edge) {\n\tcase uiWindowResizeEdgeTop:\n\tcase uiWindowResizeEdgeTopLeft:\n\tcase uiWindowResizeEdgeTopRight:\n\t\thandleResizeTop(&frame, p->initialPoint, new);\n\t\tbreak;\n\tcase uiWindowResizeEdgeBottom:\n\tcase uiWindowResizeEdgeBottomLeft:\n\tcase uiWindowResizeEdgeBottomRight:\n\t\thandleResizeBottom(&frame, p->initialPoint, new);\n\t\tbreak;\n\t}\n\n\t// constrain\n\t// TODO should we constrain against anything else as well? minMaxAutoLayoutSizes() already gives us nonnegative sizes, but...\n\tif (frame.size.width < p->min.width)\n\t\tframe.size.width = p->min.width;\n\tif (frame.size.height < p->min.height)\n\t\tframe.size.height = p->min.height;\n\t// TODO > or >= ?\n\tif (frame.size.width > p->max.width)\n\t\tframe.size.width = p->max.width;\n\tif (frame.size.height > p->max.height)\n\t\tframe.size.height = p->max.height;\n\n\t[p->w setFrame:frame display:YES];\t\t\t// and do reflect the new frame immediately\n}\n\n// TODO do our events get fired with this? *should* they?\nvoid uiprivDoManualResize(NSWindow *w, NSEvent *initialEvent, uiWindowResizeEdge edge)\n{\n\t__block struct onResizeDragParams rdp;\n\tuiprivNextEventArgs nea;\n\tBOOL (^handleEvent)(NSEvent *e);\n\t__block BOOL done;\n\n\trdp.w = w;\n\trdp.initialFrame = [rdp.w frame];\n\trdp.initialPoint = makeIndependent([initialEvent locationInWindow], rdp.w);\n\trdp.edge = edge;\n\t// TODO what happens if these change during the loop?\n\tminMaxAutoLayoutSizes(rdp.w, &(rdp.min), &(rdp.max));\n\n\tnea.mask = NSLeftMouseDraggedMask | NSLeftMouseUpMask;\n\tnea.duration = [NSDate distantFuture];\n\tnea.mode = NSEventTrackingRunLoopMode;\t\t// nextEventMatchingMask: docs suggest using this for manual mouse tracking\n\tnea.dequeue = YES;\n\thandleEvent = ^(NSEvent *e) {\n\t\tif ([e type] == NSLeftMouseUp) {\n\t\t\tdone = YES;\n\t\t\treturn YES;\t// do not send\n\t\t}\n\t\tonResizeDrag(&rdp, e);\n\t\treturn YES;\t\t// do not send\n\t};\n\tdone = NO;\n\twhile (uiprivMainStep(&nea, handleEvent))\n\t\tif (done)\n\t\t\tbreak;\n}\n"
  },
  {
    "path": "examples/controlgallery/main.c",
    "content": "// 2 september 2015\n#include <stdio.h>\n#include <string.h>\n#include \"../../ui.h\"\n\nstatic int onClosing(uiWindow *w, void *data)\n{\n\tuiQuit();\n\treturn 1;\n}\n\nstatic int onShouldQuit(void *data)\n{\n\tuiWindow *mainwin = uiWindow(data);\n\n\tuiControlDestroy(uiControl(mainwin));\n\treturn 1;\n}\n\nstatic uiControl *makeBasicControlsPage(void)\n{\n\tuiBox *vbox;\n\tuiBox *hbox;\n\tuiGroup *group;\n\tuiForm *entryForm;\n\n\tvbox = uiNewVerticalBox();\n\tuiBoxSetPadded(vbox, 1);\n\n\thbox = uiNewHorizontalBox();\n\tuiBoxSetPadded(hbox, 1);\n\tuiBoxAppend(vbox, uiControl(hbox), 0);\n\n\tuiBoxAppend(hbox,\n\t\tuiControl(uiNewButton(\"Button\")),\n\t\t0);\n\tuiBoxAppend(hbox,\n\t\tuiControl(uiNewCheckbox(\"Checkbox\")),\n\t\t0);\n\n\tuiBoxAppend(vbox,\n\t\tuiControl(uiNewLabel(\"This is a label. Right now, labels can only span one line.\")),\n\t\t0);\n\n\tuiBoxAppend(vbox,\n\t\tuiControl(uiNewHorizontalSeparator()),\n\t\t0);\n\n\tgroup = uiNewGroup(\"Entries\");\n\tuiGroupSetMargined(group, 1);\n\tuiBoxAppend(vbox, uiControl(group), 1);\n\n\tentryForm = uiNewForm();\n\tuiFormSetPadded(entryForm, 1);\n\tuiGroupSetChild(group, uiControl(entryForm));\n\n\tuiFormAppend(entryForm,\n\t\t\"Entry\",\n\t\tuiControl(uiNewEntry()),\n\t\t0);\n\tuiFormAppend(entryForm,\n\t\t\"Password Entry\",\n\t\tuiControl(uiNewPasswordEntry()),\n\t\t0);\n\tuiFormAppend(entryForm,\n\t\t\"Search Entry\",\n\t\tuiControl(uiNewSearchEntry()),\n\t\t0);\n\tuiFormAppend(entryForm,\n\t\t\"Multiline Entry\",\n\t\tuiControl(uiNewMultilineEntry()),\n\t\t1);\n\tuiFormAppend(entryForm,\n\t\t\"Multiline Entry No Wrap\",\n\t\tuiControl(uiNewNonWrappingMultilineEntry()),\n\t\t1);\n\n\treturn uiControl(vbox);\n}\n\n// TODO make these not global\nstatic uiSpinbox *spinbox;\nstatic uiSlider *slider;\nstatic uiProgressBar *pbar;\n\nstatic void onSpinboxChanged(uiSpinbox *s, void *data)\n{\n\tuiSliderSetValue(slider, uiSpinboxValue(s));\n\tuiProgressBarSetValue(pbar, uiSpinboxValue(s));\n}\n\nstatic void onSliderChanged(uiSlider *s, void *data)\n{\n\tuiSpinboxSetValue(spinbox, uiSliderValue(s));\n\tuiProgressBarSetValue(pbar, uiSliderValue(s));\n}\n\nstatic uiControl *makeNumbersPage()\n{\n\tuiBox *hbox;\n\tuiGroup *group;\n\tuiBox *vbox;\n\tuiProgressBar *ip;\n\tuiCombobox *cbox;\n\tuiEditableCombobox *ecbox;\n\tuiRadioButtons *rb;\n\n\thbox = uiNewHorizontalBox();\n\tuiBoxSetPadded(hbox, 1);\n\n\tgroup = uiNewGroup(\"Numbers\");\n\tuiGroupSetMargined(group, 1);\n\tuiBoxAppend(hbox, uiControl(group), 1);\n\n\tvbox = uiNewVerticalBox();\n\tuiBoxSetPadded(vbox, 1);\n\tuiGroupSetChild(group, uiControl(vbox));\n\n\tspinbox = uiNewSpinbox(0, 100);\n\tslider = uiNewSlider(0, 100);\n\tpbar = uiNewProgressBar();\n\tuiSpinboxOnChanged(spinbox, onSpinboxChanged, NULL);\n\tuiSliderOnChanged(slider, onSliderChanged, NULL);\n\tuiBoxAppend(vbox, uiControl(spinbox), 0);\n\tuiBoxAppend(vbox, uiControl(slider), 0);\n\tuiBoxAppend(vbox, uiControl(pbar), 0);\n\n\tip = uiNewProgressBar();\n\tuiProgressBarSetValue(ip, -1);\n\tuiBoxAppend(vbox, uiControl(ip), 0);\n\n\tgroup = uiNewGroup(\"Lists\");\n\tuiGroupSetMargined(group, 1);\n\tuiBoxAppend(hbox, uiControl(group), 1);\n\n\tvbox = uiNewVerticalBox();\n\tuiBoxSetPadded(vbox, 1);\n\tuiGroupSetChild(group, uiControl(vbox));\n\n\tcbox = uiNewCombobox();\n\tuiComboboxAppend(cbox, \"Combobox Item 1\");\n\tuiComboboxAppend(cbox, \"Combobox Item 2\");\n\tuiComboboxAppend(cbox, \"Combobox Item 3\");\n\tuiBoxAppend(vbox, uiControl(cbox), 0);\n\n\tecbox = uiNewEditableCombobox();\n\tuiEditableComboboxAppend(ecbox, \"Editable Item 1\");\n\tuiEditableComboboxAppend(ecbox, \"Editable Item 2\");\n\tuiEditableComboboxAppend(ecbox, \"Editable Item 3\");\n\tuiBoxAppend(vbox, uiControl(ecbox), 0);\n\n\trb = uiNewRadioButtons();\n\tuiRadioButtonsAppend(rb, \"Radio Button 1\");\n\tuiRadioButtonsAppend(rb, \"Radio Button 2\");\n\tuiRadioButtonsAppend(rb, \"Radio Button 3\");\n\tuiBoxAppend(vbox, uiControl(rb), 0);\n\n\treturn uiControl(hbox);\n}\n\n// TODO make this not global\nstatic uiWindow *mainwin;\n\nstatic void onOpenFileClicked(uiButton *b, void *data)\n{\n\tuiEntry *entry = uiEntry(data);\n\tchar *filename;\n\n\tfilename = uiOpenFile(mainwin);\n\tif (filename == NULL) {\n\t\tuiEntrySetText(entry, \"(cancelled)\");\n\t\treturn;\n\t}\n\tuiEntrySetText(entry, filename);\n\tuiFreeText(filename);\n}\n\nstatic void onSaveFileClicked(uiButton *b, void *data)\n{\n\tuiEntry *entry = uiEntry(data);\n\tchar *filename;\n\n\tfilename = uiSaveFile(mainwin);\n\tif (filename == NULL) {\n\t\tuiEntrySetText(entry, \"(cancelled)\");\n\t\treturn;\n\t}\n\tuiEntrySetText(entry, filename);\n\tuiFreeText(filename);\n}\n\nstatic void onMsgBoxClicked(uiButton *b, void *data)\n{\n\tuiMsgBox(mainwin,\n\t\t\"This is a normal message box.\",\n\t\t\"More detailed information can be shown here.\");\n}\n\nstatic void onMsgBoxErrorClicked(uiButton *b, void *data)\n{\n\tuiMsgBoxError(mainwin,\n\t\t\"This message box describes an error.\",\n\t\t\"More detailed information can be shown here.\");\n}\n\nstatic uiControl *makeDataChoosersPage(void)\n{\n\tuiBox *hbox;\n\tuiBox *vbox;\n\tuiGrid *grid;\n\tuiButton *button;\n\tuiEntry *entry;\n\tuiGrid *msggrid;\n\n\thbox = uiNewHorizontalBox();\n\tuiBoxSetPadded(hbox, 1);\n\n\tvbox = uiNewVerticalBox();\n\tuiBoxSetPadded(vbox, 1);\n\tuiBoxAppend(hbox, uiControl(vbox), 0);\n\n\tuiBoxAppend(vbox,\n\t\tuiControl(uiNewDatePicker()),\n\t\t0);\n\tuiBoxAppend(vbox,\n\t\tuiControl(uiNewTimePicker()),\n\t\t0);\n\tuiBoxAppend(vbox,\n\t\tuiControl(uiNewDateTimePicker()),\n\t\t0);\n\n\tuiBoxAppend(vbox,\n\t\tuiControl(uiNewFontButton()),\n\t\t0);\n\tuiBoxAppend(vbox,\n\t\tuiControl(uiNewColorButton()),\n\t\t0);\n\n\tuiBoxAppend(hbox,\n\t\tuiControl(uiNewVerticalSeparator()),\n\t\t0);\n\n\tvbox = uiNewVerticalBox();\n\tuiBoxSetPadded(vbox, 1);\n\tuiBoxAppend(hbox, uiControl(vbox), 1);\n\n\tgrid = uiNewGrid();\n\tuiGridSetPadded(grid, 1);\n\tuiBoxAppend(vbox, uiControl(grid), 0);\n\n\tbutton = uiNewButton(\"Open File\");\n\tentry = uiNewEntry();\n\tuiEntrySetReadOnly(entry, 1);\n\tuiButtonOnClicked(button, onOpenFileClicked, entry);\n\tuiGridAppend(grid, uiControl(button),\n\t\t0, 0, 1, 1,\n\t\t0, uiAlignFill, 0, uiAlignFill);\n\tuiGridAppend(grid, uiControl(entry),\n\t\t1, 0, 1, 1,\n\t\t1, uiAlignFill, 0, uiAlignFill);\n\n\tbutton = uiNewButton(\"Save File\");\n\tentry = uiNewEntry();\n\tuiEntrySetReadOnly(entry, 1);\n\tuiButtonOnClicked(button, onSaveFileClicked, entry);\n\tuiGridAppend(grid, uiControl(button),\n\t\t0, 1, 1, 1,\n\t\t0, uiAlignFill, 0, uiAlignFill);\n\tuiGridAppend(grid, uiControl(entry),\n\t\t1, 1, 1, 1,\n\t\t1, uiAlignFill, 0, uiAlignFill);\n\n\tmsggrid = uiNewGrid();\n\tuiGridSetPadded(msggrid, 1);\n\tuiGridAppend(grid, uiControl(msggrid),\n\t\t0, 2, 2, 1,\n\t\t0, uiAlignCenter, 0, uiAlignStart);\n\n\tbutton = uiNewButton(\"Message Box\");\n\tuiButtonOnClicked(button, onMsgBoxClicked, NULL);\n\tuiGridAppend(msggrid, uiControl(button),\n\t\t0, 0, 1, 1,\n\t\t0, uiAlignFill, 0, uiAlignFill);\n\tbutton = uiNewButton(\"Error Box\");\n\tuiButtonOnClicked(button, onMsgBoxErrorClicked, NULL);\n\tuiGridAppend(msggrid, uiControl(button),\n\t\t1, 0, 1, 1,\n\t\t0, uiAlignFill, 0, uiAlignFill);\n\n\treturn uiControl(hbox);\n}\n\nint main(void)\n{\n\tuiInitOptions options;\n\tconst char *err;\n\tuiTab *tab;\n\n\tmemset(&options, 0, sizeof (uiInitOptions));\n\terr = uiInit(&options);\n\tif (err != NULL) {\n\t\tfprintf(stderr, \"error initializing libui: %s\", err);\n\t\tuiFreeInitError(err);\n\t\treturn 1;\n\t}\n\n\tmainwin = uiNewWindow(\"libui Control Gallery\", 640, 480, 1);\n\tuiWindowOnClosing(mainwin, onClosing, NULL);\n\tuiOnShouldQuit(onShouldQuit, mainwin);\n\n\ttab = uiNewTab();\n\tuiWindowSetChild(mainwin, uiControl(tab));\n\tuiWindowSetMargined(mainwin, 1);\n\n\tuiTabAppend(tab, \"Basic Controls\", makeBasicControlsPage());\n\tuiTabSetMargined(tab, 0, 1);\n\n\tuiTabAppend(tab, \"Numbers and Lists\", makeNumbersPage());\n\tuiTabSetMargined(tab, 1, 1);\n\n\tuiTabAppend(tab, \"Data Choosers\", makeDataChoosersPage());\n\tuiTabSetMargined(tab, 2, 1);\n\n\tuiControlShow(uiControl(mainwin));\n\tuiMain();\n\treturn 0;\n}\n\n#if 0\n\nstatic void openClicked(uiMenuItem *item, uiWindow *w, void *data)\n{\n\tchar *filename;\n\n\tfilename = uiOpenFile(mainwin);\n\tif (filename == NULL) {\n\t\tuiMsgBoxError(mainwin, \"No file selected\", \"Don't be alarmed!\");\n\t\treturn;\n\t}\n\tuiMsgBox(mainwin, \"File selected\", filename);\n\tuiFreeText(filename);\n}\n\nstatic void saveClicked(uiMenuItem *item, uiWindow *w, void *data)\n{\n\tchar *filename;\n\n\tfilename = uiSaveFile(mainwin);\n\tif (filename == NULL) {\n\t\tuiMsgBoxError(mainwin, \"No file selected\", \"Don't be alarmed!\");\n\t\treturn;\n\t}\n\tuiMsgBox(mainwin, \"File selected (don't worry, it's still there)\", filename);\n\tuiFreeText(filename);\n}\n\nstatic uiSpinbox *spinbox;\nstatic uiSlider *slider;\nstatic uiProgressBar *progressbar;\n\nstatic void update(int value)\n{\n\tuiSpinboxSetValue(spinbox, value);\n\tuiSliderSetValue(slider, value);\n\tuiProgressBarSetValue(progressbar, value);\n}\n\nstatic void onSpinboxChanged(uiSpinbox *s, void *data)\n{\n\tupdate(uiSpinboxValue(spinbox));\n}\n\nstatic void onSliderChanged(uiSlider *s, void *data)\n{\n\tupdate(uiSliderValue(slider));\n}\n\nint main(void)\n{\n\tuiInitOptions o;\n\tconst char *err;\n\tuiMenu *menu;\n\tuiMenuItem *item;\n\tuiBox *box;\n\tuiBox *hbox;\n\tuiGroup *group;\n\tuiBox *inner;\n\tuiBox *inner2;\n\tuiEntry *entry;\n\tuiCombobox *cbox;\n\tuiEditableCombobox *ecbox;\n\tuiRadioButtons *rb;\n\tuiTab *tab;\n\n\tmemset(&o, 0, sizeof (uiInitOptions));\n\terr = uiInit(&o);\n\tif (err != NULL) {\n\t\tfprintf(stderr, \"error initializing ui: %s\\n\", err);\n\t\tuiFreeInitError(err);\n\t\treturn 1;\n\t}\n\n\tmenu = uiNewMenu(\"File\");\n\titem = uiMenuAppendItem(menu, \"Open\");\n\tuiMenuItemOnClicked(item, openClicked, NULL);\n\titem = uiMenuAppendItem(menu, \"Save\");\n\tuiMenuItemOnClicked(item, saveClicked, NULL);\n\titem = uiMenuAppendQuitItem(menu);\n\tuiOnShouldQuit(shouldQuit, NULL);\n\n\tmenu = uiNewMenu(\"Edit\");\n\titem = uiMenuAppendCheckItem(menu, \"Checkable Item\");\n\tuiMenuAppendSeparator(menu);\n\titem = uiMenuAppendItem(menu, \"Disabled Item\");\n\tuiMenuItemDisable(item);\n\titem = uiMenuAppendPreferencesItem(menu);\n\n\tmenu = uiNewMenu(\"Help\");\n\titem = uiMenuAppendItem(menu, \"Help\");\n\titem = uiMenuAppendAboutItem(menu);\n\n\tmainwin = uiNewWindow(\"libui Control Gallery\", 640, 480, 1);\n\tuiWindowSetMargined(mainwin, 1);\n\tuiWindowOnClosing(mainwin, onClosing, NULL);\n\n\tbox = uiNewVerticalBox();\n\tuiBoxSetPadded(box, 1);\n\tuiWindowSetChild(mainwin, uiControl(box));\n\n\thbox = uiNewHorizontalBox();\n\tuiBoxSetPadded(hbox, 1);\n\tuiBoxAppend(box, uiControl(hbox), 1);\n\n\tgroup = uiNewGroup(\"Basic Controls\");\n\tuiGroupSetMargined(group, 1);\n\tuiBoxAppend(hbox, uiControl(group), 0);\n\n\tinner = uiNewVerticalBox();\n\tuiBoxSetPadded(inner, 1);\n\tuiGroupSetChild(group, uiControl(inner));\n\n\tuiBoxAppend(inner,\n\t\tuiControl(uiNewButton(\"Button\")),\n\t\t0);\n\tuiBoxAppend(inner,\n\t\tuiControl(uiNewCheckbox(\"Checkbox\")),\n\t\t0);\n\tentry = uiNewEntry();\n\tuiEntrySetText(entry, \"Entry\");\n\tuiBoxAppend(inner,\n\t\tuiControl(entry),\n\t\t0);\n\tuiBoxAppend(inner,\n\t\tuiControl(uiNewLabel(\"Label\")),\n\t\t0);\n\n\tuiBoxAppend(inner,\n\t\tuiControl(uiNewHorizontalSeparator()),\n\t\t0);\n\n\tuiBoxAppend(inner,\n\t\tuiControl(uiNewDatePicker()),\n\t\t0);\n\tuiBoxAppend(inner,\n\t\tuiControl(uiNewTimePicker()),\n\t\t0);\n\tuiBoxAppend(inner,\n\t\tuiControl(uiNewDateTimePicker()),\n\t\t0);\n\n\tuiBoxAppend(inner,\n\t\tuiControl(uiNewFontButton()),\n\t\t0);\n\n\tuiBoxAppend(inner,\n\t\tuiControl(uiNewColorButton()),\n\t\t0);\n\n\tinner2 = uiNewVerticalBox();\n\tuiBoxSetPadded(inner2, 1);\n\tuiBoxAppend(hbox, uiControl(inner2), 1);\n\n\tgroup = uiNewGroup(\"Numbers\");\n\tuiGroupSetMargined(group, 1);\n\tuiBoxAppend(inner2, uiControl(group), 0);\n\n\tinner = uiNewVerticalBox();\n\tuiBoxSetPadded(inner, 1);\n\tuiGroupSetChild(group, uiControl(inner));\n\n\tspinbox = uiNewSpinbox(0, 100);\n\tuiSpinboxOnChanged(spinbox, onSpinboxChanged, NULL);\n\tuiBoxAppend(inner, uiControl(spinbox), 0);\n\n\tslider = uiNewSlider(0, 100);\n\tuiSliderOnChanged(slider, onSliderChanged, NULL);\n\tuiBoxAppend(inner, uiControl(slider), 0);\n\n\tprogressbar = uiNewProgressBar();\n\tuiBoxAppend(inner, uiControl(progressbar), 0);\n\n\tgroup = uiNewGroup(\"Lists\");\n\tuiGroupSetMargined(group, 1);\n\tuiBoxAppend(inner2, uiControl(group), 0);\n\n\tinner = uiNewVerticalBox();\n\tuiBoxSetPadded(inner, 1);\n\tuiGroupSetChild(group, uiControl(inner));\n\n\tcbox = uiNewCombobox();\n\tuiComboboxAppend(cbox, \"Combobox Item 1\");\n\tuiComboboxAppend(cbox, \"Combobox Item 2\");\n\tuiComboboxAppend(cbox, \"Combobox Item 3\");\n\tuiBoxAppend(inner, uiControl(cbox), 0);\n\n\tecbox = uiNewEditableCombobox();\n\tuiEditableComboboxAppend(ecbox, \"Editable Item 1\");\n\tuiEditableComboboxAppend(ecbox, \"Editable Item 2\");\n\tuiEditableComboboxAppend(ecbox, \"Editable Item 3\");\n\tuiBoxAppend(inner, uiControl(ecbox), 0);\n\n\trb = uiNewRadioButtons();\n\tuiRadioButtonsAppend(rb, \"Radio Button 1\");\n\tuiRadioButtonsAppend(rb, \"Radio Button 2\");\n\tuiRadioButtonsAppend(rb, \"Radio Button 3\");\n\tuiBoxAppend(inner, uiControl(rb), 1);\n\n\ttab = uiNewTab();\n\tuiTabAppend(tab, \"Page 1\", uiControl(uiNewHorizontalBox()));\n\tuiTabAppend(tab, \"Page 2\", uiControl(uiNewHorizontalBox()));\n\tuiTabAppend(tab, \"Page 3\", uiControl(uiNewHorizontalBox()));\n\tuiBoxAppend(inner2, uiControl(tab), 1);\n\n\tuiControlShow(uiControl(mainwin));\n\tuiMain();\n\tuiUninit();\n\treturn 0;\n}\n\n#endif\n"
  },
  {
    "path": "examples/cpp-multithread/main.cpp",
    "content": "// 6 december 2015\n#include <thread>\n#include <chrono>\n#include <mutex>\n#include <condition_variable>\n#include <string.h>\n#include <stdlib.h>\n#include <time.h>\n#include \"../../ui.h\"\nusing namespace std;\n\nuiMultilineEntry *e;\ncondition_variable cv;\nmutex m;\nunique_lock<mutex> ourlock(m);\nthread *timeThread;\n\nvoid sayTime(void *data)\n{\n\tchar *s = (char *) data;\n\n\tuiMultilineEntryAppend(e, s);\n\tdelete s;\n}\n\nvoid threadproc(void)\n{\n\tourlock.lock();\n\twhile (cv.wait_for(ourlock, chrono::seconds(1)) == cv_status::timeout) {\n\t\ttime_t t;\n\t\tchar *base;\n\t\tchar *s;\n\n\t\tt = time(NULL);\n\t\tbase = ctime(&t);\n\t\ts = new char[strlen(base) + 1];\n\t\tstrcpy(s, base);\n\t\tuiQueueMain(sayTime, s);\n\t}\n}\n\nint onClosing(uiWindow *w, void *data)\n{\n\tcv.notify_all();\n\t// C++ throws a hissy fit if you don't do this\n\t// we might as well, to ensure no uiQueueMain() gets in after uiQuit()\n\ttimeThread->join();\n\tuiQuit();\n\treturn 1;\n}\n\nvoid saySomething(uiButton *b, void *data)\n{\n\tuiMultilineEntryAppend(e, \"Saying something\\n\");\n}\n\nint main(void)\n{\n\tuiInitOptions o;\n\tuiWindow *w;\n\tuiBox *b;\n\tuiButton *btn;\n\n\tmemset(&o, 0, sizeof (uiInitOptions));\n\tif (uiInit(&o) != NULL)\n\t\tabort();\n\n\tw = uiNewWindow(\"Hello\", 320, 240, 0);\n\tuiWindowSetMargined(w, 1);\n\n\tb = uiNewVerticalBox();\n\tuiBoxSetPadded(b, 1);\n\tuiWindowSetChild(w, uiControl(b));\n\n\te = uiNewMultilineEntry();\n\tuiMultilineEntrySetReadOnly(e, 1);\n\n\tbtn = uiNewButton(\"Say Something\");\n\tuiButtonOnClicked(btn, saySomething, NULL);\n\tuiBoxAppend(b, uiControl(btn), 0);\n\n\tuiBoxAppend(b, uiControl(e), 1);\n\n\t// timeThread needs to lock ourlock itself - see http://stackoverflow.com/a/34121629/3408572\n\tourlock.unlock();\n\ttimeThread = new thread(threadproc);\n\n\tuiWindowOnClosing(w, onClosing, NULL);\n\tuiControlShow(uiControl(w));\n\tuiMain();\n\treturn 0;\n}\n"
  },
  {
    "path": "examples/datetime/main.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <time.h>\n#include \"../../ui.h\"\n\nuiDateTimePicker *dtboth, *dtdate, *dttime;\n\nconst char *timeFormat(uiDateTimePicker *d)\n{\n\tconst char *fmt;\n\n\tif (d == dtboth)\n\t\tfmt = \"%c\";\n\telse if (d == dtdate)\n\t\tfmt = \"%x\";\n\telse if (d == dttime)\n\t\tfmt = \"%X\";\n\telse\n\t\tfmt = \"\";\n\treturn fmt;\n}\n\nvoid onChanged(uiDateTimePicker *d, void *data)\n{\n\tstruct tm time;\n\tchar buf[64];\n\n\tuiDateTimePickerTime(d, &time);\n\tstrftime(buf, sizeof (buf), timeFormat(d), &time);\n\tuiLabelSetText(uiLabel(data), buf);\n}\n\nvoid onClicked(uiButton *b, void *data)\n{\n\tintptr_t now;\n\ttime_t t;\n\tstruct tm tmbuf;\n\n\tnow = (intptr_t) data;\n\tt = 0;\n\tif (now)\n\t\tt = time(NULL);\n\ttmbuf = *localtime(&t);\n\n\tif (now) {\n\t\tuiDateTimePickerSetTime(dtdate, &tmbuf);\n\t\tuiDateTimePickerSetTime(dttime, &tmbuf);\n\t} else\n\t\tuiDateTimePickerSetTime(dtboth, &tmbuf);\n}\n\nint onClosing(uiWindow *w, void *data)\n{\n\tuiQuit();\n\treturn 1;\n}\n\nint main(void)\n{\n\tuiInitOptions o;\n\tconst char *err;\n\tuiWindow *w;\n\tuiGrid *g;\n\tuiLabel *l;\n\tuiButton *b;\n\n\tmemset(&o, 0, sizeof (uiInitOptions));\n\terr = uiInit(&o);\n\tif (err != NULL) {\n\t\tfprintf(stderr, \"error initializing ui: %s\\n\", err);\n\t\tuiFreeInitError(err);\n\t\treturn 1;\n\t}\n\n\tw = uiNewWindow(\"Date / Time\", 320, 240, 0);\n\tuiWindowSetMargined(w, 1);\n\n\tg = uiNewGrid();\n\tuiGridSetPadded(g, 1);\n\tuiWindowSetChild(w, uiControl(g));\n\n\tdtboth = uiNewDateTimePicker();\n\tdtdate = uiNewDatePicker();\n\tdttime = uiNewTimePicker();\n\n\tuiGridAppend(g, uiControl(dtboth),\n\t\t0, 0, 2, 1,\n\t\t1, uiAlignFill, 0, uiAlignFill);\n\tuiGridAppend(g, uiControl(dtdate),\n\t\t0, 1, 1, 1,\n\t\t1, uiAlignFill, 0, uiAlignFill);\n\tuiGridAppend(g, uiControl(dttime),\n\t\t1, 1, 1, 1,\n\t\t1, uiAlignFill, 0, uiAlignFill);\n\n\tl = uiNewLabel(\"\");\n\tuiGridAppend(g, uiControl(l),\n\t\t0, 2, 2, 1,\n\t\t1, uiAlignCenter, 0, uiAlignFill);\n\tuiDateTimePickerOnChanged(dtboth, onChanged, l);\n\tl = uiNewLabel(\"\");\n\tuiGridAppend(g, uiControl(l),\n\t\t0, 3, 1, 1,\n\t\t1, uiAlignCenter, 0, uiAlignFill);\n\tuiDateTimePickerOnChanged(dtdate, onChanged, l);\n\tl = uiNewLabel(\"\");\n\tuiGridAppend(g, uiControl(l),\n\t\t1, 3, 1, 1,\n\t\t1, uiAlignCenter, 0, uiAlignFill);\n\tuiDateTimePickerOnChanged(dttime, onChanged, l);\n\n\tb = uiNewButton(\"Now\");\n\tuiButtonOnClicked(b, onClicked, (void *) 1);\n\tuiGridAppend(g, uiControl(b),\n\t\t0, 4, 1, 1,\n\t\t1, uiAlignFill, 1, uiAlignEnd);\n\tb = uiNewButton(\"Unix epoch\");\n\tuiButtonOnClicked(b, onClicked, (void *) 0);\n\tuiGridAppend(g, uiControl(b),\n\t\t1, 4, 1, 1,\n\t\t1, uiAlignFill, 1, uiAlignEnd);\n\n\tuiWindowOnClosing(w, onClosing, NULL);\n\tuiControlShow(uiControl(w));\n\tuiMain();\n\treturn 0;\n}\n"
  },
  {
    "path": "examples/drawtext/main.c",
    "content": "// 10 march 2018\n#include <stdio.h>\n#include <string.h>\n#include \"../../ui.h\"\n\nuiWindow *mainwin;\nuiArea *area;\nuiAreaHandler handler;\nuiFontButton *fontButton;\nuiCombobox *alignment;\n\nuiAttributedString *attrstr;\n\nstatic void appendWithAttribute(const char *what, uiAttribute *attr, uiAttribute *attr2)\n{\n\tsize_t start, end;\n\n\tstart = uiAttributedStringLen(attrstr);\n\tend = start + strlen(what);\n\tuiAttributedStringAppendUnattributed(attrstr, what);\n\tuiAttributedStringSetAttribute(attrstr, attr, start, end);\n\tif (attr2 != NULL)\n\t\tuiAttributedStringSetAttribute(attrstr, attr2, start, end);\n}\n\nstatic void makeAttributedString(void)\n{\n\tuiAttribute *attr, *attr2;\n\tuiOpenTypeFeatures *otf;\n\n\tattrstr = uiNewAttributedString(\n\t\t\"Drawing strings with libui is done with the uiAttributedString and uiDrawTextLayout objects.\\n\"\n\t\t\"uiAttributedString lets you have a variety of attributes: \");\n\n\tattr = uiNewFamilyAttribute(\"Courier New\");\n\tappendWithAttribute(\"font family\", attr, NULL);\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tattr = uiNewSizeAttribute(18);\n\tappendWithAttribute(\"font size\", attr, NULL);\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tattr = uiNewWeightAttribute(uiTextWeightBold);\n\tappendWithAttribute(\"font weight\", attr, NULL);\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tattr = uiNewItalicAttribute(uiTextItalicItalic);\n\tappendWithAttribute(\"font italicness\", attr, NULL);\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tattr = uiNewStretchAttribute(uiTextStretchCondensed);\n\tappendWithAttribute(\"font stretch\", attr, NULL);\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tattr = uiNewColorAttribute(0.75, 0.25, 0.5, 0.75);\n\tappendWithAttribute(\"text color\", attr, NULL);\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tattr = uiNewBackgroundAttribute(0.5, 0.5, 0.25, 0.5);\n\tappendWithAttribute(\"text background color\", attr, NULL);\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\n\tattr = uiNewUnderlineAttribute(uiUnderlineSingle);\n\tappendWithAttribute(\"underline style\", attr, NULL);\n\tuiAttributedStringAppendUnattributed(attrstr, \", \");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \"and \");\n\tattr = uiNewUnderlineAttribute(uiUnderlineDouble);\n\tattr2 = uiNewUnderlineColorAttribute(uiUnderlineColorCustom, 1.0, 0.0, 0.5, 1.0);\n\tappendWithAttribute(\"underline color\", attr, attr2);\n\tuiAttributedStringAppendUnattributed(attrstr, \". \");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \"Furthermore, there are attributes allowing for \");\n\tattr = uiNewUnderlineAttribute(uiUnderlineSuggestion);\n\tattr2 = uiNewUnderlineColorAttribute(uiUnderlineColorSpelling, 0, 0, 0, 0);\n\tappendWithAttribute(\"special underlines for indicating spelling errors\", attr, attr2);\n\tuiAttributedStringAppendUnattributed(attrstr, \" (and other types of errors) \");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \"and control over OpenType features such as ligatures (for instance, \");\n\totf = uiNewOpenTypeFeatures();\n\tuiOpenTypeFeaturesAdd(otf, 'l', 'i', 'g', 'a', 0);\n\tattr = uiNewFeaturesAttribute(otf);\n\tappendWithAttribute(\"afford\", attr, NULL);\n\tuiAttributedStringAppendUnattributed(attrstr, \" vs. \");\n\tuiOpenTypeFeaturesAdd(otf, 'l', 'i', 'g', 'a', 1);\n\tattr = uiNewFeaturesAttribute(otf);\n\tappendWithAttribute(\"afford\", attr, NULL);\n\tuiFreeOpenTypeFeatures(otf);\n\tuiAttributedStringAppendUnattributed(attrstr, \").\\n\");\n\n\tuiAttributedStringAppendUnattributed(attrstr, \"Use the controls opposite to the text to control properties of the text.\");\n}\n\nstatic void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *p)\n{\n\tuiDrawTextLayout *textLayout;\n\tuiFontDescriptor defaultFont;\n\tuiDrawTextLayoutParams params;\n\n\tparams.String = attrstr;\n\tuiFontButtonFont(fontButton, &defaultFont);\n\tparams.DefaultFont = &defaultFont;\n\tparams.Width = p->AreaWidth;\n\tparams.Align = (uiDrawTextAlign) uiComboboxSelected(alignment);\n\ttextLayout = uiDrawNewTextLayout(&params);\n\tuiDrawText(p->Context, textLayout, 0, 0);\n\tuiDrawFreeTextLayout(textLayout);\n\tuiFreeFontButtonFont(&defaultFont);\n}\n\nstatic void handlerMouseEvent(uiAreaHandler *a, uiArea *area, uiAreaMouseEvent *e)\n{\n\t// do nothing\n}\n\nstatic void handlerMouseCrossed(uiAreaHandler *ah, uiArea *a, int left)\n{\n\t// do nothing\n}\n\nstatic void handlerDragBroken(uiAreaHandler *ah, uiArea *a)\n{\n\t// do nothing\n}\n\nstatic int handlerKeyEvent(uiAreaHandler *ah, uiArea *a, uiAreaKeyEvent *e)\n{\n\t// reject all keys\n\treturn 0;\n}\n\nstatic void onFontChanged(uiFontButton *b, void *data)\n{\n\tuiAreaQueueRedrawAll(area);\n}\n\nstatic void onComboboxSelected(uiCombobox *b, void *data)\n{\n\tuiAreaQueueRedrawAll(area);\n}\n\nstatic int onClosing(uiWindow *w, void *data)\n{\n\tuiControlDestroy(uiControl(mainwin));\n\tuiQuit();\n\treturn 0;\n}\n\nstatic int shouldQuit(void *data)\n{\n\tuiControlDestroy(uiControl(mainwin));\n\treturn 1;\n}\n\nint main(void)\n{\n\tuiInitOptions o;\n\tconst char *err;\n\tuiBox *hbox, *vbox;\n\tuiForm *form;\n\n\thandler.Draw = handlerDraw;\n\thandler.MouseEvent = handlerMouseEvent;\n\thandler.MouseCrossed = handlerMouseCrossed;\n\thandler.DragBroken = handlerDragBroken;\n\thandler.KeyEvent = handlerKeyEvent;\n\n\tmemset(&o, 0, sizeof (uiInitOptions));\n\terr = uiInit(&o);\n\tif (err != NULL) {\n\t\tfprintf(stderr, \"error initializing ui: %s\\n\", err);\n\t\tuiFreeInitError(err);\n\t\treturn 1;\n\t}\n\n\tuiOnShouldQuit(shouldQuit, NULL);\n\n\tmakeAttributedString();\n\n\tmainwin = uiNewWindow(\"libui Text-Drawing Example\", 640, 480, 1);\n\tuiWindowSetMargined(mainwin, 1);\n\tuiWindowOnClosing(mainwin, onClosing, NULL);\n\n\thbox = uiNewHorizontalBox();\n\tuiBoxSetPadded(hbox, 1);\n\tuiWindowSetChild(mainwin, uiControl(hbox));\n\n\tvbox = uiNewVerticalBox();\n\tuiBoxSetPadded(vbox, 1);\n\tuiBoxAppend(hbox, uiControl(vbox), 0);\n\n\tfontButton = uiNewFontButton();\n\tuiFontButtonOnChanged(fontButton, onFontChanged, NULL);\n\tuiBoxAppend(vbox, uiControl(fontButton), 0);\n\n\tform = uiNewForm();\n\tuiFormSetPadded(form, 1);\n\t// 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?\n\tuiBoxAppend(vbox, uiControl(form), 0);\n\n\talignment = uiNewCombobox();\n\t// note that the items match with the values of the uiDrawTextAlign values\n\tuiComboboxAppend(alignment, \"Left\");\n\tuiComboboxAppend(alignment, \"Center\");\n\tuiComboboxAppend(alignment, \"Right\");\n\tuiComboboxSetSelected(alignment, 0);\t\t// start with left alignment\n\tuiComboboxOnSelected(alignment, onComboboxSelected, NULL);\n\tuiFormAppend(form, \"Alignment\", uiControl(alignment), 0);\n\n\tarea = uiNewArea(&handler);\n\tuiBoxAppend(hbox, uiControl(area), 1);\n\n\tuiControlShow(uiControl(mainwin));\n\tuiMain();\n\tuiFreeAttributedString(attrstr);\n\tuiUninit();\n\treturn 0;\n}\n"
  },
  {
    "path": "examples/example.manifest",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">\n<assemblyIdentity\n    version=\"1.0.0.0\"\n    processorArchitecture=\"*\"\n    name=\"CompanyName.ProductName.YourApplication\"\n    type=\"win32\"\n/>\n<description>Your application description here.</description>\n<!-- do NOT include the comctl6 dependency here; this lets us find bugs related to theming -->\n<compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\">\n    <application>\n        <!--The ID below indicates application support for Windows Vista -->\n        <supportedOS Id=\"{e2011457-1546-43c5-a5fe-008deee3d3f0}\"/>\n        <!--The ID below indicates application support for Windows 7 -->\n        <supportedOS Id=\"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}\"/>\n    </application>\n</compatibility>\n</assembly>\n\n"
  },
  {
    "path": "examples/example.static.manifest",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">\n<assemblyIdentity\n    version=\"1.0.0.0\"\n    processorArchitecture=\"*\"\n    name=\"CompanyName.ProductName.YourApplication\"\n    type=\"win32\"\n/>\n<description>Your application description here.</description>\n<!-- we DO need comctl6 in the static case -->\n<dependency>\n    <dependentAssembly>\n        <assemblyIdentity\n            type=\"win32\"\n            name=\"Microsoft.Windows.Common-Controls\"\n            version=\"6.0.0.0\"\n            processorArchitecture=\"*\"\n            publicKeyToken=\"6595b64144ccf1df\"\n            language=\"*\"\n        />\n    </dependentAssembly>\n</dependency>\n<compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\">\n    <application>\n        <!--The ID below indicates application support for Windows Vista -->\n        <supportedOS Id=\"{e2011457-1546-43c5-a5fe-008deee3d3f0}\"/>\n        <!--The ID below indicates application support for Windows 7 -->\n        <supportedOS Id=\"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}\"/>\n    </application>\n</compatibility>\n</assembly>\n\n"
  },
  {
    "path": "examples/histogram/main.c",
    "content": "// 13 october 2015\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <time.h>\n#include \"../../ui.h\"\n\nuiWindow *mainwin;\nuiArea *histogram;\nuiAreaHandler handler;\nuiSpinbox *datapoints[10];\nuiColorButton *colorButton;\nint currentPoint = -1;\n\n// some metrics\n#define xoffLeft 20\t\t\t/* histogram margins */\n#define yoffTop 20\n#define xoffRight 20\n#define yoffBottom 20\n#define pointRadius 5\n\n// helper to quickly set a brush color\nstatic void setSolidBrush(uiDrawBrush *brush, uint32_t color, double alpha)\n{\n\tuint8_t component;\n\n\tbrush->Type = uiDrawBrushTypeSolid;\n\tcomponent = (uint8_t) ((color >> 16) & 0xFF);\n\tbrush->R = ((double) component) / 255;\n\tcomponent = (uint8_t) ((color >> 8) & 0xFF);\n\tbrush->G = ((double) component) / 255;\n\tcomponent = (uint8_t) (color & 0xFF);\n\tbrush->B = ((double) component) / 255;\n\tbrush->A = alpha;\n}\n\n// and some colors\n// names and values from https://msdn.microsoft.com/en-us/library/windows/desktop/dd370907%28v=vs.85%29.aspx\n#define colorWhite 0xFFFFFF\n#define colorBlack 0x000000\n#define colorDodgerBlue 0x1E90FF\n\nstatic void pointLocations(double width, double height, double *xs, double *ys)\n{\n\tdouble xincr, yincr;\n\tint i, n;\n\n\txincr = width / 9;\t\t// 10 - 1 to make the last point be at the end\n\tyincr = height / 100;\n\n\tfor (i = 0; i < 10; i++) {\n\t\t// get the value of the point\n\t\tn = uiSpinboxValue(datapoints[i]);\n\t\t// because y=0 is the top but n=0 is the bottom, we need to flip\n\t\tn = 100 - n;\n\t\txs[i] = xincr * i;\n\t\tys[i] = yincr * n;\n\t}\n}\n\nstatic uiDrawPath *constructGraph(double width, double height, int extend)\n{\n\tuiDrawPath *path;\n\tdouble xs[10], ys[10];\n\tint i;\n\n\tpointLocations(width, height, xs, ys);\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\n\tuiDrawPathNewFigure(path, xs[0], ys[0]);\n\tfor (i = 1; i < 10; i++)\n\t\tuiDrawPathLineTo(path, xs[i], ys[i]);\n\n\tif (extend) {\n\t\tuiDrawPathLineTo(path, width, height);\n\t\tuiDrawPathLineTo(path, 0, height);\n\t\tuiDrawPathCloseFigure(path);\n\t}\n\n\tuiDrawPathEnd(path);\n\treturn path;\n}\n\nstatic void graphSize(double clientWidth, double clientHeight, double *graphWidth, double *graphHeight)\n{\n\t*graphWidth = clientWidth - xoffLeft - xoffRight;\n\t*graphHeight = clientHeight - yoffTop - yoffBottom;\n}\n\nstatic void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *p)\n{\n\tuiDrawPath *path;\n\tuiDrawBrush brush;\n\tuiDrawStrokeParams sp;\n\tuiDrawMatrix m;\n\tdouble graphWidth, graphHeight;\n\tdouble graphR, graphG, graphB, graphA;\n\n\t// fill the area with white\n\tsetSolidBrush(&brush, colorWhite, 1.0);\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathAddRectangle(path, 0, 0, p->AreaWidth, p->AreaHeight);\n\tuiDrawPathEnd(path);\n\tuiDrawFill(p->Context, path, &brush);\n\tuiDrawFreePath(path);\n\n\t// figure out dimensions\n\tgraphSize(p->AreaWidth, p->AreaHeight, &graphWidth, &graphHeight);\n\n\t// clear sp to avoid passing garbage to uiDrawStroke()\n\t// for example, we don't use dashing\n\tmemset(&sp, 0, sizeof (uiDrawStrokeParams));\n\n\t// make a stroke for both the axes and the histogram line\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.Thickness = 2;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\n\t// draw the axes\n\tsetSolidBrush(&brush, colorBlack, 1.0);\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigure(path,\n\t\txoffLeft, yoffTop);\n\tuiDrawPathLineTo(path,\n\t\txoffLeft, yoffTop + graphHeight);\n\tuiDrawPathLineTo(path,\n\t\txoffLeft + graphWidth, yoffTop + graphHeight);\n\tuiDrawPathEnd(path);\n\tuiDrawStroke(p->Context, path, &brush, &sp);\n\tuiDrawFreePath(path);\n\n\t// now transform the coordinate space so (0, 0) is the top-left corner of the graph\n\tuiDrawMatrixSetIdentity(&m);\n\tuiDrawMatrixTranslate(&m, xoffLeft, yoffTop);\n\tuiDrawTransform(p->Context, &m);\n\n\t// now get the color for the graph itself and set up the brush\n\tuiColorButtonColor(colorButton, &graphR, &graphG, &graphB, &graphA);\n\tbrush.Type = uiDrawBrushTypeSolid;\n\tbrush.R = graphR;\n\tbrush.G = graphG;\n\tbrush.B = graphB;\n\t// we set brush->A below to different values for the fill and stroke\n\n\t// now create the fill for the graph below the graph line\n\tpath = constructGraph(graphWidth, graphHeight, 1);\n\tbrush.A = graphA / 2;\n\tuiDrawFill(p->Context, path, &brush);\n\tuiDrawFreePath(path);\n\n\t// now draw the histogram line\n\tpath = constructGraph(graphWidth, graphHeight, 0);\n\tbrush.A = graphA;\n\tuiDrawStroke(p->Context, path, &brush, &sp);\n\tuiDrawFreePath(path);\n\n\t// now draw the point being hovered over\n\tif (currentPoint != -1) {\n\t\tdouble xs[10], ys[10];\n\n\t\tpointLocations(graphWidth, graphHeight, xs, ys);\n\t\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\t\tuiDrawPathNewFigureWithArc(path,\n\t\t\txs[currentPoint], ys[currentPoint],\n\t\t\tpointRadius,\n\t\t\t0, 6.23,\t\t// TODO pi\n\t\t\t0);\n\t\tuiDrawPathEnd(path);\n\t\t// use the same brush as for the histogram lines\n\t\tuiDrawFill(p->Context, path, &brush);\n\t\tuiDrawFreePath(path);\n\t}\n}\n\nstatic int inPoint(double x, double y, double xtest, double ytest)\n{\n\t// TODO switch to using a matrix\n\tx -= xoffLeft;\n\ty -= yoffTop;\n\treturn (x >= xtest - pointRadius) &&\n\t\t(x <= xtest + pointRadius) &&\n\t\t(y >= ytest - pointRadius) &&\n\t\t(y <= ytest + pointRadius);\n}\n\nstatic void handlerMouseEvent(uiAreaHandler *a, uiArea *area, uiAreaMouseEvent *e)\n{\n\tdouble graphWidth, graphHeight;\n\tdouble xs[10], ys[10];\n\tint i;\n\n\tgraphSize(e->AreaWidth, e->AreaHeight, &graphWidth, &graphHeight);\n\tpointLocations(graphWidth, graphHeight, xs, ys);\n\n\tfor (i = 0; i < 10; i++)\n\t\tif (inPoint(e->X, e->Y, xs[i], ys[i]))\n\t\t\tbreak;\n\tif (i == 10)\t\t// not in a point\n\t\ti = -1;\n\n\tcurrentPoint = i;\n\t// TODO only redraw the relevant area\n\tuiAreaQueueRedrawAll(histogram);\n}\n\nstatic void handlerMouseCrossed(uiAreaHandler *ah, uiArea *a, int left)\n{\n\t// do nothing\n}\n\nstatic void handlerDragBroken(uiAreaHandler *ah, uiArea *a)\n{\n\t// do nothing\n}\n\nstatic int handlerKeyEvent(uiAreaHandler *ah, uiArea *a, uiAreaKeyEvent *e)\n{\n\t// reject all keys\n\treturn 0;\n}\n\nstatic void onDatapointChanged(uiSpinbox *s, void *data)\n{\n\tuiAreaQueueRedrawAll(histogram);\n}\n\nstatic void onColorChanged(uiColorButton *b, void *data)\n{\n\tuiAreaQueueRedrawAll(histogram);\n}\n\nstatic int onClosing(uiWindow *w, void *data)\n{\n\tuiControlDestroy(uiControl(mainwin));\n\tuiQuit();\n\treturn 0;\n}\n\nstatic int shouldQuit(void *data)\n{\n\tuiControlDestroy(uiControl(mainwin));\n\treturn 1;\n}\n\nint main(void)\n{\n\tuiInitOptions o;\n\tconst char *err;\n\tuiBox *hbox, *vbox;\n\tint i;\n\tuiDrawBrush brush;\n\n\thandler.Draw = handlerDraw;\n\thandler.MouseEvent = handlerMouseEvent;\n\thandler.MouseCrossed = handlerMouseCrossed;\n\thandler.DragBroken = handlerDragBroken;\n\thandler.KeyEvent = handlerKeyEvent;\n\n\tmemset(&o, 0, sizeof (uiInitOptions));\n\terr = uiInit(&o);\n\tif (err != NULL) {\n\t\tfprintf(stderr, \"error initializing ui: %s\\n\", err);\n\t\tuiFreeInitError(err);\n\t\treturn 1;\n\t}\n\n\tuiOnShouldQuit(shouldQuit, NULL);\n\n\tmainwin = uiNewWindow(\"libui Histogram Example\", 640, 480, 1);\n\tuiWindowSetMargined(mainwin, 1);\n\tuiWindowOnClosing(mainwin, onClosing, NULL);\n\n\thbox = uiNewHorizontalBox();\n\tuiBoxSetPadded(hbox, 1);\n\tuiWindowSetChild(mainwin, uiControl(hbox));\n\n\tvbox = uiNewVerticalBox();\n\tuiBoxSetPadded(vbox, 1);\n\tuiBoxAppend(hbox, uiControl(vbox), 0);\n\n\tsrand(time(NULL));\n\tfor (i = 0; i < 10; i++) {\n\t\tdatapoints[i] = uiNewSpinbox(0, 100);\n\t\tuiSpinboxSetValue(datapoints[i], rand() % 101);\n\t\tuiSpinboxOnChanged(datapoints[i], onDatapointChanged, NULL);\n\t\tuiBoxAppend(vbox, uiControl(datapoints[i]), 0);\n\t}\n\n\tcolorButton = uiNewColorButton();\n\t// TODO inline these\n\tsetSolidBrush(&brush, colorDodgerBlue, 1.0);\n\tuiColorButtonSetColor(colorButton,\n\t\tbrush.R,\n\t\tbrush.G,\n\t\tbrush.B,\n\t\tbrush.A);\n\tuiColorButtonOnChanged(colorButton, onColorChanged, NULL);\n\tuiBoxAppend(vbox, uiControl(colorButton), 0);\n\n\thistogram = uiNewArea(&handler);\n\tuiBoxAppend(hbox, uiControl(histogram), 1);\n\n\tuiControlShow(uiControl(mainwin));\n\tuiMain();\n\tuiUninit();\n\treturn 0;\n}\n"
  },
  {
    "path": "examples/meson.build",
    "content": "# 24 march 2019\n\nlibui_example_sources = []\nlibui_example_link_args = []\nlibui_example_cpp_extra_args = []\nif libui_OS == 'windows'\n\tlibui_example_manifest = 'example.manifest'\n\tif libui_mode == 'static'\n\t\tlibui_example_manifest = 'example.static.manifest'\n\tendif\n\tlibui_example_sources += [\n\t\twindows.compile_resources('resources.rc',\n\t\t\targs: libui_manifest_args,\n\t\t\tdepend_files: [libui_example_manifest]),\n\t]\n\t# because Microsoft's toolchain is dumb\n\tif libui_MSVC\n\t\tlibui_example_link_args += ['/ENTRY:mainCRTStartup']\n\tendif\nelif libui_OS == 'darwin'\n\t# since we use a deployment target of 10.8, the non-C++11-compliant libstdc++ is chosen by default; we need C++11\n\t# see issue #302 for more details\n\tlibui_example_cpp_extra_args += ['--stdlib=libc++']\nendif\n\nlibui_examples = {\n\t'controlgallery': {\n\t\t'sources':\t\t['controlgallery/main.c'],\n\t},\n\t'histogram': {\n\t\t'sources':\t\t['histogram/main.c'],\n\t},\n\t'cpp-multithread': {\n\t\t'sources':\t\t['cpp-multithread/main.cpp'],\n\t\t'deps':\t\t[\n\t\t\tdependency('threads',\n\t\t\t\trequired: true),\n\t\t],\n\t\t'cpp_args':\tlibui_example_cpp_extra_args,\n\t\t'link_args':\tlibui_example_cpp_extra_args,\n\t},\n\t'drawtext': {\n\t\t'sources':\t\t['drawtext/main.c'],\n\t},\n\t'timer': {\n\t\t'sources':\t\t['timer/main.c'],\n\t},\n\t'datetime': {\n\t\t'sources':\t\t['datetime/main.c'],\n\t},\n}\nforeach name, args : libui_examples\n\t# TODO once we upgrade to 0.49.0, add pie: true\n\texecutable(name, args['sources'] + libui_example_sources,\n\t\tdependencies: args.get('deps', []) + libui_binary_deps,\n\t\tlink_with: libui_libui,\n\t\tcpp_args: args.get('cpp_args', []),\n\t\tlink_args: args.get('link_args', []) + libui_example_link_args,\n\t\tgui_app: false,\n\t\tinstall: false)\nendforeach\n"
  },
  {
    "path": "examples/resources.rc",
    "content": "// 30 may 2015\n\n// this is a UTF-8 file\n#pragma code_page(65001)\n\n// this is the Common Controls 6 manifest\n// TODO set up the string values here\n// 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\n#ifndef _UI_STATIC\n1 24 \"example.manifest\"\n#else\n1 24 \"example.static.manifest\"\n#endif\n"
  },
  {
    "path": "examples/timer/main.c",
    "content": "#include <string.h>\n#include <stdlib.h>\n#include <time.h>\n#include \"../../ui.h\"\n\nuiMultilineEntry *e;\n\nint sayTime(void *data)\n{\n\ttime_t t;\n\tchar *s;\n\n\tt = time(NULL);\n\ts = ctime(&t);\n\n\tuiMultilineEntryAppend(e, s);\n\treturn 1;\n}\n\nint onClosing(uiWindow *w, void *data)\n{\n\tuiQuit();\n\treturn 1;\n}\n\nvoid saySomething(uiButton *b, void *data)\n{\n\tuiMultilineEntryAppend(e, \"Saying something\\n\");\n}\n\nint main(void)\n{\n\tuiInitOptions o;\n\tuiWindow *w;\n\tuiBox *b;\n\tuiButton *btn;\n\n\tmemset(&o, 0, sizeof (uiInitOptions));\n\tif (uiInit(&o) != NULL)\n\t\tabort();\n\n\tw = uiNewWindow(\"Hello\", 320, 240, 0);\n\tuiWindowSetMargined(w, 1);\n\n\tb = uiNewVerticalBox();\n\tuiBoxSetPadded(b, 1);\n\tuiWindowSetChild(w, uiControl(b));\n\n\te = uiNewMultilineEntry();\n\tuiMultilineEntrySetReadOnly(e, 1);\n\n\tbtn = uiNewButton(\"Say Something\");\n\tuiButtonOnClicked(btn, saySomething, NULL);\n\tuiBoxAppend(b, uiControl(btn), 0);\n\n\tuiBoxAppend(b, uiControl(e), 1);\n\n\tuiTimer(1000, sayTime, NULL);\n\n\tuiWindowOnClosing(w, onClosing, NULL);\n\tuiControlShow(uiControl(w));\n\tuiMain();\n\treturn 0;\n}\n"
  },
  {
    "path": "meson.build",
    "content": "# 17 march 2019\n\n# TODO I'm not sure how to allow building 32-bit instead of 64-bit with meson\n\n# TODO remove cpp from this list once https://github.com/mesonbuild/meson/issues/5181 is settled; move it to the OS checks and under cpp-multithread\nproject('libui', ['c', 'cpp'],\n\tmeson_version: '>=0.48.0',\n\tdefault_options: [\n\t\t'buildtype=debug',\t\t\t\t# build debug by default\n\t\t'default_library=shared',\t\t\t# build shared libraries by default\n\t\t'layout=flat',\t\t\t\t\t# keep all outputs together by default\n\n\t\t# these are forced options, but meson doesn't let me set these up separately before I call project() (TODO https://github.com/mesonbuild/meson/issues/5179)\n\t\t'warning_level=3',\t\t\t\t# always max warnings\n\t\t'b_pch=false',\t\t\t\t\t# we don't want precompiled headers\n\t\t'b_staticpic=true',\t\t\t\t# use PIC even for static libraries\n\t\t'c_std=c99',\t\t\t\t\t# strict C99\n\t\t'c_winlibs=',\t\t\t\t\t# we define our own Windows libraries\n\t\t'cpp_std=c++11',\t\t\t\t# strict C++11\n\t\t'cpp_eh=sc',\t\t\t\t\t# shut the compiler up in some cases\n\t\t'cpp_winlibs=',\t\t\t\t\t# likewise as with c_winlibs\n\t],\n\tlicense: 'MIT')\n\n# TODO after https://github.com/mesonbuild/meson/issues/5179 is settled, remove these\nlibui_OS = host_machine.system()\nlibui_MSVC = meson.get_compiler('c').get_id() == 'msvc'\n\n# TODO switch to tabs; the spaces are just so I can share this file while I'm writing it\nlibui_forced_options = {\n\t'warning_level': '3',     # always max warnings\n\t'b_pch':         'false', # we don't want precompiled headers\n\t'b_staticpic':   'true',  # use PIC even for static libraries\n\t'c_std':         'c99',   # strict C99\n\t'c_winlibs':     '[]',    # we define our own Windows libraries\n\t'cpp_std':       'c++11', # strict C++11\n\t'cpp_eh':        'sc',    # shut the compiler up in some cases\n\t'cpp_winlibs':   '[]',    # likewise as with c_winlibs\n}\nforeach name, value : libui_forced_options\n\t# TODO rewrite this when https://github.com/mesonbuild/meson/issues/5181 is settled\n\tif not ((name == 'c_winlibs' or name == 'cpp_eh' or name == 'cpp_winlibs') and not libui_MSVC) and not (name == 'c_std' and libui_MSVC)\n\t\tactual = '@0@'.format(get_option(name))\n\t\tif actual != value\n\t\t\terror('sorry, but libui requires that option ' + name + ' has the default value ' + value)\n\t\tendif\n\tendif\nendforeach\n\nlibui_OS = host_machine.system()\nlibui_MSVC = meson.get_compiler('c').get_id() == 'msvc'\n\nif libui_OS == 'darwin'\n\tadd_languages('objc',\n\t\trequired: true)\nendif\n\nlibui_mode = get_option('default_library')\nif libui_mode == 'both'\n\terror('sorry, but libui does not support building both shared and static libraries at the same time, because Windows resource file rules differ between the two')\nendif\n\nlibui_is_debug = get_option('buildtype').startswith('debug')\n\nlibui_project_compile_args = []\nlibui_project_link_args = []\n\nif libui_OS == 'darwin'\n\tlibui_macosx_version_min = '-mmacosx-version-min=10.8'\n\tlibui_project_compile_args += [libui_macosx_version_min]\n\tlibui_project_link_args += [libui_macosx_version_min]\nendif\n\nif libui_MSVC\n\t# TODO subsystem version\n\n\tlibui_project_compile_args += [\n\t\t'/wd4100',\n\t\t'/bigobj',\n\t]\n\tif libui_is_debug\n\t\tlibui_project_compile_args += ['/RTC1', '/RTCs', '/RTCu']\n\tendif\n\n\tlibui_project_link_args += [\n\t\t'/LARGEADDRESSAWARE',\n\t\t'/INCREMENTAL:NO',\n\t\t'/MANIFEST:NO',\n\t]\n\n\t# TODO autogenerate a .def file?\nelse\n\tlibui_project_compile_args += [\n\t\t'-Wno-unused-parameter',\n\t\t'-Wno-switch',\n\t]\n\n\tif libui_OS == 'windows'\n\t\t# don't require shipping the MinGW-w64 DLLs\n\t\tlibui_project_link_args += [\n\t\t\t'-static',\n\t\t\t'-static-libgcc',\n\t\t\t'-static-libstdc++',\n\t\t]\n\tendif\nendif\n\n# TODO come up with a better way to do this, both for libui (the compiler define, used by winapi.hpp, and the manifest args) and for the binaries (the manifest args)\n# TODO (after the above TODO is resolved) move that below the part below that actually adds these arguments\nlibui_manifest_args = []\nif libui_mode == 'static'\n\tlibui_project_compile_args += ['-D_UI_STATIC']\n\tlibui_manifest_args = ['-D_UI_STATIC']\nendif\n\nadd_project_arguments(libui_project_compile_args,\n\tlanguage: ['c', 'cpp', 'objc'])\nadd_project_link_arguments(libui_project_link_args,\n\tlanguage: ['c', 'cpp', 'objc'])\n\n# TODO:\n# meson determins whether -Wl,--no-undefined is provided via\n# built-in option b_lundef, and it's true by default, which is what\n# we want (so I don't make mistakes like asking for unknown\n# functions in my dependencies). However, meson also is smart\n# about specifying this properly on systems that don't support it, like\n# FreeBSD (where I had the comment \"figure out why FreeBSD\n# follows linked libraries here\" when I was on cmake) and OpenBSD\n# (according to someone on freenode #mesonbuild), but it isn't clear\n# whether it's just ignored or if the value is forced to false.\n# Therefore, once this is determined, we can uncomment the\n# following.\nlibui_libui_link_args = []\n#if not libui_MSVC and get_option(\"b_lundef\")\n\t# TODO what should this be on MSVC?\n#\tlibui_libui_link_args += ['-Wl,--no-allow-shlib-undefined']\n#endif\n\nlibui_sources = []\nlibui_deps = []\nlibui_soversion = ''\nlibui_rpath = ''\nsubdir('common')\nif libui_OS == 'windows'\n\tsubdir('windows')\n\tinstall_headers('ui_windows.h')\nelif libui_OS == 'darwin'\n\tsubdir('darwin')\n\tinstall_headers('ui_darwin.h')\nelse\n\tsubdir('unix')\n\tinstall_headers('ui_unix.h')\nendif\nlibui_libui = library('ui', libui_sources,\n\tdependencies: libui_deps,\n\tbuild_rpath: libui_rpath,\n\tinstall_rpath: libui_rpath,\n\tname_prefix: 'lib',\t\t# always call it libui, even in Windows DLLs\n\tinstall: true,\n\tgnu_symbol_visibility: 'hidden',\n\tc_args: ['-Dlibui_EXPORTS'],\n\tcpp_args: ['-Dlibui_EXPORTS'],\n\tobjc_args: ['-Dlibui_EXPORTS'],\n\tlink_args: libui_libui_link_args,\n\tsoversion: libui_soversion,\n\tdarwin_versions: [])\t\t# TODO\ninstall_headers('ui.h')\n\n# TODO when the API is stable enough to be versioned, create a pkg-config file (https://mesonbuild.com/Pkgconfig-module.html) and a declare_dependency() section too\n\nlibui_binary_deps = []\nif libui_mode == 'static'\n\tlibui_binary_deps = libui_deps\nendif\nsubdir('test')\nsubdir('examples')\n"
  },
  {
    "path": "stats.osxdrawtext",
    "content": "diff --git a/darwin/drawtext.m b/darwin/drawtext.m\nindex a84b68b..c95bbde 100644\n--- a/darwin/drawtext.m\n+++ b/darwin/drawtext.m\n@@ -108,7 +108,7 @@ static CFAttributedStringRef attrstrToCoreFoundation(uiAttributedString *s, uiDr\n \t\tboundsNoLeading = CTLineGetBoundsWithOptions(line, kCTLineBoundsExcludeTypographicLeading);\n \n \t\t// this is equivalent to boundsNoLeading.size.height + boundsNoLeading.origin.y (manually verified)\n-\t\tascent = bounds.size.height + bounds.origin.y;\n+if(i!=5)\t\tascent = bounds.size.height + bounds.origin.y;\n \t\tdescent = -boundsNoLeading.origin.y;\n \t\t// TODO does this preserve leading sign?\n \t\tleading = -bounds.origin.y - descent;\n@@ -119,11 +119,20 @@ static CFAttributedStringRef attrstrToCoreFoundation(uiAttributedString *s, uiDr\n \t\tif (leading > 0)\n \t\t\tleading = floor(leading + 0.5);\n \n+NSLog(@\"line %d\", (int)i);\n+NSLog(@\"ypos %g\", ypos);\n+if (i>0) {\n+NSLog(@\"expected Y: %g\", metrics[i - 1].Y - metrics[i - 1].Height);\n+}\n+\n \t\tmetrics[i].X = origins[i].x;\n \t\tmetrics[i].Y = origins[i].y - descent - leading;\n \t\tmetrics[i].Width = bounds.size.width;\n \t\tmetrics[i].Height = ascent + descent + leading;\n \n+NSLog(@\"o %g a %g d %g l %g\", origins[i].y, ascent, descent, leading);\n+NSLog(@\"actual Y: %g height: %g\", metrics[i].Y, metrics[i].Height);\n+\n \t\tmetrics[i].BaselineY = origins[i].y;\n \t\tmetrics[i].Ascent = ascent;\n \t\tmetrics[i].Descent = descent;\n@@ -148,7 +157,7 @@ static CFAttributedStringRef attrstrToCoreFoundation(uiAttributedString *s, uiDr\n \t\tmetrics[i].BaselineY = size.height - metrics[i].BaselineY;\n \t\t// TODO also adjust by metrics[i].Height?\n \t}\n-\n+NSLog(@\"===\");\n \tuiFree(origins);\n \treturn metrics;\n }\n"
  },
  {
    "path": "test/OLD_page16.c",
    "content": "// 21 june 2016\n#include \"test.h\"\n\nstatic uiTableModelHandler mh;\n\nstatic int modelNumColumns(uiTableModelHandler *mh, uiTableModel *m)\n{\n\treturn 9;\n}\n\nstatic uiTableModelColumnType modelColumnType(uiTableModelHandler *mh, uiTableModel *m, int column)\n{\n\tif (column == 3 || column == 4)\n\t\treturn uiTableModelColumnColor;\n\tif (column == 5)\n\t\treturn uiTableModelColumnImage;\n\tif (column == 7 || column == 8)\n\t\treturn uiTableModelColumnInt;\n\treturn uiTableModelColumnString;\n}\n\nstatic int modelNumRows(uiTableModelHandler *mh, uiTableModel *m)\n{\n\treturn 15;\n}\n\nstatic uiImage *img[2];\nstatic char row9text[1024];\nstatic int yellowRow = -1;\nstatic int checkStates[15];\n\nstatic void *modelCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, int col)\n{\n\tchar buf[256];\n\n\tif (col == 3) {\n\t\tif (row == yellowRow)\n\t\t\treturn uiTableModelGiveColor(1, 1, 0, 1);\n\t\tif (row == 3)\n\t\t\treturn uiTableModelGiveColor(1, 0, 0, 1);\n\t\tif (row == 11)\n\t\t\treturn uiTableModelGiveColor(0, 0.5, 1, 0.5);\n\t\treturn NULL;\n\t}\n\tif (col == 4) {\n\t\tif ((row % 2) == 1)\n\t\t\treturn uiTableModelGiveColor(0.5, 0, 0.75, 1);\n\t\treturn NULL;\n\t}\n\tif (col == 5) {\n\t\tif (row < 8)\n\t\t\treturn img[0];\n\t\treturn img[1];\n\t}\n\tif (col == 7)\n\t\treturn uiTableModelGiveInt(checkStates[row]);\n\tif (col == 8) {\n\t\tif (row == 0)\n\t\t\treturn uiTableModelGiveInt(0);\n\t\tif (row == 13)\n\t\t\treturn uiTableModelGiveInt(100);\n\t\tif (row == 14)\n\t\t\treturn uiTableModelGiveInt(-1);\n\t\treturn uiTableModelGiveInt(50);\n\t}\n\tswitch (col) {\n\tcase 0:\n\t\tsprintf(buf, \"Row %d\", row);\n\t\tbreak;\n\tcase 2:\n\t\tif (row == 9)\n\t\t\treturn uiTableModelStrdup(row9text);\n\t\t// fall through\n\tcase 1:\n\t\tstrcpy(buf, \"Part\");\n\t\tbreak;\n\tcase 6:\n\t\tstrcpy(buf, \"Make Yellow\");\n\t\tbreak;\n\t}\n\treturn uiTableModelStrdup(buf);\n}\n\nstatic void modelSetCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, int col, const void *val)\n{\n\tif (row == 9 && col == 2)\n\t\tstrcpy(row9text, (const char *) val);\n\tif (col == 6)\n\t\tyellowRow = row;\n\tif (col == 7)\n\t\tcheckStates[row] = uiTableModelTakeInt(val);\n}\n\nuiBox *makePage16(void)\n{\n\tuiBox *page16;\n\tuiTableModel *m;\n\tuiTable *t;\n\tuiTableColumn *tc;\n\n\timg[0] = uiNewImage(16, 16);\n\tappendImageNamed(img[0], \"andlabs_16x16test_24june2016.png\");\n\tappendImageNamed(img[0], \"andlabs_32x32test_24june2016.png\");\n\timg[1] = uiNewImage(16, 16);\n\tappendImageNamed(img[1], \"tango-icon-theme-0.8.90_16x16_x-office-spreadsheet.png\");\n\tappendImageNamed(img[1], \"tango-icon-theme-0.8.90_32x32_x-office-spreadsheet.png\");\n\n\tstrcpy(row9text, \"Part\");\n\n\tmemset(checkStates, 0, 15 * sizeof (int));\n\n\tpage16 = newVerticalBox();\n\n\tmh.NumColumns = modelNumColumns;\n\tmh.ColumnType = modelColumnType;\n\tmh.NumRows = modelNumRows;\n\tmh.CellValue = modelCellValue;\n\tmh.SetCellValue = modelSetCellValue;\n\tm = uiNewTableModel(&mh);\n\n\tt = uiNewTable(m);\n\tuiBoxAppend(page16, uiControl(t), 1);\n\n\tuiTableAppendTextColumn(t, \"Column 1\", 0);\n\n\ttc = uiTableAppendColumn(t, \"Column 2\");\n\tuiTableColumnAppendImagePart(tc, 5, 0);\n\tuiTableColumnAppendTextPart(tc, 1, 0);\n\tuiTableColumnAppendTextPart(tc, 2, 1);\n\tuiTableColumnPartSetTextColor(tc, 1, 4);\n\tuiTableColumnPartSetEditable(tc, 2, 1);\n\n\tuiTableSetRowBackgroundColorModelColumn(t, 3);\n\n\ttc = uiTableAppendColumn(t, \"Buttons\");\n\tuiTableColumnAppendCheckboxPart(tc, 7, 0);\n\tuiTableColumnAppendButtonPart(tc, 6, 1);\n\n\ttc = uiTableAppendColumn(t, \"Progress Bar\");\n\tuiTableColumnAppendProgressBarPart(tc, 8, 0);\n\n\treturn page16;\n}\n"
  },
  {
    "path": "test/drawtests.c",
    "content": "// 9 october 2015\n#include \"test.h\"\n\n// TODO\n// - test multiple clips\n// - test saving and restoring clips\n// - copy tests from https://github.com/Microsoft/WinObjC\n\nstruct drawtest {\n\tconst char *name;\n\tvoid (*draw)(uiAreaDrawParams *p);\n\t// TODO mouse event\n};\n\nstatic void drawOriginal(uiAreaDrawParams *p)\n{\n\tuiDrawPath *path;\n\tuiDrawBrush brush;\n\tuiDrawStrokeParams sp;\n\n\tsp.Dashes = NULL;\n\tsp.NumDashes = 0;\n\tsp.DashPhase = 0;\n\n\tbrush.Type = uiDrawBrushTypeSolid;\n\tbrush.A = 1;\n\n\tbrush.R = 1;\n\tbrush.G = 0;\n\tbrush.B = 0;\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigure(path, p->ClipX + 5, p->ClipY + 5);\n\tuiDrawPathLineTo(path, (p->ClipX + p->ClipWidth) - 5, (p->ClipY + p->ClipHeight) - 5);\n\tuiDrawPathEnd(path);\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.Thickness = 1;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\tuiDrawStroke(p->Context, path, &brush, &sp);\n\tuiDrawFreePath(path);\n\n\tbrush.R = 0;\n\tbrush.G = 0;\n\tbrush.B = 0.75;\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigure(path, p->ClipX, p->ClipY);\n\tuiDrawPathLineTo(path, p->ClipX + p->ClipWidth, p->ClipY);\n\tuiDrawPathLineTo(path, 50, 150);\n\tuiDrawPathLineTo(path, 50, 50);\n\tuiDrawPathCloseFigure(path);\n\tuiDrawPathEnd(path);\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinRound;\n\tsp.Thickness = 5;\n\tuiDrawStroke(p->Context, path, &brush, &sp);\n\tuiDrawFreePath(path);\n\n\tbrush.R = 0;\n\tbrush.G = 0.75;\n\tbrush.B = 0;\n\tbrush.A = 0.5;\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathAddRectangle(path, 120, 80, 50, 50);\n\tuiDrawPathEnd(path);\n\tuiDrawFill(p->Context, path, &brush);\n\tuiDrawFreePath(path);\n\tbrush.A = 1;\n\n\tbrush.R = 0;\n\tbrush.G = 0.5;\n\tbrush.B = 0;\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigure(path, 5.5, 10.5);\n\tuiDrawPathLineTo(path, 5.5, 50.5);\n\tuiDrawPathEnd(path);\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.Thickness = 1;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\tuiDrawStroke(p->Context, path, &brush, &sp);\n\tuiDrawFreePath(path);\n\n\tbrush.R = 0.5;\n\tbrush.G = 0.75;\n\tbrush.B = 0;\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigure(path, 400, 100);\n\tuiDrawPathArcTo(path,\n\t\t400, 100,\n\t\t50,\n\t\t30. * (uiPi / 180.),\n\t\t300. * (uiPi / 180.),\n\t\t0);\n\t// the sweep test below doubles as a clockwise test so a checkbox isn't needed anymore\n\tuiDrawPathLineTo(path, 400, 100);\n\tuiDrawPathNewFigureWithArc(path,\n\t\t510, 100,\n\t\t50,\n\t\t30. * (uiPi / 180.),\n\t\t300. * (uiPi / 180.),\n\t\t0);\n\tuiDrawPathCloseFigure(path);\n\t// and now with 330 to make sure sweeps work properly\n\tuiDrawPathNewFigure(path, 400, 210);\n\tuiDrawPathArcTo(path,\n\t\t400, 210,\n\t\t50,\n\t\t30. * (uiPi / 180.),\n\t\t330. * (uiPi / 180.),\n\t\t0);\n\tuiDrawPathLineTo(path, 400, 210);\n\tuiDrawPathNewFigureWithArc(path,\n\t\t510, 210,\n\t\t50,\n\t\t30. * (uiPi / 180.),\n\t\t330. * (uiPi / 180.),\n\t\t0);\n\tuiDrawPathCloseFigure(path);\n\tuiDrawPathEnd(path);\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.Thickness = 1;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\tuiDrawStroke(p->Context, path, &brush, &sp);\n\tuiDrawFreePath(path);\n\n\tbrush.R = 0;\n\tbrush.G = 0.5;\n\tbrush.B = 0.75;\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigure(path, 300, 300);\n\tuiDrawPathBezierTo(path,\n\t\t350, 320,\n\t\t310, 390,\n\t\t435, 372);\n\tuiDrawPathEnd(path);\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.Thickness = 1;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\tuiDrawStroke(p->Context, path, &brush, &sp);\n\tuiDrawFreePath(path);\n}\n\nstatic void drawArcs(uiAreaDrawParams *p)\n{\n\tuiDrawPath *path;\n\tint start = 20;\n\tint step = 20;\n\tint rad = 25;\n\tint x, y;\n\tdouble angle;\n\tdouble add;\n\tint i;\n\tuiDrawBrush brush;\n\tuiDrawStrokeParams sp;\n\n\tsp.Dashes = NULL;\n\tsp.NumDashes = 0;\n\tsp.DashPhase = 0;\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\n\tadd = (2.0 * uiPi) / 12;\n\n\tx = start + rad;\n\ty = start + rad;\n\tangle = 0;\n\tfor (i = 0; i < 13; i++) {\n\t\tuiDrawPathNewFigureWithArc(path,\n\t\t\tx, y,\n\t\t\trad,\n\t\t\t0, angle,\n\t\t\t0);\n\t\tangle += add;\n\t\tx += 2 * rad + step;\n\t}\n\n\ty += 2 * rad + step;\n\tx = start + rad;\n\tangle = 0;\n\tfor (i = 0; i < 13; i++) {\n\t\tuiDrawPathNewFigure(path, x, y);\n\t\tuiDrawPathArcTo(path,\n\t\t\tx, y,\n\t\t\trad,\n\t\t\t0, angle,\n\t\t\t0);\n\t\tangle += add;\n\t\tx += 2 * rad + step;\n\t}\n\n\ty += 2 * rad + step;\n\tx = start + rad;\n\tangle = 0;\n\tfor (i = 0; i < 13; i++) {\n\t\tuiDrawPathNewFigureWithArc(path,\n\t\t\tx, y,\n\t\t\trad,\n\t\t\t(uiPi / 4), angle,\n\t\t\t0);\n\t\tangle += add;\n\t\tx += 2 * rad + step;\n\t}\n\n\ty += 2 * rad + step;\n\tx = start + rad;\n\tangle = 0;\n\tfor (i = 0; i < 13; i++) {\n\t\tuiDrawPathNewFigure(path, x, y);\n\t\tuiDrawPathArcTo(path,\n\t\t\tx, y,\n\t\t\trad,\n\t\t\t(uiPi / 4), angle,\n\t\t\t0);\n\t\tangle += add;\n\t\tx += 2 * rad + step;\n\t}\n\n\ty += 2 * rad + step;\n\tx = start + rad;\n\tangle = 0;\n\tfor (i = 0; i < 13; i++) {\n\t\tuiDrawPathNewFigureWithArc(path,\n\t\t\tx, y,\n\t\t\trad,\n\t\t\tuiPi + (uiPi / 5), angle,\n\t\t\t0);\n\t\tangle += add;\n\t\tx += 2 * rad + step;\n\t}\n\n\ty += 2 * rad + step;\n\tx = start + rad;\n\tangle = 0;\n\tfor (i = 0; i < 13; i++) {\n\t\tuiDrawPathNewFigure(path, x, y);\n\t\tuiDrawPathArcTo(path,\n\t\t\tx, y,\n\t\t\trad,\n\t\t\tuiPi + (uiPi / 5), angle,\n\t\t\t0);\n\t\tangle += add;\n\t\tx += 2 * rad + step;\n\t}\n\n\tuiDrawPathEnd(path);\n\n\tbrush.Type = uiDrawBrushTypeSolid;\n\tbrush.R = 0;\n\tbrush.G = 0;\n\tbrush.B = 0;\n\tbrush.A = 1;\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.Thickness = 1;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\tuiDrawStroke(p->Context, path, &brush, &sp);\n\n\tuiDrawFreePath(path);\n}\n\n// Direct2D Documentation Code\n\nstatic void d2dColorToRGB(uint32_t color, double *r, double *g, double *b)\n{\n\tuint8_t rr, gg, bb;\n\n\trr = (color & 0xFF0000) >> 16;\n\tgg = (color & 0x00FF00) >> 8;\n\tbb = color & 0x0000FF;\n\t*r = ((double) rr) / 255.0;\n\t*g = ((double) gg) / 255.0;\n\t*b = ((double) bb) / 255.0;\n}\n#define d2dBlack 0x000000\n#define d2dLightSlateGray 0x778899\n#define d2dCornflowerBlue 0x6495ED\n#define d2dWhite 0xFFFFFF\n#define d2dYellowGreen 0x9ACD32\n#define d2dYellow 0xFFFF00\n#define d2dForestGreen 0x228B22\n#define d2dOliveDrab 0x6B8E23\n#define d2dLightSkyBlue 0x87CEFA\n\nstatic void d2dSolidBrush(uiDrawBrush *brush, uint32_t color, double alpha)\n{\n\tbrush->Type = uiDrawBrushTypeSolid;\n\td2dColorToRGB(color, &(brush->R), &(brush->G), &(brush->B));\n\tbrush->A = alpha;\n}\n\nstatic void d2dClear(uiAreaDrawParams *p, uint32_t color, double alpha)\n{\n\tuiDrawPath *path;\n\tuiDrawBrush brush;\n\n\td2dSolidBrush(&brush, color, alpha);\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathAddRectangle(path, 0, 0, p->AreaWidth, p->AreaHeight);\n\tuiDrawPathEnd(path);\n\tuiDrawFill(p->Context, path, &brush);\n\tuiDrawFreePath(path);\n}\n\n// from https://msdn.microsoft.com/en-us/library/windows/desktop/hh780340%28v=vs.85%29.aspx\n// also at https://msdn.microsoft.com/en-us/library/windows/desktop/dd535473%28v=vs.85%29.aspx\nstatic void drawD2DW8QS(uiAreaDrawParams *p)\n{\n\tuiDrawPath *path;\n\tuiDrawBrush brush;\n\n\td2dSolidBrush(&brush, d2dBlack, 1.0);\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathAddRectangle(path,\n\t\t100,\n\t\t100,\n\t\t(p->AreaWidth - 100) - 100,\n\t\t(p->AreaHeight - 100) - 100);\n\tuiDrawPathEnd(path);\n\tuiDrawFill(p->Context, path, &brush);\n\tuiDrawFreePath(path);\n}\n\n// from https://msdn.microsoft.com/en-us/library/windows/desktop/dd370994%28v=vs.85%29.aspx\nstatic void drawD2DSimpleApp(uiAreaDrawParams *p)\n{\n\tuiDrawPath *path;\n\tuiDrawBrush lightSlateGray;\n\tuiDrawBrush cornflowerBlue;\n\tuiDrawStrokeParams sp;\n\tint x, y;\n\n\tsp.Dashes = NULL;\n\tsp.NumDashes = 0;\n\tsp.DashPhase = 0;\n\n\td2dSolidBrush(&lightSlateGray, d2dLightSlateGray, 1.0);\n\td2dSolidBrush(&cornflowerBlue, d2dCornflowerBlue, 1.0);\n\n\td2dClear(p, d2dWhite, 1.0);\n\n\tsp.Thickness = 0.5;\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\n\tfor (x = 0; x < p->AreaWidth; x += 10) {\n\t\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\t\tuiDrawPathNewFigure(path, x, 0);\n\t\tuiDrawPathLineTo(path, x, p->AreaHeight);\n\t\tuiDrawPathEnd(path);\n\t\tuiDrawStroke(p->Context, path, &lightSlateGray, &sp);\n\t\tuiDrawFreePath(path);\n\t}\n\n\tfor (y = 0; y < p->AreaHeight; y += 10) {\n\t\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\t\tuiDrawPathNewFigure(path, 0, y);\n\t\tuiDrawPathLineTo(path, p->AreaWidth, y);\n\t\tuiDrawPathEnd(path);\n\t\tuiDrawStroke(p->Context, path, &lightSlateGray, &sp);\n\t\tuiDrawFreePath(path);\n\t}\n\n\tdouble left, top, right, bottom;\n\n\tleft = p->AreaWidth / 2.0 - 50.0;\n\tright = p->AreaWidth / 2.0 + 50.0;\n\ttop = p->AreaHeight / 2.0 - 50.0;\n\tbottom = p->AreaHeight / 2.0 + 50.0;\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathAddRectangle(path, left, top, right - left, bottom - top);\n\tuiDrawPathEnd(path);\n\tuiDrawFill(p->Context, path, &lightSlateGray);\n\tuiDrawFreePath(path);\n\n\tleft = p->AreaWidth / 2.0 - 100.0;\n\tright = p->AreaWidth / 2.0 + 100.0;\n\ttop = p->AreaHeight / 2.0 - 100.0;\n\tbottom = p->AreaHeight / 2.0 + 100.0;\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathAddRectangle(path, left, top, right - left, bottom - top);\n\tuiDrawPathEnd(path);\n\tsp.Thickness = 1.0;\n\tuiDrawStroke(p->Context, path, &cornflowerBlue, &sp);\n\tuiDrawFreePath(path);\n}\n\n// TODO? https://msdn.microsoft.com/en-us/library/windows/desktop/dd372260(v=vs.85).aspx\n\n// TODO https://msdn.microsoft.com/en-us/library/windows/desktop/dd756654%28v=vs.85%29.aspx\n\n// TODO? all subsections too? https://msdn.microsoft.com/en-us/library/windows/desktop/hh973240%28v=vs.85%29.aspx\n\n// TODO differing examples of? https://msdn.microsoft.com/en-us/library/windows/desktop/dd756651%28v=vs.85%29.aspx\n\n// from https://msdn.microsoft.com/en-us/library/windows/desktop/dd756680%28v=vs.85%29.aspx\nstatic void drawD2DSolidBrush(uiAreaDrawParams *p)\n{\n\tuiDrawPath *path;\n\tuiDrawBrush black;\n\tuiDrawBrush yellowGreen;\n\tuiDrawStrokeParams sp;\n\n\tsp.Dashes = NULL;\n\tsp.NumDashes = 0;\n\tsp.DashPhase = 0;\n\n\td2dSolidBrush(&black, d2dBlack, 1.0);\n\td2dSolidBrush(&yellowGreen, d2dYellowGreen, 1.0);\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\t// the example doesn't define a rectangle\n\t// 150x150 seems to be right given the other examples though\n\tuiDrawPathAddRectangle(path, 25, 25, 150, 150);\n\tuiDrawPathEnd(path);\n\n\tuiDrawFill(p->Context, path, &yellowGreen);\n\tsp.Thickness = 1.0;\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\tuiDrawStroke(p->Context, path, &black, &sp);\n\n\tuiDrawFreePath(path);\n}\n\n// from https://msdn.microsoft.com/en-us/library/windows/desktop/dd756678%28v=vs.85%29.aspx\nstatic void drawD2DLinearBrush(uiAreaDrawParams *p)\n{\n\tuiDrawPath *path;\n\tuiDrawBrush black;\n\tuiDrawBrush gradient;\n\tuiDrawBrushGradientStop stops[2];\n\tuiDrawStrokeParams sp;\n\n\tuiDrawMatrix m;\n\n\tsp.Dashes = NULL;\n\tsp.NumDashes = 0;\n\tsp.DashPhase = 0;\n\n\t// leave some room\n\tuiDrawMatrixSetIdentity(&m);\n\tuiDrawMatrixTranslate(&m, 25, 25);\n\tuiDrawTransform(p->Context, &m);\n\n\tgradient.Type = uiDrawBrushTypeLinearGradient;\n\tgradient.X0 = 0;\n\tgradient.Y0 = 0;\n\tgradient.X1 = 150;\n\tgradient.Y1 = 150;\n\tstops[0].Pos = 0.0;\n\td2dColorToRGB(d2dYellow, &(stops[0].R), &(stops[0].G), &(stops[0].B));\n\tstops[0].A = 1.0;\n\tstops[1].Pos = 1.0;\n\td2dColorToRGB(d2dForestGreen, &(stops[1].R), &(stops[1].G), &(stops[1].B));\n\tstops[1].A = 1.0;\n\tgradient.Stops = stops;\n\tgradient.NumStops = 2;\n\n\td2dSolidBrush(&black, d2dBlack, 1.0);\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathAddRectangle(path, 0, 0, 150, 150);\n\tuiDrawPathEnd(path);\n\n\tuiDrawFill(p->Context, path, &gradient);\n\tsp.Thickness = 1.0;\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\tuiDrawStroke(p->Context, path, &black, &sp);\n\n\tuiDrawFreePath(path);\n}\n\n// from https://msdn.microsoft.com/en-us/library/windows/desktop/dd756679%28v=vs.85%29.aspx\n// TODO expand this to change the origin point with a mouse click (not in the original but useful to have)\nstatic void drawD2DRadialBrush(uiAreaDrawParams *p)\n{\n\tuiDrawPath *path;\n\tuiDrawBrush black;\n\tuiDrawBrush gradient;\n\tuiDrawBrushGradientStop stops[2];\n\tuiDrawStrokeParams sp;\n\n\tuiDrawMatrix m;\n\n\tsp.Dashes = NULL;\n\tsp.NumDashes = 0;\n\tsp.DashPhase = 0;\n\n\t// leave some room\n\tuiDrawMatrixSetIdentity(&m);\n\tuiDrawMatrixTranslate(&m, 25, 25);\n\tuiDrawTransform(p->Context, &m);\n\n\tgradient.Type = uiDrawBrushTypeRadialGradient;\n\tgradient.X0 = 75;\n\tgradient.Y0 = 75;\n\tgradient.X1 = 75;\n\tgradient.Y1 = 75;\n\tgradient.OuterRadius = 75;\n\tstops[0].Pos = 0.0;\n\td2dColorToRGB(d2dYellow, &(stops[0].R), &(stops[0].G), &(stops[0].B));\n\tstops[0].A = 1.0;\n\tstops[1].Pos = 1.0;\n\td2dColorToRGB(d2dForestGreen, &(stops[1].R), &(stops[1].G), &(stops[1].B));\n\tstops[1].A = 1.0;\n\tgradient.Stops = stops;\n\tgradient.NumStops = 2;\n\n\td2dSolidBrush(&black, d2dBlack, 1.0);\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigure(path, 150, 75);\n\tuiDrawPathArcTo(path,\n\t\t75, 75,\n\t\t75,\n\t\t0,\n\t\t2 * uiPi,\n\t\t0);\n\tuiDrawPathEnd(path);\n\n\tuiDrawFill(p->Context, path, &gradient);\n\tsp.Thickness = 1.0;\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\tuiDrawStroke(p->Context, path, &black, &sp);\n\n\tuiDrawFreePath(path);\n}\n\n// TODO https://msdn.microsoft.com/en-us/library/windows/desktop/dd756677%28v=vs.85%29.aspx\n\n// TODO? other pages have some of these https://msdn.microsoft.com/en-us/library/windows/desktop/dd756653%28v=vs.85%29.aspx\n\n// from https://msdn.microsoft.com/en-us/library/windows/desktop/ee264309%28v=vs.85%29.aspx\nstatic void drawD2DPathGeometries(uiAreaDrawParams *p)\n{\n\tuiDrawPath *leftMountain;\n\tuiDrawPath *rightMountain;\n\tuiDrawPath *sun;\n\tuiDrawPath *sunRays;\n\tuiDrawPath *river;\n\tuiDrawBrush radial;\n\tuiDrawBrush scene;\n\tuiDrawStrokeParams sp;\n\tuiDrawBrushGradientStop stops[2];\n\n\tsp.Dashes = NULL;\n\tsp.NumDashes = 0;\n\tsp.DashPhase = 0;\n\n\t// TODO this is definitely wrong but the example doesn't have the right brush in it\n\tradial.Type = uiDrawBrushTypeRadialGradient;\n\tradial.X0 = 75;\n\tradial.Y0 = 75;\n\tradial.X1 = 75;\n\tradial.Y1 = 75;\n\tradial.OuterRadius = 75;\n\tstops[0].Pos = 0.0;\n\td2dColorToRGB(d2dYellow, &(stops[0].R), &(stops[0].G), &(stops[0].B));\n\tstops[0].A = 1.0;\n\tstops[1].Pos = 1.0;\n\td2dColorToRGB(d2dForestGreen, &(stops[1].R), &(stops[1].G), &(stops[1].B));\n\tstops[1].A = 1.0;\n\tradial.Stops = stops;\n\tradial.NumStops = 2;\n\n\tleftMountain = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigure(leftMountain, 346, 255);\n\tuiDrawPathLineTo(leftMountain, 267, 177);\n\tuiDrawPathLineTo(leftMountain, 236, 192);\n\tuiDrawPathLineTo(leftMountain, 212, 160);\n\tuiDrawPathLineTo(leftMountain, 156, 255);\n\tuiDrawPathLineTo(leftMountain, 346, 255);\n\tuiDrawPathCloseFigure(leftMountain);\n\tuiDrawPathEnd(leftMountain);\n\n\trightMountain = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigure(rightMountain, 575, 263);\n\tuiDrawPathLineTo(rightMountain, 481, 146);\n\tuiDrawPathLineTo(rightMountain, 449, 181);\n\tuiDrawPathLineTo(rightMountain, 433, 159);\n\tuiDrawPathLineTo(rightMountain, 401, 214);\n\tuiDrawPathLineTo(rightMountain, 381, 199);\n\tuiDrawPathLineTo(rightMountain, 323, 263);\n\tuiDrawPathLineTo(rightMountain, 575, 263);\n\tuiDrawPathCloseFigure(rightMountain);\n\tuiDrawPathEnd(rightMountain);\n\n\tsun = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigureWithArc(sun,\n\t\t(440.0 - 270.0) / 2 + 270.0, 255,\n\t\t85,\n\t\tuiPi, uiPi,\n\t\t0);\n\tuiDrawPathCloseFigure(sun);\n\tuiDrawPathEnd(sun);\n\n\t// the original examples had these as hollow figures\n\t// we don't support them, so we'll have to stroke it separately\n\tsunRays = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigure(sunRays, 299, 182);\n\tuiDrawPathBezierTo(sunRays,\n\t\t299, 182,\n\t\t294, 176,\n\t\t285, 178);\n\tuiDrawPathBezierTo(sunRays,\n\t\t276, 179,\n\t\t272, 173,\n\t\t272, 173);\n\tuiDrawPathNewFigure(sunRays, 354, 156);\n\tuiDrawPathBezierTo(sunRays,\n\t\t354, 156,\n\t\t358, 149,\n\t\t354, 142);\n\tuiDrawPathBezierTo(sunRays,\n\t\t349, 134,\n\t\t354, 127,\n\t\t354, 127);\n\tuiDrawPathNewFigure(sunRays, 322, 164);\n\tuiDrawPathBezierTo(sunRays,\n\t\t322, 164,\n\t\t322, 156,\n\t\t314, 152);\n\tuiDrawPathBezierTo(sunRays,\n\t\t306, 149,\n\t\t305, 141,\n\t\t305, 141);\n\tuiDrawPathNewFigure(sunRays, 385, 164);\n\tuiDrawPathBezierTo(sunRays,\n\t\t385, 164,\n\t\t392, 161,\n\t\t394, 152);\n\tuiDrawPathBezierTo(sunRays,\n\t\t395, 144,\n\t\t402, 141,\n\t\t402, 142);\n\tuiDrawPathNewFigure(sunRays, 408, 182);\n\tuiDrawPathBezierTo(sunRays,\n\t\t408, 182,\n\t\t416, 184,\n\t\t422, 178);\n\tuiDrawPathBezierTo(sunRays,\n\t\t428, 171,\n\t\t435, 173,\n\t\t435, 173);\n\tuiDrawPathEnd(sunRays);\n\n\triver = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigure(river, 183, 392);\n\tuiDrawPathBezierTo(river,\n\t\t238, 284,\n\t\t472, 345,\n\t\t356, 303);\n\tuiDrawPathBezierTo(river,\n\t\t237, 261,\n\t\t333, 256,\n\t\t333, 256);\n\tuiDrawPathBezierTo(river,\n\t\t335, 257,\n\t\t241, 261,\n\t\t411, 306);\n\tuiDrawPathBezierTo(river,\n\t\t574, 350,\n\t\t288, 324,\n\t\t296, 392);\n\tuiDrawPathEnd(river);\n\n\td2dClear(p, d2dWhite, 1.0);\n\n\t// TODO draw the grid\n\n\tuiDrawFill(p->Context, sun, &radial);\n\n\td2dSolidBrush(&scene, d2dBlack, 1.0);\n\tsp.Thickness = 1.0;\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\tuiDrawStroke(p->Context, sun, &scene, &sp);\n\tuiDrawStroke(p->Context, sunRays, &scene, &sp);\n\n\td2dSolidBrush(&scene, d2dOliveDrab, 1.0);\n\tuiDrawFill(p->Context, leftMountain, &scene);\n\n\td2dSolidBrush(&scene, d2dBlack, 1.0);\n\tuiDrawStroke(p->Context, leftMountain, &scene, &sp);\n\n\td2dSolidBrush(&scene, d2dLightSkyBlue, 1.0);\n\tuiDrawFill(p->Context, river, &scene);\n\n\td2dSolidBrush(&scene, d2dBlack, 1.0);\n\tuiDrawStroke(p->Context, river, &scene, &sp);\n\n\td2dSolidBrush(&scene, d2dYellowGreen, 1.0);\n\tuiDrawFill(p->Context, rightMountain, &scene);\n\n\td2dSolidBrush(&scene, d2dBlack, 1.0);\n\tuiDrawStroke(p->Context, rightMountain, &scene, &sp);\n\n\tuiDrawFreePath(leftMountain);\n\tuiDrawFreePath(rightMountain);\n\tuiDrawFreePath(sun);\n\tuiDrawFreePath(sunRays);\n\tuiDrawFreePath(river);\n}\n\n// TODO https://msdn.microsoft.com/en-us/library/windows/desktop/dd756690%28v=vs.85%29.aspx\n\n// from https://msdn.microsoft.com/en-us/library/windows/desktop/dd756681%28v=vs.85%29.aspx\nstatic void drawD2DGeometryGroup(uiAreaDrawParams *p)\n{\n\tuiDrawPath *alternate;\n\tuiDrawPath *winding;\n\tuiDrawBrush fill;\n\tuiDrawBrush stroke;\n\tuiDrawStrokeParams sp;\n\tuiDrawMatrix m;\n\n\tsp.Dashes = NULL;\n\tsp.NumDashes = 0;\n\tsp.DashPhase = 0;\n\n\talternate = uiDrawNewPath(uiDrawFillModeAlternate);\n\tuiDrawPathNewFigureWithArc(alternate,\n\t\t105, 105,\n\t\t25,\n\t\t0, 2 * uiPi,\n\t\t0);\n\tuiDrawPathNewFigureWithArc(alternate,\n\t\t105, 105,\n\t\t50,\n\t\t0, 2 * uiPi,\n\t\t0);\n\tuiDrawPathNewFigureWithArc(alternate,\n\t\t105, 105,\n\t\t75,\n\t\t0, 2 * uiPi,\n\t\t0);\n\tuiDrawPathNewFigureWithArc(alternate,\n\t\t105, 105,\n\t\t100,\n\t\t0, 2 * uiPi,\n\t\t0);\n\tuiDrawPathEnd(alternate);\n\n\twinding = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigureWithArc(winding,\n\t\t105, 105,\n\t\t25,\n\t\t0, 2 * uiPi,\n\t\t0);\n\tuiDrawPathNewFigureWithArc(winding,\n\t\t105, 105,\n\t\t50,\n\t\t0, 2 * uiPi,\n\t\t0);\n\tuiDrawPathNewFigureWithArc(winding,\n\t\t105, 105,\n\t\t75,\n\t\t0, 2 * uiPi,\n\t\t0);\n\tuiDrawPathNewFigureWithArc(winding,\n\t\t105, 105,\n\t\t100,\n\t\t0, 2 * uiPi,\n\t\t0);\n\tuiDrawPathEnd(winding);\n\n\td2dClear(p, d2dWhite, 1.0);\n\n\t// TODO grid\n\n\t// TODO the example doesn't provide these\n\td2dSolidBrush(&fill, d2dForestGreen, 1.0);\n\td2dSolidBrush(&stroke, d2dCornflowerBlue, 1.0);\n\n\tsp.Thickness = 1.0;\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\n\tuiDrawFill(p->Context, alternate, &fill);\n\tuiDrawStroke(p->Context, alternate, &stroke, &sp);\n\t// TODO text\n\n\tuiDrawMatrixSetIdentity(&m);\n\tuiDrawMatrixTranslate(&m, 300, 0);\n\tuiDrawTransform(p->Context, &m);\n\tuiDrawFill(p->Context, winding, &fill);\n\tuiDrawStroke(p->Context, winding, &stroke, &sp);\n//\t// TODO text\n\n\tuiDrawFreePath(winding);\n\tuiDrawFreePath(alternate);\n}\n\n// TODO https://msdn.microsoft.com/en-us/library/windows/desktop/dd756676%28v=vs.85%29.aspx\n\n// TODO? https://msdn.microsoft.com/en-us/library/windows/desktop/dd370971%28v=vs.85%29.aspx\n\n// TODO are there even examples here? https://msdn.microsoft.com/en-us/library/windows/desktop/dd370966%28v=vs.85%29.aspx\n\n// from https://msdn.microsoft.com/en-us/library/windows/desktop/dd756687%28v=vs.85%29.aspx\nstatic void drawD2DRotate(uiAreaDrawParams *p)\n{\n\tuiDrawPath *path;\n\tuiDrawBrush original;\n\tuiDrawBrush fill;\n\tuiDrawBrush transform;\n\tuiDrawStrokeParams originalsp;\n\tuiDrawStrokeParams transformsp;\n\tuiDrawMatrix m;\n\n\toriginalsp.Dashes = NULL;\n\toriginalsp.NumDashes = 0;\n\toriginalsp.DashPhase = 0;\n\ttransformsp.Dashes = NULL;\n\ttransformsp.NumDashes = 0;\n\ttransformsp.DashPhase = 0;\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathAddRectangle(path, 438.0, 301.5, 498.0 - 438.0, 361.5 - 301.5);\n\tuiDrawPathEnd(path);\n\n\t// TODO the example doesn't specify what these should be\n\td2dSolidBrush(&original, d2dBlack, 1.0);\n\td2dSolidBrush(&fill, d2dWhite, 0.5);\n\td2dSolidBrush(&transform, d2dForestGreen, 1.0);\n\t// TODO this needs to be dashed\n\toriginalsp.Thickness = 1.0;\n\toriginalsp.Cap = uiDrawLineCapFlat;\n\toriginalsp.Join = uiDrawLineJoinMiter;\n\toriginalsp.MiterLimit = uiDrawDefaultMiterLimit;\n\ttransformsp.Thickness = 1.0;\n\ttransformsp.Cap = uiDrawLineCapFlat;\n\ttransformsp.Join = uiDrawLineJoinMiter;\n\ttransformsp.MiterLimit = uiDrawDefaultMiterLimit;\n\n\t// save for when we do the translated one\n\tuiDrawSave(p->Context);\n\n\tuiDrawStroke(p->Context, path, &original, &originalsp);\n\n\tuiDrawMatrixSetIdentity(&m);\n\tuiDrawMatrixRotate(&m,\n\t\t468.0, 331.5,\n\t\t45.0 * (uiPi / 180));\n\tuiDrawTransform(p->Context, &m);\n\n\tuiDrawFill(p->Context, path, &fill);\n\tuiDrawStroke(p->Context, path, &transform, &transformsp);\n\n\tuiDrawRestore(p->Context);\n\n\t// translate to test the corner axis\n\tuiDrawMatrixSetIdentity(&m);\n\tuiDrawMatrixTranslate(&m, -200, -200);\n\tuiDrawTransform(p->Context, &m);\n\n\tuiDrawStroke(p->Context, path, &original, &originalsp);\n\n\tuiDrawMatrixSetIdentity(&m);\n\tuiDrawMatrixRotate(&m,\n\t\t438.0, 301.5,\n\t\t45.0 * (uiPi / 180));\n\tuiDrawTransform(p->Context, &m);\n\n\tuiDrawFill(p->Context, path, &fill);\n\tuiDrawStroke(p->Context, path, &transform, &transformsp);\n\n\tuiDrawFreePath(path);\n}\n\n// from https://msdn.microsoft.com/en-us/library/windows/desktop/dd756688%28v=vs.85%29.aspx\nstatic void drawD2DScale(uiAreaDrawParams *p)\n{\n\tuiDrawPath *path;\n\tuiDrawBrush original;\n\tuiDrawBrush fill;\n\tuiDrawBrush transform;\n\tuiDrawStrokeParams originalsp;\n\tuiDrawStrokeParams transformsp;\n\tuiDrawMatrix m;\n\n\toriginalsp.Dashes = NULL;\n\toriginalsp.NumDashes = 0;\n\toriginalsp.DashPhase = 0;\n\ttransformsp.Dashes = NULL;\n\ttransformsp.NumDashes = 0;\n\ttransformsp.DashPhase = 0;\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathAddRectangle(path, 438.0, 80.5, 498.0 - 438.0, 140.5 - 80.5);\n\tuiDrawPathEnd(path);\n\n\t// TODO the example doesn't specify what these should be\n\td2dSolidBrush(&original, d2dBlack, 1.0);\n\td2dSolidBrush(&fill, d2dWhite, 0.5);\n\td2dSolidBrush(&transform, d2dForestGreen, 1.0);\n\t// TODO this needs to be dashed\n\toriginalsp.Thickness = 1.0;\n\toriginalsp.Cap = uiDrawLineCapFlat;\n\toriginalsp.Join = uiDrawLineJoinMiter;\n\toriginalsp.MiterLimit = uiDrawDefaultMiterLimit;\n\ttransformsp.Thickness = 1.0;\n\ttransformsp.Cap = uiDrawLineCapFlat;\n\ttransformsp.Join = uiDrawLineJoinMiter;\n\ttransformsp.MiterLimit = uiDrawDefaultMiterLimit;\n\n\t// save for when we do the translated one\n\tuiDrawSave(p->Context);\n\n\tuiDrawStroke(p->Context, path, &original, &originalsp);\n\n\tuiDrawMatrixSetIdentity(&m);\n\tuiDrawMatrixScale(&m,\n\t\t438.0, 80.5,\n\t\t1.3, 1.3);\n\tuiDrawTransform(p->Context, &m);\n\n\tuiDrawFill(p->Context, path, &fill);\n\tuiDrawStroke(p->Context, path, &transform, &transformsp);\n\n\tuiDrawRestore(p->Context);\n\n\t// for testing purposes, show what happens if we scale about (0, 0)\n\tuiDrawMatrixSetIdentity(&m);\n\tuiDrawMatrixTranslate(&m, -300, 50);\n\tuiDrawTransform(p->Context, &m);\n\n\tuiDrawStroke(p->Context, path, &original, &originalsp);\n\n\tuiDrawMatrixSetIdentity(&m);\n\tuiDrawMatrixScale(&m,\n\t\t0, 0,\n\t\t1.3, 1.3);\n\tuiDrawTransform(p->Context, &m);\n\n\tuiDrawFill(p->Context, path, &fill);\n\tuiDrawStroke(p->Context, path, &transform, &transformsp);\n\n\tuiDrawFreePath(path);\n}\n\n// from https://msdn.microsoft.com/en-us/library/windows/desktop/dd756689%28v=vs.85%29.aspx\n// TODO counterclockwise?!\nvoid drawD2DSkew(uiAreaDrawParams *p)\n{\n\tuiDrawPath *path;\n\tuiDrawBrush original;\n\tuiDrawBrush fill;\n\tuiDrawBrush transform;\n\tuiDrawStrokeParams originalsp;\n\tuiDrawStrokeParams transformsp;\n\tuiDrawMatrix m;\n\n\toriginalsp.Dashes = NULL;\n\toriginalsp.NumDashes = 0;\n\toriginalsp.DashPhase = 0;\n\ttransformsp.Dashes = NULL;\n\ttransformsp.NumDashes = 0;\n\ttransformsp.DashPhase = 0;\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathAddRectangle(path, 126.0, 301.5, 186.0 - 126.0, 361.5 - 301.5);\n\tuiDrawPathEnd(path);\n\n\t// TODO the example doesn't specify what these should be\n\td2dSolidBrush(&original, d2dBlack, 1.0);\n\td2dSolidBrush(&fill, d2dWhite, 0.5);\n\td2dSolidBrush(&transform, d2dForestGreen, 1.0);\n\t// TODO this needs to be dashed\n\toriginalsp.Thickness = 1.0;\n\toriginalsp.Cap = uiDrawLineCapFlat;\n\toriginalsp.Join = uiDrawLineJoinMiter;\n\toriginalsp.MiterLimit = uiDrawDefaultMiterLimit;\n\ttransformsp.Thickness = 1.0;\n\ttransformsp.Cap = uiDrawLineCapFlat;\n\ttransformsp.Join = uiDrawLineJoinMiter;\n\ttransformsp.MiterLimit = uiDrawDefaultMiterLimit;\n\n\t// save for when we do the translated one\n\tuiDrawSave(p->Context);\n\n\tuiDrawStroke(p->Context, path, &original, &originalsp);\n\n\tuiDrawMatrixSetIdentity(&m);\n\tuiDrawMatrixSkew(&m,\n\t\t126.0, 301.5,\n\t\t45.0 * (uiPi / 180), 0);\n\tuiDrawTransform(p->Context, &m);\n\n\tuiDrawFill(p->Context, path, &fill);\n\tuiDrawStroke(p->Context, path, &transform, &transformsp);\n\n\tuiDrawRestore(p->Context);\n\n\t// for testing purposes, show what happens if we skew about (0, 0)\n\tuiDrawMatrixSetIdentity(&m);\n\tuiDrawMatrixTranslate(&m, 0, -200);\n\tuiDrawTransform(p->Context, &m);\n\n\tuiDrawStroke(p->Context, path, &original, &originalsp);\n\n\tuiDrawMatrixSetIdentity(&m);\n\tuiDrawMatrixSkew(&m,\n\t\t0, 0,\n\t\t45.0 * (uiPi / 180), 0);\n\tuiDrawTransform(p->Context, &m);\n\n\tuiDrawFill(p->Context, path, &fill);\n\tuiDrawStroke(p->Context, path, &transform, &transformsp);\n\n\tuiDrawFreePath(path);\n}\n\n// from https://msdn.microsoft.com/en-us/library/windows/desktop/dd756691%28v=vs.85%29.aspx\nstatic void drawD2DTranslate(uiAreaDrawParams *p)\n{\n\tuiDrawPath *path;\n\tuiDrawBrush original;\n\tuiDrawBrush fill;\n\tuiDrawBrush transform;\n\tuiDrawStrokeParams originalsp;\n\tuiDrawStrokeParams transformsp;\n\tuiDrawMatrix m;\n\n\toriginalsp.Dashes = NULL;\n\toriginalsp.NumDashes = 0;\n\toriginalsp.DashPhase = 0;\n\ttransformsp.Dashes = NULL;\n\ttransformsp.NumDashes = 0;\n\ttransformsp.DashPhase = 0;\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathAddRectangle(path, 126.0, 80.5, 186.0 - 126.0, 140.5 - 80.5);\n\tuiDrawPathEnd(path);\n\n\t// TODO the example doesn't specify what these should be\n\td2dSolidBrush(&original, d2dBlack, 1.0);\n\td2dSolidBrush(&fill, d2dWhite, 0.5);\n\td2dSolidBrush(&transform, d2dForestGreen, 1.0);\n\t// TODO this needs to be dashed\n\toriginalsp.Thickness = 1.0;\n\toriginalsp.Cap = uiDrawLineCapFlat;\n\toriginalsp.Join = uiDrawLineJoinMiter;\n\toriginalsp.MiterLimit = uiDrawDefaultMiterLimit;\n\ttransformsp.Thickness = 1.0;\n\ttransformsp.Cap = uiDrawLineCapFlat;\n\ttransformsp.Join = uiDrawLineJoinMiter;\n\ttransformsp.MiterLimit = uiDrawDefaultMiterLimit;\n\n\tuiDrawStroke(p->Context, path, &original, &originalsp);\n\n\tuiDrawMatrixSetIdentity(&m);\n\tuiDrawMatrixTranslate(&m, 20, 10);\n\tuiDrawTransform(p->Context, &m);\n\n\tuiDrawFill(p->Context, path, &fill);\n\tuiDrawStroke(p->Context, path, &transform, &transformsp);\n\n\tuiDrawFreePath(path);\n}\n\n// from https://msdn.microsoft.com/en-us/library/windows/desktop/dd756672%28v=vs.85%29.aspx\n// TODO the points seem off\nstatic void drawD2DMultiTransforms(uiAreaDrawParams *p)\n{\n\tuiDrawPath *path;\n\tuiDrawBrush original;\n\tuiDrawBrush fill;\n\tuiDrawBrush transform;\n\tuiDrawStrokeParams originalsp;\n\tuiDrawStrokeParams transformsp;\n\tuiDrawMatrix mtranslate;\n\tuiDrawMatrix mrotate;\n\n\toriginalsp.Dashes = NULL;\n\toriginalsp.NumDashes = 0;\n\toriginalsp.DashPhase = 0;\n\ttransformsp.Dashes = NULL;\n\ttransformsp.NumDashes = 0;\n\ttransformsp.DashPhase = 0;\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathAddRectangle(path, 300.0, 40.0, 360.0 - 300.0, 100.0 - 40.0);\n\tuiDrawPathEnd(path);\n\n\t// TODO the example doesn't specify what these should be\n\td2dSolidBrush(&original, d2dBlack, 1.0);\n\td2dSolidBrush(&fill, d2dWhite, 0.5);\n\td2dSolidBrush(&transform, d2dForestGreen, 1.0);\n\t// TODO this needs to be dashed\n\toriginalsp.Thickness = 1.0;\n\toriginalsp.Cap = uiDrawLineCapFlat;\n\toriginalsp.Join = uiDrawLineJoinMiter;\n\toriginalsp.MiterLimit = uiDrawDefaultMiterLimit;\n\ttransformsp.Thickness = 1.0;\n\ttransformsp.Cap = uiDrawLineCapFlat;\n\ttransformsp.Join = uiDrawLineJoinMiter;\n\ttransformsp.MiterLimit = uiDrawDefaultMiterLimit;\n\n\tuiDrawMatrixSetIdentity(&mtranslate);\n\tuiDrawMatrixTranslate(&mtranslate, 20.0, 10.0);\n\tuiDrawMatrixSetIdentity(&mrotate);\n\tuiDrawMatrixRotate(&mrotate,\n\t\t330.0, 70.0,\n\t\t45.0 * (uiPi / 180));\n\n\t// save for when we do the opposite one\n\tuiDrawSave(p->Context);\n\n\tuiDrawStroke(p->Context, path, &original, &originalsp);\n\n\tuiDrawTransform(p->Context, &mrotate);\n\tuiDrawTransform(p->Context, &mtranslate);\n\n\tuiDrawFill(p->Context, path, &fill);\n\tuiDrawStroke(p->Context, path, &transform, &transformsp);\n\n\tuiDrawRestore(p->Context);\n\tuiDrawFreePath(path);\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathAddRectangle(path, 40.0, 40.0, 100.0 - 40.0, 100.0 - 40.0);\n\tuiDrawPathEnd(path);\n\n\tuiDrawMatrixSetIdentity(&mtranslate);\n\tuiDrawMatrixTranslate(&mtranslate, 20.0, 10.0);\n\tuiDrawMatrixSetIdentity(&mrotate);\n\tuiDrawMatrixRotate(&mrotate,\n\t\t70.0, 70.0,\n\t\t45.0 * (uiPi / 180));\n\n\tuiDrawStroke(p->Context, path, &original, &originalsp);\n\n\tuiDrawTransform(p->Context, &mtranslate);\n\tuiDrawTransform(p->Context, &mrotate);\n\n\tuiDrawFill(p->Context, path, &fill);\n\tuiDrawStroke(p->Context, path, &transform, &transformsp);\n\n\tuiDrawFreePath(path);\n}\n\n// TODO https://msdn.microsoft.com/en-us/library/windows/desktop/dd756675%28v=vs.85%29.aspx\n\n// TODO https://msdn.microsoft.com/en-us/library/windows/desktop/dd756673%28v=vs.85%29.aspx\n\n// TODO https://msdn.microsoft.com/en-us/library/windows/desktop/dd756684%28v=vs.85%29.aspx\n\n// TODO dashing https://msdn.microsoft.com/en-us/library/windows/desktop/dd756683%28v=vs.85%29.aspx\n\n// from https://msdn.microsoft.com/en-us/library/windows/desktop/dd756682%28v=vs.85%29.aspx\nstatic void drawD2DComplexShape(uiAreaDrawParams *p)\n{\n\tuiDrawPath *path;\n\tuiDrawBrush black;\n\tuiDrawBrush gradient;\n\tuiDrawBrushGradientStop stops[2];\n\tuiDrawStrokeParams sp;\n\tuiDrawMatrix m;\n\n\tsp.Dashes = NULL;\n\tsp.NumDashes = 0;\n\tsp.DashPhase = 0;\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigure(path, 0, 0);\n\tuiDrawPathLineTo(path, 200, 0);\n\tuiDrawPathBezierTo(path,\n\t\t150, 50,\n\t\t150, 150,\n\t\t200, 200);\n\tuiDrawPathLineTo(path, 0, 200);\n\tuiDrawPathBezierTo(path,\n\t\t50, 150,\n\t\t50, 50,\n\t\t0, 0);\n\tuiDrawPathCloseFigure(path);\n\tuiDrawPathEnd(path);\n\n\td2dSolidBrush(&black, d2dBlack, 1.0);\n\n\tstops[0].Pos =0.0;\n\tstops[0].R = 0.0;\n\tstops[0].G = 1.0;\n\tstops[0].B = 1.0;\n\tstops[0].A = 0.25;\n\tstops[1].Pos = 1.0;\n\tstops[1].R = 0.0;\n\tstops[1].G = 0.0;\n\tstops[1].B = 1.0;\n\tstops[1].A = 1.0;\n\tgradient.Type = uiDrawBrushTypeLinearGradient;\n\tgradient.X0 = 100;\n\tgradient.Y0 = 0;\n\tgradient.X1 = 100;\n\tgradient.Y1 = 200;\n\tgradient.Stops = stops;\n\tgradient.NumStops = 2;\n\n\tuiDrawMatrixSetIdentity(&m);\n\tuiDrawMatrixTranslate(&m, 20, 20);\n\tuiDrawTransform(p->Context, &m);\n\n\tsp.Thickness = 10.0;\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\n\tuiDrawStroke(p->Context, path, &black, &sp);\n\tuiDrawFill(p->Context, path, &gradient);\n\n\tuiDrawFreePath(path);\n}\n\n// TODO https://msdn.microsoft.com/en-us/library/windows/desktop/dd756692%28v=vs.85%29.aspx\n\n// TODO https://msdn.microsoft.com/en-us/library/windows/desktop/dd756686%28v=vs.85%29.aspx\n\n// TODO https://msdn.microsoft.com/en-us/library/windows/desktop/dd756685%28v=vs.85%29.aspx\n\n// TODO https://msdn.microsoft.com/en-us/library/windows/desktop/dd756674%28v=vs.85%29.aspx\n\n// TODO? https://msdn.microsoft.com/en-us/library/windows/desktop/ee329947%28v=vs.85%29.aspx\n\n// TODO https://msdn.microsoft.com/en-us/library/windows/desktop/ff485857%28v=vs.85%29.aspx\n\n// TODO? https://msdn.microsoft.com/en-us/library/windows/desktop/dd756755%28v=vs.85%29.aspx\n\n// TODO go through the API reference and spot examples that aren't listed\n\n// TODO all of these https://msdn.microsoft.com/en-us/library/windows/desktop/dd368187%28v=vs.85%29.aspx\n\n// cairo Samples Page (http://cairographics.org/samples/)\n\nstatic void crsourcergba(uiDrawBrush *brush, double r, double g, double b, double a)\n{\n\tbrush->Type = uiDrawBrushTypeSolid;\n\tbrush->R = r;\n\tbrush->G = g;\n\tbrush->B = b;\n\tbrush->A = a;\n}\n\n// arc\nstatic void drawCSArc(uiAreaDrawParams *p)\n{\n\tdouble xc = 128.0;\n\tdouble yc = 128.0;\n\tdouble radius = 100.0;\n\tdouble angle1 = 45.0  * (uiPi / 180.0);\n\tdouble angle2 = 180.0 * (uiPi / 180.0);\n\tuiDrawBrush source;\n\tuiDrawStrokeParams sp;\n\tuiDrawPath *path;\n\n\tcrsourcergba(&source, 0, 0, 0, 1);\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\tsp.Dashes = NULL;\n\tsp.NumDashes = 0;\n\tsp.DashPhase = 0;\n\n\tsp.Thickness = 10.0;\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigureWithArc(path,\n\t\txc, yc,\n\t\tradius,\n\t\tangle1,\n\t\tangle2 - angle1,\n\t\t0);\n\tuiDrawPathEnd(path);\n\tuiDrawStroke(p->Context, path, &source, &sp);\n\tuiDrawFreePath(path);\n\n\tcrsourcergba(&source, 1, 0.2, 0.2, 0.6);\n\tsp.Thickness = 6.0;\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigureWithArc(path,\n\t\txc, yc,\n\t\t10.0,\n\t\t0, 2 * uiPi,\n\t\t0);\n\tuiDrawPathEnd(path);\n\tuiDrawFill(p->Context, path, &source);\n\tuiDrawFreePath(path);\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigureWithArc(path,\n\t\txc, yc,\n\t\tradius,\n\t\tangle1, 0,\n\t\t0);\n\tuiDrawPathLineTo(path, xc, yc);\n\tuiDrawPathNewFigureWithArc(path,\n\t\txc, yc,\n\t\tradius,\n\t\tangle2, 0,\n\t\t0);\n\tuiDrawPathLineTo(path, xc, yc);\n\tuiDrawPathEnd(path);\n\tuiDrawStroke(p->Context, path, &source, &sp);\n\tuiDrawFreePath(path);\n}\n\n// arc negative\nstatic void drawCSArcNegative(uiAreaDrawParams *p)\n{\n\tdouble xc = 128.0;\n\tdouble yc = 128.0;\n\tdouble radius = 100.0;\n\tdouble angle1 = 45.0  * (uiPi / 180.0);\n\tdouble angle2 = 180.0 * (uiPi / 180.0);\n\tuiDrawBrush source;\n\tuiDrawStrokeParams sp;\n\tuiDrawPath *path;\n\n\tcrsourcergba(&source, 0, 0, 0, 1);\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\tsp.Dashes = NULL;\n\tsp.NumDashes = 0;\n\tsp.DashPhase = 0;\n\n\tsp.Thickness = 10.0;\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigureWithArc(path,\n\t\txc, yc,\n\t\tradius,\n\t\tangle1,\n\t\tangle2 - angle1,\n\t\t1);\n\tuiDrawPathEnd(path);\n\tuiDrawStroke(p->Context, path, &source, &sp);\n\tuiDrawFreePath(path);\n\n\tcrsourcergba(&source, 1, 0.2, 0.2, 0.6);\n\tsp.Thickness = 6.0;\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigureWithArc(path,\n\t\txc, yc,\n\t\t10.0,\n\t\t0, 2 * uiPi,\n\t\t0);\n\tuiDrawPathEnd(path);\n\tuiDrawFill(p->Context, path, &source);\n\tuiDrawFreePath(path);\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigureWithArc(path,\n\t\txc, yc,\n\t\tradius,\n\t\tangle1, 0,\n\t\t0);\n\tuiDrawPathLineTo(path, xc, yc);\n\tuiDrawPathNewFigureWithArc(path,\n\t\txc, yc,\n\t\tradius,\n\t\tangle2, 0,\n\t\t0);\n\tuiDrawPathLineTo(path, xc, yc);\n\tuiDrawPathEnd(path);\n\tuiDrawStroke(p->Context, path, &source, &sp);\n\tuiDrawFreePath(path);\n}\n\n// clip\nstatic void drawCSClip(uiAreaDrawParams *p)\n{\n\tuiDrawBrush source;\n\tuiDrawStrokeParams sp;\n\tuiDrawPath *path;\n\n\tcrsourcergba(&source, 0, 0, 0, 1);\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\tsp.Dashes = NULL;\n\tsp.NumDashes = 0;\n\tsp.DashPhase = 0;\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\n\tuiDrawPathNewFigureWithArc(path,\n\t\t128.0, 128.0,\n\t\t76.8,\n\t\t0, 2 * uiPi,\n\t\t0);\n\tuiDrawPathEnd(path);\n\tuiDrawClip(p->Context, path);\n\tuiDrawFreePath(path);\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathAddRectangle(path, 0, 0, 256, 256);\n\tuiDrawPathEnd(path);\n\tuiDrawFill(p->Context, path, &source);\n\tuiDrawFreePath(path);\n\n\tcrsourcergba(&source, 0, 1, 0, 1);\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigure(path, 0, 0);\n\tuiDrawPathLineTo(path, 256, 256);\n\tuiDrawPathNewFigure(path, 256, 0);\n\tuiDrawPathLineTo(path, 0, 256);\n\tuiDrawPathEnd(path);\n\tsp.Thickness = 10.0;\n\tuiDrawStroke(p->Context, path, &source, &sp);\n\tuiDrawFreePath(path);\n}\n\n// TODO clip image\n\n// curve rectangle\nstatic void drawCSCurveRectangle(uiAreaDrawParams *p)\n{\n\tdouble x0      = 25.6,   /* parameters like cairo_rectangle */\n\t\ty0      = 25.6,\n\t\trect_width  = 204.8,\n\t\trect_height = 204.8,\n\t\tradius = 102.4;   /* and an approximate curvature radius */\n\n\tdouble x1,y1;\n\n\tuiDrawBrush source;\n\tuiDrawStrokeParams sp;\n\tuiDrawPath *path;\n\n\tcrsourcergba(&source, 0, 0, 0, 1);\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\tsp.Dashes = NULL;\n\tsp.NumDashes = 0;\n\tsp.DashPhase = 0;\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\n\tx1=x0+rect_width;\n\ty1=y0+rect_height;\n\tif (!rect_width || !rect_height)\n\t\treturn;\n\tif (rect_width/2 < radius) {\n\t\tif (rect_height/2<radius) {\n\t\t\tuiDrawPathNewFigure(path, x0, (y0 + y1)/2);\n\t\t\tuiDrawPathBezierTo(path, x0 ,y0, x0, y0, (x0 + x1)/2, y0);\n\t\t\tuiDrawPathBezierTo(path, x1, y0, x1, y0, x1, (y0 + y1)/2);\n\t\t\tuiDrawPathBezierTo(path, x1, y1, x1, y1, (x1 + x0)/2, y1);\n\t\t\tuiDrawPathBezierTo(path, x0, y1, x0, y1, x0, (y0 + y1)/2);\n\t\t} else {\n\t\t\tuiDrawPathNewFigure(path, x0, y0 + radius);\n\t\t\tuiDrawPathBezierTo(path, x0 ,y0, x0, y0, (x0 + x1)/2, y0);\n\t\t\tuiDrawPathBezierTo(path, x1, y0, x1, y0, x1, y0 + radius);\n\t\t\tuiDrawPathLineTo(path, x1 , y1 - radius);\n\t\t\tuiDrawPathBezierTo(path, x1, y1, x1, y1, (x1 + x0)/2, y1);\n\t\t\tuiDrawPathBezierTo(path, x0, y1, x0, y1, x0, y1- radius);\n\t\t}\n\t} else {\n\t\tif (rect_height / 2 < radius) {\n\t\t\tuiDrawPathNewFigure(path, x0, (y0 + y1)/2);\n\t\t\tuiDrawPathBezierTo(path, x0 , y0, x0 , y0, x0 + radius, y0);\n\t\t\tuiDrawPathLineTo(path, x1 - radius, y0);\n\t\t\tuiDrawPathBezierTo(path, x1, y0, x1, y0, x1, (y0 + y1)/2);\n\t\t\tuiDrawPathBezierTo(path, x1, y1, x1, y1, x1 - radius, y1);\n\t\t\tuiDrawPathLineTo(path, x0 + radius, y1);\n\t\t\tuiDrawPathBezierTo(path, x0, y1, x0, y1, x0, (y0 + y1)/2);\n\t\t} else {\n\t\t\tuiDrawPathNewFigure(path, x0, y0 + radius);\n\t\t\tuiDrawPathBezierTo(path, x0 , y0, x0 , y0, x0 + radius, y0);\n\t\t\tuiDrawPathLineTo(path, x1 - radius, y0);\n\t\t\tuiDrawPathBezierTo(path, x1, y0, x1, y0, x1, y0 + radius);\n\t\t\tuiDrawPathLineTo(path, x1 , y1 - radius);\n\t\t\tuiDrawPathBezierTo(path, x1, y1, x1, y1, x1 - radius, y1);\n\t\t\tuiDrawPathLineTo(path, x0 + radius, y1);\n\t\t\tuiDrawPathBezierTo(path, x0, y1, x0, y1, x0, y1- radius);\n\t\t}\n\t}\n\tuiDrawPathCloseFigure(path);\n\tuiDrawPathEnd(path);\n\n\tcrsourcergba(&source, 0.5, 0.5, 1, 1.0);\n\tuiDrawFill(p->Context, path, &source);\n\tcrsourcergba(&source, 0.5, 0, 0, 0.5);\n\tsp.Thickness = 10.0;\n\tuiDrawStroke(p->Context, path, &source, &sp);\n\n\tuiDrawFreePath(path);\n}\n\n// curve to\nstatic void drawCSCurveTo(uiAreaDrawParams *p)\n{\n\tdouble x=25.6,  y=128.0;\n\tdouble x1=102.4, y1=230.4,\n\t\tx2=153.6, y2=25.6,\n\t\tx3=230.4, y3=128.0;\n\tuiDrawBrush source;\n\tuiDrawStrokeParams sp;\n\tuiDrawPath *path;\n\n\tcrsourcergba(&source, 0, 0, 0, 1);\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\tsp.Dashes = NULL;\n\tsp.NumDashes = 0;\n\tsp.DashPhase = 0;\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\n\tuiDrawPathNewFigure(path, x, y);\n\tuiDrawPathBezierTo(path, x1, y1, x2, y2, x3, y3);\n\tuiDrawPathEnd(path);\n\tsp.Thickness = 10.0;\n\tuiDrawStroke(p->Context, path, &source, &sp);\n\tuiDrawFreePath(path);\n\n\tcrsourcergba(&source, 1, 0.2, 0.2, 0.6);\n\tsp.Thickness = 6.0;\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigure(path, x, y);\n\tuiDrawPathLineTo(path, x1, y1);\n\tuiDrawPathNewFigure(path, x2, y2);\n\tuiDrawPathLineTo(path, x3, y3);\n\tuiDrawPathEnd(path);\n\tuiDrawStroke(p->Context, path, &source, &sp);\n\tuiDrawFreePath(path);\n}\n\n// dash\nstatic void drawCSDash(uiAreaDrawParams *p)\n{\n\tdouble dashes[] = {\n\t\t50.0,  /* ink */\n\t\t10.0,  /* skip */\n\t\t10.0,  /* ink */\n\t\t10.0   /* skip*/\n\t};\n\tint    ndash  = sizeof (dashes)/sizeof(dashes[0]);\n\tdouble offset = -50.0;\n\n\tuiDrawBrush source;\n\tuiDrawStrokeParams sp;\n\tuiDrawPath *path;\n\n\tcrsourcergba(&source, 0, 0, 0, 1);\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\tsp.Dashes = dashes;\n\tsp.NumDashes = ndash;\n\tsp.DashPhase = offset;\n\tsp.Thickness = 10.0;\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigure(path, 128.0, 25.6);\n\tuiDrawPathLineTo(path, 230.4, 230.4);\n\tuiDrawPathLineTo(path, 230.4 -102.4, 230.4 + 0.0);\n\tuiDrawPathBezierTo(path,\n\t\t51.2, 230.4,\n\t\t51.2, 128.0,\n\t\t128.0, 128.0);\n\tuiDrawPathEnd(path);\n\n\tuiDrawStroke(p->Context, path, &source, &sp);\n\tuiDrawFreePath(path);\n}\n\n// fill and stroke2\nstatic void drawCSFillAndStroke2(uiAreaDrawParams *p)\n{\n\tuiDrawBrush source;\n\tuiDrawStrokeParams sp;\n\tuiDrawPath *path;\n\n\tcrsourcergba(&source, 0, 0, 0, 1);\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\tsp.Dashes = NULL;\n\tsp.NumDashes = 0;\n\tsp.DashPhase = 0;\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\n\tuiDrawPathNewFigure(path, 128.0, 25.6);\n\tuiDrawPathLineTo(path, 230.4, 230.4);\n\tuiDrawPathLineTo(path, 230.4 - 102.4, 230.4 + 0.0);\n\tuiDrawPathBezierTo(path, 51.2, 230.4, 51.2, 128.0, 128.0, 128.0);\n\tuiDrawPathCloseFigure(path);\n\n\tuiDrawPathNewFigure(path, 64.0, 25.6);\n\tuiDrawPathLineTo(path, 64.0 + 51.2, 25.6 + 51.2);\n\tuiDrawPathLineTo(path, 64.0 + 51.2 -51.2, 25.6 + 51.2 + 51.2);\n\tuiDrawPathLineTo(path, 64.0 + 51.2 -51.2 -51.2, 25.6 + 51.2 + 51.2 -51.2);\n\tuiDrawPathCloseFigure(path);\n\n\tuiDrawPathEnd(path);\n\n\tsp.Thickness = 10.0;\n\tcrsourcergba(&source, 0, 0, 1, 1);\n\tuiDrawFill(p->Context, path, &source);\n\tcrsourcergba(&source, 0, 0, 0, 1);\n\tuiDrawStroke(p->Context, path, &source, &sp);\n\tuiDrawFreePath(path);\n}\n\n// fill style\nstatic void drawCSFillStyle(uiAreaDrawParams *p)\n{\n\tuiDrawBrush source;\n\tuiDrawStrokeParams sp;\n\tuiDrawPath *path;\n\tuiDrawMatrix m;\n\n\tcrsourcergba(&source, 0, 0, 0, 1);\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\tsp.Dashes = NULL;\n\tsp.NumDashes = 0;\n\tsp.DashPhase = 0;\n\n\tsp.Thickness = 6;\n\n\tpath = uiDrawNewPath(uiDrawFillModeAlternate);\n\tuiDrawPathAddRectangle(path, 12, 12, 232, 70);\n\tuiDrawPathNewFigureWithArc(path,\n\t\t64, 64,\n\t\t40,\n\t\t0, 2*uiPi,\n\t\t0);\n\tuiDrawPathNewFigureWithArc(path,\n\t\t192, 64,\n\t\t40,\n\t\t0, -2*uiPi,\n\t\t1);\n\tuiDrawPathEnd(path);\n\n\tcrsourcergba(&source, 0, 0.7, 0, 1);\n\tuiDrawFill(p->Context, path, &source);\n\tcrsourcergba(&source, 0, 0, 0, 1);\n\tuiDrawStroke(p->Context, path, &source, &sp);\n\tuiDrawFreePath(path);\n\n\tuiDrawMatrixSetIdentity(&m);\n\tuiDrawMatrixTranslate(&m, 0, 128);\n\tuiDrawTransform(p->Context, &m);\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathAddRectangle(path, 12, 12, 232, 70);\n\tuiDrawPathNewFigureWithArc(path,\n\t\t64, 64,\n\t\t40,\n\t\t0, 2*uiPi,\n\t\t0);\n\tuiDrawPathNewFigureWithArc(path,\n\t\t192, 64,\n\t\t40,\n\t\t0, -2*uiPi,\n\t\t1);\n\tuiDrawPathEnd(path);\n\n\tcrsourcergba(&source, 0, 0, 0.9, 1);\n\tuiDrawFill(p->Context, path, &source);\n\tcrsourcergba(&source, 0, 0, 0, 1);\n\tuiDrawStroke(p->Context, path, &source, &sp);\n\tuiDrawFreePath(path);\n}\n\n// TOOD gradient (radial gradient with two circles)\n\n// TODO image\n\n// TODO imagepattern\n\n// multi segment caps\nstatic void drawCSMultiCaps(uiAreaDrawParams *p)\n{\n\tuiDrawBrush source;\n\tuiDrawStrokeParams sp;\n\tuiDrawPath *path;\n\n\tcrsourcergba(&source, 0, 0, 0, 1);\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\tsp.Dashes = NULL;\n\tsp.NumDashes = 0;\n\tsp.DashPhase = 0;\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\n\tuiDrawPathNewFigure(path, 50.0, 75.0);\n\tuiDrawPathLineTo(path, 200.0, 75.0);\n\n\tuiDrawPathNewFigure(path, 50.0, 125.0);\n\tuiDrawPathLineTo(path, 200.0, 125.0);\n\n\tuiDrawPathNewFigure(path, 50.0, 175.0);\n\tuiDrawPathLineTo(path, 200.0, 175.0);\n\tuiDrawPathEnd(path);\n\n\tsp.Thickness = 30.0;\n\tsp.Cap = uiDrawLineCapRound;\n\tuiDrawStroke(p->Context, path, &source, &sp);\n\tuiDrawFreePath(path);\n}\n\n// rounded rectangle\nstatic void drawCSRoundRect(uiAreaDrawParams *p)\n{\n\tdouble x         = 25.6,        /* parameters like cairo_rectangle */\n\t\ty         = 25.6,\n\t\twidth         = 204.8,\n\t\theight        = 204.8,\n\t\taspect        = 1.0,     /* aspect ratio */\n\t\tcorner_radius = height / 10.0;   /* and corner curvature radius */\n\n\tdouble radius = corner_radius / aspect;\n\tdouble degrees = uiPi / 180.0;\n\n\tuiDrawBrush source;\n\tuiDrawStrokeParams sp;\n\tuiDrawPath *path;\n\n\tcrsourcergba(&source, 0, 0, 0, 1);\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\tsp.Dashes = NULL;\n\tsp.NumDashes = 0;\n\tsp.DashPhase = 0;\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\n\t// top right corner\n\tuiDrawPathNewFigureWithArc(path,\n\t\tx + width - radius, y + radius,\n\t\tradius,\n\t\t-90 * degrees, uiPi / 2,\n\t\t0);\n\t// bottom right corner\n\tuiDrawPathArcTo(path,\n\t\tx + width - radius, y + height - radius,\n\t\tradius,\n\t\t0 * degrees, uiPi / 2,\n\t\t0);\n\t// bottom left corner\n\tuiDrawPathArcTo(path,\n\t\tx + radius, y + height - radius,\n\t\tradius,\n\t\t90 * degrees, uiPi / 2,\n\t\t0);\n\t// top left corner\n\tuiDrawPathArcTo(path,\n\t\tx + radius, y + radius,\n\t\tradius,\n\t\t180 * degrees, uiPi / 2,\n\t\t0);\n\tuiDrawPathCloseFigure(path);\n\tuiDrawPathEnd(path);\n\n\tcrsourcergba(&source, 0.5, 0.5, 1, 1);\n\tuiDrawFill(p->Context, path, &source);\n\tcrsourcergba(&source, 0.5, 0, 0, 0.5);\n\tsp.Thickness = 10.0;\n\tuiDrawStroke(p->Context, path, &source, &sp);\n\tuiDrawFreePath(path);\n}\n\n// set line cap\nstatic void drawCSSetLineCap(uiAreaDrawParams *p)\n{\n\tuiDrawBrush source;\n\tuiDrawStrokeParams sp;\n\tuiDrawPath *path;\n\n\tcrsourcergba(&source, 0, 0, 0, 1);\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\tsp.Dashes = NULL;\n\tsp.NumDashes = 0;\n\tsp.DashPhase = 0;\n\n\tsp.Thickness = 30.0;\n\n\tsp.Cap = uiDrawLineCapFlat;\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigure(path, 64.0, 50.0);\n\tuiDrawPathLineTo(path, 64.0, 200.0);\n\tuiDrawPathEnd(path);\n\tuiDrawStroke(p->Context, path, &source, &sp);\n\tuiDrawFreePath(path);\n\n\tsp.Cap = uiDrawLineCapRound;\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigure(path, 128.0, 50.0);\n\tuiDrawPathLineTo(path, 128.0, 200.0);\n\tuiDrawPathEnd(path);\n\tuiDrawStroke(p->Context, path, &source, &sp);\n\tuiDrawFreePath(path);\n\n\tsp.Cap = uiDrawLineCapSquare;\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigure(path, 192.0, 50.0);\n\tuiDrawPathLineTo(path, 192.0, 200.0);\n\tuiDrawPathEnd(path);\n\tuiDrawStroke(p->Context, path, &source, &sp);\n\tuiDrawFreePath(path);\n\n\t// draw helping lines\n\t// keep the square cap to match the reference picture on the cairo website\n\tcrsourcergba(&source, 1, 0.2, 0.2, 1);\n\tsp.Thickness = 2.56;\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigure(path, 64.0, 50.0);\n\tuiDrawPathLineTo(path, 64.0, 200.0);\n\tuiDrawPathNewFigure(path, 128.0, 50.0);\n\tuiDrawPathLineTo(path, 128.0, 200.0);\n\tuiDrawPathNewFigure(path, 192.0, 50.0);\n\tuiDrawPathLineTo(path, 192.0, 200.0);\n\tuiDrawPathEnd(path);\n\tuiDrawStroke(p->Context, path, &source, &sp);\n\tuiDrawFreePath(path);\n}\n\n// set line join\nstatic void drawCSSetLineJoin(uiAreaDrawParams *p)\n{\n\tuiDrawBrush source;\n\tuiDrawStrokeParams sp;\n\tuiDrawPath *path;\n\n\tcrsourcergba(&source, 0, 0, 0, 1);\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\tsp.Dashes = NULL;\n\tsp.NumDashes = 0;\n\tsp.DashPhase = 0;\n\n\tsp.Thickness = 40.96;\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigure(path, 76.8, 84.48);\n\tuiDrawPathLineTo(path, 76.8 + 51.2, 84.48 -51.2);\n\tuiDrawPathLineTo(path, 76.8 + 51.2 + 51.2, 84.48 - 51.2 + 51.2);\n\tuiDrawPathEnd(path);\n\tsp.Join = uiDrawLineJoinMiter;\n\tuiDrawStroke(p->Context, path, &source, &sp);\n\tuiDrawFreePath(path);\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigure(path, 76.8, 161.28);\n\tuiDrawPathLineTo(path, 76.8 + 51.2, 161.28 -51.2);\n\tuiDrawPathLineTo(path, 76.8 + 51.2 + 51.2, 161.28 - 51.2 + 51.2);\n\tuiDrawPathEnd(path);\n\tsp.Join = uiDrawLineJoinBevel;\n\tuiDrawStroke(p->Context, path, &source, &sp);\n\tuiDrawFreePath(path);\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigure(path, 76.8, 238.08);\n\tuiDrawPathLineTo(path, 76.8 + 51.2, 238.08 -51.2);\n\tuiDrawPathLineTo(path, 76.8 + 51.2 + 51.2, 238.08 - 51.2 + 51.2);\n\tuiDrawPathEnd(path);\n\tsp.Join = uiDrawLineJoinRound;\n\tuiDrawStroke(p->Context, path, &source, &sp);\n\tuiDrawFreePath(path);\n}\n\n// TODO text\n\n// TODO text align center\n\n// TODO text extents\n\n// Quartz 2D Programming Guide\n\nstatic void cgaddrect(uiDrawPath *path, uiAreaDrawParams *p, double x, double y, double width, double height)\n{\n\tuiDrawPathAddRectangle(path,\n\t\tx, p->AreaHeight - y - height,\n\t\twidth, height);\n}\n\n// Graphics Contexts > Creating a Window Graphics Context in Mac OS X\nstatic void drawQ2DCreateWindowGC(uiAreaDrawParams *p)\n{\n\tuiDrawPath *path;\n\tuiDrawBrush brush;\n\n\tcrsourcergba(&brush, 1, 0, 0, 1);\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tcgaddrect(path, p, 0, 0, 200, 100);\n\tuiDrawPathEnd(path);\n\tuiDrawFill(p->Context, path, &brush);\n\tuiDrawFreePath(path);\n\n\tcrsourcergba(&brush, 0, 0, 1, .5);\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tcgaddrect(path, p, 0, 0, 100, 200);\n\tuiDrawPathEnd(path);\n\tuiDrawFill(p->Context, path, &brush);\n\tuiDrawFreePath(path);\n}\n\n// TODO Patterns page?\n\n// TODO Shadows page?\n\n// TODO Gradients page (includes some circle-circle radial gradients)\n\n// TODO Transparency Layers page?\n\n// TODO Core Graphics Layer Drawing page?\n\n// Cocoa Drawing Guide\n\n// TODO Advanced Drawing Techniques page?\n\n// TODO Text page, if any?\n\nstatic const struct drawtest tests[] = {\n\t{ \"Original uiArea test\", drawOriginal },\n\t{ \"Arc test\", drawArcs },\n\t{ \"Direct2D: Direct2D Quickstart for Windows 8\", drawD2DW8QS },\n\t{ \"Direct2D: Creating a Simple Direct2D Application\", drawD2DSimpleApp },\n\t{ \"Direct2D: How to Create a Solid Color Brush\", drawD2DSolidBrush },\n\t{ \"Direct2D: How to Create a Linear Gradient Brush\", drawD2DLinearBrush },\n\t{ \"Direct2D: How to Create a Radial Gradient Brush\", drawD2DRadialBrush },\n\t{ \"Direct2D: Path Geometries Overview\", drawD2DPathGeometries },\n\t{ \"Direct2D: How to Create Geometry Groups\", drawD2DGeometryGroup },\n\t{ \"Direct2D: How to Rotate an Object\", drawD2DRotate },\n\t{ \"Direct2D: How to Scale an Object\", drawD2DScale },\n\t{ \"Direct2D: How to Skew an Object\", drawD2DSkew },\n\t{ \"Direct2D: How to Translate an Object\", drawD2DTranslate },\n\t{ \"Direct2D: How to Apply Multiple Transforms to an Object\", drawD2DMultiTransforms },\n\t{ \"Direct2D: How to Draw and Fill a Complex Shape\", drawD2DComplexShape },\n\t{ \"cairo samples: arc\", drawCSArc },\n\t{ \"cairo samples: arc negative\", drawCSArcNegative },\n\t{ \"cairo samples: clip\", drawCSClip },\n\t{ \"cairo samples: curve rectangle\", drawCSCurveRectangle },\n\t{ \"cairo samples: curve to\", drawCSCurveTo },\n\t{ \"cairo samples: dash\", drawCSDash },\n\t{ \"cairo samples: fill and stroke2\", drawCSFillAndStroke2 },\n\t{ \"cairo samples: fill style\", drawCSFillStyle },\n\t{ \"cairo samples: multi segment caps\", drawCSMultiCaps },\n\t{ \"cairo samples: rounded rectangle\", drawCSRoundRect },\n\t{ \"cairo samples: set line cap\", drawCSSetLineCap },\n\t{ \"cairo samples: set line join\", drawCSSetLineJoin },\n\t{ \"Quartz 2D PG: Creating a Window Graphics Context in Mac OS X\", drawQ2DCreateWindowGC },\n\t{ NULL, NULL },\n};\n\nvoid runDrawTest(int n, uiAreaDrawParams *p)\n{\n\t(*(tests[n].draw))(p);\n}\n\nvoid populateComboboxWithTests(uiCombobox *c)\n{\n\tsize_t i;\n\n\tfor (i = 0; tests[i].name != NULL; i++)\n\t\tuiComboboxAppend(c, tests[i].name);\n}\n"
  },
  {
    "path": "test/images/gen.go",
    "content": "// 25 june 2016\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"image\"\n\t\"image/draw\"\n\t_ \"image/png\"\n)\n\ntype img struct {\n\tfilename\tstring\n\tdata\t\t[]byte\n\twidth\tint\n\theight\tint\n\tstride\tint\n}\n\nfunc main() {\n\tif len(os.Args[1:]) == 0 {\n\t\tpanic(\"no files specified\")\n\t}\n\n\timages := make([]*img, 0, len(os.Args[1:]))\n\tfor _, fn := range os.Args[1:] {\n\t\tf, err := os.Open(fn)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tii, _, err := image.Decode(f)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tf.Close()\n\n\t\ti := image.NewRGBA(ii.Bounds())\n\t\tdraw.Draw(i, i.Rect, ii, ii.Bounds().Min, draw.Src)\n\n\t\tim := &img{\n\t\t\tfilename:\t\tfn,\n\t\t\tdata:\t\t\ti.Pix,\n\t\t\twidth:\t\ti.Rect.Dx(),\n\t\t\theight:\t\ti.Rect.Dy(),\n\t\t\tstride:\t\ti.Stride,\n\t\t}\n\t\timages = append(images, im)\n\t}\n\n\tfmt.Println(\"// auto-generated by images/gen.go\")\n\tfmt.Println(\"#include \\\"test.h\\\"\")\n\tfmt.Println()\n\tfor i, im := range images {\n\t\tfmt.Printf(\"static const uint8_t dat%d[] = {\", i)\n\t\tfor j := 0; j < len(im.data); j++ {\n\t\t\tif (j % 16) == 0 {\n\t\t\t\tfmt.Printf(\"\\n\\t\")\n\t\t\t} else {\n\t\t\t\tfmt.Printf(\" \")\n\t\t\t}\n\t\t\tfmt.Printf(\"0x%02X,\", im.data[j])\n\n\t\t}\n\t\tfmt.Println(\"\\n};\")\n\t\tfmt.Println()\n\t}\n\tfmt.Println(\"static const struct {\")\n\tfmt.Println(\"\tconst char *name;\")\n\tfmt.Println(\"\tvoid *data;\")\n\tfmt.Println(\"\tint width;\")\n\tfmt.Println(\"\tint height;\")\n\tfmt.Println(\"\tint stride;\")\n\tfmt.Println(\"} files[] = {\")\n\tfor i, im := range images {\n\t\tfmt.Printf(\"\t{ %q, dat%d, %d, %d, %d },\\n\",\n\t\t\tim.filename, i, im.width, im.height, im.stride)\n\t}\n\tfmt.Println(\"};\")\n\tfmt.Println()\n\tfmt.Println(\"void appendImageNamed(uiImage *img, const char *name)\")\n\tfmt.Println(\"{\")\n\tfmt.Println(\"\tint i;\")\n\tfmt.Println(\"\")\n\tfmt.Println(\"\ti = 0;\")\n\tfmt.Println(\"\tfor (;;) {\")\n\tfmt.Println(\"\t\tif (strcmp(name, files[i].name) == 0) {\")\n\tfmt.Println(\"\t\t\tuiImageAppend(img, files[i].data, files[i].width, files[i].height, files[i].stride);\")\n\tfmt.Println(\"\t\t\treturn;\")\n\tfmt.Println(\"\t\t}\")\n\tfmt.Println(\"\t\ti++;\")\n\tfmt.Println(\"\t}\")\n\tfmt.Println(\"}\")\n\tfmt.Println()\n}\n"
  },
  {
    "path": "test/images.c",
    "content": "// auto-generated by images/gen.go\n#include \"test.h\"\n\nstatic const uint8_t dat0[] = {\n\t0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF,\n\t0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF,\n\t0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF,\n\t0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF,\n\t0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF,\n\t0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF,\n\t0x8A, 0xC3, 0xFF, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n};\n\nstatic const uint8_t dat1[] = {\n\t0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF,\n\t0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF,\n\t0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF,\n\t0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF,\n\t0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF,\n\t0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF,\n\t0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF,\n\t0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF,\n\t0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF,\n\t0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xE5, 0x60, 0xFC, 0xFF,\n\t0xE5, 0x60, 0xFC, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF,\n\t0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF,\n\t0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF,\n\t0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF,\n\t0xFF, 0x40, 0x79, 0xFF, 0xFF, 0x40, 0x79, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF,\n\t0x8A, 0xC3, 0xFF, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0x8A, 0xC3, 0xFF, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0x8A, 0xC3, 0xFF, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF,\n\t0xFF, 0xFB, 0x43, 0xFF, 0xFF, 0xFB, 0x43, 0xFF, 0x8A, 0xC3, 0xFF, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n\t0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF, 0x89, 0xC5, 0x7C, 0xFF,\n};\n\nstatic const uint8_t dat2[] = {\n\t0x67, 0x67, 0x67, 0xAC, 0x81, 0x81, 0x81, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x81, 0x81, 0x81, 0xFF,\n\t0x81, 0x81, 0x81, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x81, 0x81, 0x81, 0xFF,\n\t0x81, 0x81, 0x81, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x81, 0x81, 0x81, 0xFF,\n\t0x81, 0x81, 0x81, 0xFF, 0x2B, 0x2B, 0x2B, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n\t0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n\t0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n\t0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB7, 0xB7, 0xB7, 0xFF, 0xB7, 0xB7, 0xB7, 0xFF,\n\t0xB7, 0xB7, 0xB8, 0xFF, 0x9E, 0x9E, 0x9F, 0xFF, 0xB8, 0xB8, 0xB8, 0xFF, 0xB8, 0xB8, 0xB8, 0xFF,\n\t0x9F, 0x9F, 0x9F, 0xFF, 0xB8, 0xB8, 0xB9, 0xFF, 0xB8, 0xB8, 0xB9, 0xFF, 0x9F, 0x9F, 0x9F, 0xFF,\n\t0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9E, 0x9E, 0x9E, 0xFF, 0x9F, 0x9F, 0x9E, 0xFF,\n\t0x9F, 0x9F, 0x9F, 0xFF, 0x9F, 0x9F, 0x9F, 0xFF, 0x9F, 0x9F, 0x9F, 0xFF, 0x9F, 0x9F, 0x9F, 0xFF,\n\t0x9F, 0x9F, 0x9F, 0xFF, 0x9F, 0x9F, 0x9F, 0xFF, 0x9F, 0x9F, 0x9F, 0xFF, 0x9F, 0x9F, 0x9F, 0xFF,\n\t0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB7, 0xB8, 0xB7, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF,\n\t0xC3, 0xC3, 0xC3, 0xFF, 0x9F, 0x9F, 0x9F, 0xFF, 0xEE, 0xEE, 0xEE, 0xFF, 0xEE, 0xEE, 0xEE, 0xFF,\n\t0xC2, 0xC2, 0xC2, 0xFF, 0xEE, 0xEF, 0xEE, 0xFF, 0xEF, 0xEE, 0xEF, 0xFF, 0xC2, 0xC2, 0xC2, 0xFF,\n\t0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0xB1, 0xB1, 0xFF, 0xB8, 0xB8, 0xB8, 0xFF,\n\t0xB9, 0xB8, 0xB9, 0xFF, 0x9F, 0x9F, 0x9F, 0xFF, 0xE0, 0xE0, 0xE0, 0xFF, 0xE0, 0xE1, 0xE0, 0xFF,\n\t0xC2, 0xC2, 0xC2, 0xFF, 0xE1, 0xE0, 0xE0, 0xFF, 0xE1, 0xE1, 0xE1, 0xFF, 0xC2, 0xC2, 0xC2, 0xFF,\n\t0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB9, 0xB8, 0xB8, 0xFF, 0xC4, 0xC4, 0xC4, 0xFF,\n\t0xC3, 0xC4, 0xC4, 0xFF, 0x9F, 0x9F, 0x9F, 0xFF, 0xEE, 0xEE, 0xEF, 0xFF, 0xEF, 0xEE, 0xEF, 0xFF,\n\t0xC2, 0xC2, 0xC2, 0xFF, 0xEF, 0xEF, 0xEF, 0xFF, 0xF0, 0xEF, 0xEF, 0xFF, 0xC2, 0xC2, 0xC2, 0xFF,\n\t0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB1, 0xB1, 0xB1, 0xFF, 0xB9, 0xB9, 0xB8, 0xFF,\n\t0xB9, 0xB9, 0xB9, 0xFF, 0x9F, 0x9F, 0x9F, 0xFF, 0xC9, 0xA6, 0xA5, 0xFF, 0xAE, 0x3A, 0x36, 0xFF,\n\t0xA5, 0x0F, 0x0C, 0xFF, 0xA4, 0x02, 0x01, 0xFF, 0xA4, 0x02, 0x01, 0xFF, 0xA4, 0x0D, 0x0A, 0xFF,\n\t0xB0, 0x3B, 0x37, 0xFF, 0x8D, 0x69, 0x69, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB9, 0xB9, 0xB9, 0xFF, 0xC4, 0xC4, 0xC4, 0xFF,\n\t0xC4, 0xC4, 0xC4, 0xFF, 0x9E, 0x40, 0x3D, 0xFF, 0xA7, 0x0A, 0x07, 0xFF, 0xC6, 0x64, 0x53, 0xFF,\n\t0xCB, 0x71, 0x5C, 0xFF, 0xC9, 0x73, 0x5D, 0xFF, 0xC5, 0x71, 0x5B, 0xFF, 0xBF, 0x6C, 0x59, 0xFF,\n\t0xC0, 0x6E, 0x61, 0xFF, 0xAC, 0x20, 0x1D, 0xFF, 0x7E, 0x29, 0x27, 0xC1, 0x19, 0x19, 0x19, 0x19,\n\t0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB2, 0xB2, 0xB2, 0xFF, 0xB9, 0xB9, 0xB9, 0xFF,\n\t0xA6, 0x5C, 0x5B, 0xFF, 0xAF, 0x20, 0x17, 0xFF, 0xCB, 0x72, 0x5D, 0xFF, 0xC7, 0x65, 0x4D, 0xFF,\n\t0xBB, 0x53, 0x38, 0xFF, 0xB6, 0x51, 0x36, 0xFF, 0xB0, 0x4E, 0x35, 0xFF, 0xB3, 0x5D, 0x47, 0xFF,\n\t0xAE, 0x5B, 0x45, 0xFF, 0xA9, 0x57, 0x43, 0xFF, 0xAA, 0x2F, 0x28, 0xFF, 0x64, 0x17, 0x16, 0xA1,\n\t0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB9, 0xB9, 0xB9, 0xFF, 0xC4, 0xC4, 0xC4, 0xFF,\n\t0xA5, 0x14, 0x12, 0xFF, 0xCA, 0x6F, 0x5A, 0xFF, 0xBF, 0x54, 0x39, 0xFF, 0xBA, 0x52, 0x37, 0xFF,\n\t0xB4, 0x4F, 0x36, 0xFF, 0xAF, 0x4D, 0x34, 0xFF, 0x88, 0x65, 0x56, 0xFF, 0x76, 0x5F, 0x5A, 0xFF,\n\t0x71, 0x5B, 0x56, 0xFF, 0x6B, 0x58, 0x53, 0xFF, 0x77, 0x60, 0x5B, 0xFF, 0x98, 0x0C, 0x0B, 0xF7,\n\t0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB2, 0xB2, 0xB2, 0xFF, 0xB9, 0xB9, 0xB9, 0xFF,\n\t0xA4, 0x02, 0x01, 0xFF, 0xC7, 0x69, 0x51, 0xFF, 0xB8, 0x51, 0x37, 0xFF, 0xB2, 0x4E, 0x36, 0xFF,\n\t0xAD, 0x4C, 0x34, 0xFF, 0xAE, 0x65, 0x3A, 0xFF, 0x95, 0x99, 0x5D, 0xFF, 0x1D, 0x84, 0xA0, 0xFF,\n\t0x17, 0x7F, 0x99, 0xFF, 0x3D, 0x91, 0xA5, 0xFF, 0x3B, 0x8E, 0xA0, 0xFF, 0xA2, 0x01, 0x01, 0xFF,\n\t0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB9, 0xB9, 0xB9, 0xFF, 0xC4, 0xC4, 0xC4, 0xFF,\n\t0x9F, 0x12, 0x12, 0xFF, 0xCA, 0x7E, 0x6D, 0xFF, 0xB8, 0x5F, 0x48, 0xFF, 0xAB, 0x4C, 0x33, 0xFF,\n\t0xA6, 0x49, 0x32, 0xFF, 0xB1, 0x8B, 0x44, 0xFF, 0xB8, 0xBC, 0x50, 0xFF, 0x35, 0x80, 0x86, 0xFF,\n\t0x50, 0x9C, 0xAD, 0xFF, 0x27, 0x83, 0x97, 0xFF, 0x49, 0x6E, 0x77, 0xFF, 0x92, 0x08, 0x08, 0xF7,\n\t0x81, 0x81, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n\t0xBC, 0x78, 0x78, 0xFF, 0x9C, 0x1D, 0x1A, 0xFF, 0xBD, 0x7E, 0x6D, 0xFF, 0xBE, 0x7F, 0x70, 0xFF,\n\t0xBD, 0x83, 0x74, 0xFF, 0xCD, 0xC7, 0x87, 0xFF, 0xCB, 0xD0, 0x89, 0xFF, 0xB4, 0xB4, 0x8A, 0xFF,\n\t0x5C, 0x93, 0xA0, 0xFF, 0x44, 0x65, 0x6D, 0xFF, 0x7B, 0x16, 0x18, 0xFF, 0x66, 0x1D, 0x1D, 0xA1,\n\t0x69, 0x69, 0x69, 0xB4, 0x81, 0x81, 0x81, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x81, 0x81, 0x81, 0xFF,\n\t0x81, 0x81, 0x81, 0xFF, 0x93, 0x40, 0x40, 0xFF, 0x9E, 0x06, 0x05, 0xFF, 0x99, 0x46, 0x3D, 0xFF,\n\t0x9E, 0x62, 0x4D, 0xFF, 0xA6, 0xA1, 0x4C, 0xFF, 0x9F, 0x9B, 0x4A, 0xFF, 0x94, 0x88, 0x45, 0xFF,\n\t0x4B, 0x3C, 0x43, 0xFF, 0x9A, 0x05, 0x05, 0xFF, 0x77, 0x1F, 0x1F, 0xBA, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x16, 0x16, 0x55, 0x7F, 0x17, 0x17, 0xD3,\n\t0x9C, 0x0B, 0x0B, 0xF9, 0xA3, 0x02, 0x01, 0xFF, 0xA2, 0x01, 0x01, 0xFF, 0x9C, 0x0B, 0x0B, 0xF9,\n\t0x88, 0x1E, 0x1E, 0xD3, 0x41, 0x22, 0x22, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n};\n\nstatic const uint8_t dat3[] = {\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x30, 0x30, 0x30, 0x52, 0x91, 0x91, 0x91, 0xF2, 0x99, 0x99, 0x99, 0xFF, 0x9C, 0x9C, 0x9C, 0xFF,\n\t0x9E, 0x9E, 0x9E, 0xFF, 0x9C, 0x9C, 0x9C, 0xFF, 0x99, 0x99, 0x99, 0xFF, 0x96, 0x96, 0x96, 0xFF,\n\t0x93, 0x93, 0x93, 0xFF, 0x8F, 0x8F, 0x8F, 0xFF, 0x8C, 0x8C, 0x8C, 0xFF, 0x88, 0x88, 0x88, 0xFF,\n\t0x84, 0x84, 0x84, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0x7D, 0x7D, 0x7D, 0xFF, 0x7A, 0x7A, 0x7A, 0xFF,\n\t0x76, 0x76, 0x76, 0xFF, 0x72, 0x72, 0x72, 0xFF, 0x6F, 0x6F, 0x6F, 0xFF, 0x6B, 0x6B, 0x6B, 0xFF,\n\t0x68, 0x68, 0x68, 0xFF, 0x64, 0x64, 0x64, 0xFF, 0x5F, 0x5F, 0x5F, 0xF2, 0x1E, 0x1E, 0x1E, 0x52,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x8D, 0x8D, 0x8D, 0xEF, 0xFD, 0xFD, 0xFD, 0xFF, 0xFD, 0xFD, 0xFD, 0xFF, 0xFE, 0xFE, 0xFE, 0xFF,\n\t0xFE, 0xFE, 0xFE, 0xFF, 0xFE, 0xFE, 0xFE, 0xFF, 0xFD, 0xFD, 0xFD, 0xFF, 0xFD, 0xFD, 0xFD, 0xFF,\n\t0xFD, 0xFD, 0xFD, 0xFF, 0xFD, 0xFD, 0xFD, 0xFF, 0xFC, 0xFC, 0xFC, 0xFF, 0xFC, 0xFC, 0xFC, 0xFF,\n\t0xFC, 0xFC, 0xFC, 0xFF, 0xFB, 0xFB, 0xFB, 0xFF, 0xFB, 0xFB, 0xFB, 0xFF, 0xFB, 0xFB, 0xFB, 0xFF,\n\t0xFA, 0xFA, 0xFA, 0xFF, 0xFA, 0xFA, 0xFA, 0xFF, 0xFA, 0xFA, 0xFA, 0xFF, 0xF9, 0xF9, 0xF9, 0xFF,\n\t0xF9, 0xF9, 0xF9, 0xFF, 0xF9, 0xF9, 0xF9, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x5D, 0x5D, 0x5D, 0xEF,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x93, 0x93, 0x93, 0xFF, 0xFD, 0xFD, 0xFD, 0xFF, 0xDC, 0xDC, 0xDC, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF,\n\t0xDE, 0xDE, 0xDE, 0xFF, 0xDE, 0xDE, 0xDE, 0xFF, 0xDF, 0xDF, 0xDF, 0xFF, 0xDF, 0xDF, 0xDF, 0xFF,\n\t0xE0, 0xE0, 0xE0, 0xFF, 0xE1, 0xE1, 0xE1, 0xFF, 0xE1, 0xE1, 0xE1, 0xFF, 0xE1, 0xE1, 0xE1, 0xFF,\n\t0xE2, 0xE2, 0xE2, 0xFF, 0xE2, 0xE2, 0xE2, 0xFF, 0xE2, 0xE2, 0xE2, 0xFF, 0xE2, 0xE2, 0xE2, 0xFF,\n\t0xE3, 0xE3, 0xE3, 0xFF, 0xE3, 0xE3, 0xE3, 0xFF, 0xE3, 0xE3, 0xE3, 0xFF, 0xE3, 0xE3, 0xE3, 0xFF,\n\t0xE3, 0xE3, 0xE3, 0xFF, 0xE2, 0xE2, 0xE2, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x5D, 0x5D, 0x5D, 0xFF,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x92, 0x92, 0x92, 0xFF, 0xFE, 0xFE, 0xFE, 0xFF, 0xDD, 0xDD, 0xDD, 0xFF, 0x78, 0x78, 0x78, 0xFF,\n\t0x8D, 0x8D, 0x8D, 0xFF, 0x8E, 0x8E, 0x8E, 0xFF, 0x8F, 0x8F, 0x8F, 0xFF, 0x8F, 0x8F, 0x8F, 0xFF,\n\t0x78, 0x78, 0x78, 0xFF, 0x8F, 0x8F, 0x8F, 0xFF, 0x90, 0x90, 0x90, 0xFF, 0x90, 0x90, 0x90, 0xFF,\n\t0x7A, 0x7A, 0x7A, 0xFF, 0x91, 0x91, 0x91, 0xFF, 0x91, 0x91, 0x91, 0xFF, 0x91, 0x91, 0x91, 0xFF,\n\t0x7B, 0x7B, 0x7B, 0xFF, 0x91, 0x91, 0x91, 0xFF, 0x91, 0x91, 0x91, 0xFF, 0x91, 0x91, 0x91, 0xFF,\n\t0x7D, 0x7D, 0x7D, 0xFF, 0xE3, 0xE3, 0xE3, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x5D, 0x5D, 0x5D, 0xFF,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x90, 0x90, 0x90, 0xFF, 0xFE, 0xFE, 0xFE, 0xFF, 0xDE, 0xDE, 0xDE, 0xFF, 0x94, 0x94, 0x94, 0xFF,\n\t0xB0, 0xB0, 0xB0, 0xFF, 0xB1, 0xB1, 0xB1, 0xFF, 0xB1, 0xB1, 0xB1, 0xFF, 0xB1, 0xB1, 0xB1, 0xFF,\n\t0x96, 0x96, 0x96, 0xFF, 0xB2, 0xB2, 0xB2, 0xFF, 0xB3, 0xB3, 0xB3, 0xFF, 0xB3, 0xB3, 0xB3, 0xFF,\n\t0x98, 0x98, 0x98, 0xFF, 0xB4, 0xB4, 0xB4, 0xFF, 0xB4, 0xB4, 0xB4, 0xFF, 0xB5, 0xB5, 0xB5, 0xFF,\n\t0x99, 0x99, 0x99, 0xFF, 0xB5, 0xB5, 0xB5, 0xFF, 0xB5, 0xB5, 0xB5, 0xFF, 0xB5, 0xB5, 0xB5, 0xFF,\n\t0x99, 0x99, 0x99, 0xFF, 0xE4, 0xE4, 0xE4, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x5C, 0x5C, 0x5C, 0xFF,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x8E, 0x8E, 0x8E, 0xFF, 0xFD, 0xFD, 0xFD, 0xFF, 0xDF, 0xDF, 0xDF, 0xFF, 0x94, 0x94, 0x94, 0xFF,\n\t0xB1, 0xB1, 0xB1, 0xFF, 0xB1, 0xB1, 0xB1, 0xFF, 0xB2, 0xB2, 0xB2, 0xFF, 0xB2, 0xB2, 0xB2, 0xFF,\n\t0x97, 0x97, 0x97, 0xFF, 0xB3, 0xB3, 0xB3, 0xFF, 0xB4, 0xB4, 0xB4, 0xFF, 0xB4, 0xB4, 0xB4, 0xFF,\n\t0x99, 0x99, 0x99, 0xFF, 0xB5, 0xB5, 0xB5, 0xFF, 0xB5, 0xB5, 0xB5, 0xFF, 0xB5, 0xB5, 0xB5, 0xFF,\n\t0x99, 0x99, 0x99, 0xFF, 0xB5, 0xB5, 0xB5, 0xFF, 0xB5, 0xB5, 0xB5, 0xFF, 0xB5, 0xB5, 0xB5, 0xFF,\n\t0x99, 0x99, 0x99, 0xFF, 0xE5, 0xE5, 0xE5, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x5C, 0x5C, 0x5C, 0xFF,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x8B, 0x8B, 0x8B, 0xFF, 0xFD, 0xFD, 0xFD, 0xFF, 0xDF, 0xDF, 0xDF, 0xFF, 0x7A, 0x7A, 0x7A, 0xFF,\n\t0x91, 0x91, 0x91, 0xFF, 0x92, 0x92, 0x92, 0xFF, 0x92, 0x92, 0x92, 0xFF, 0x93, 0x93, 0x93, 0xFF,\n\t0x7D, 0x7D, 0x7D, 0xFF, 0x94, 0x94, 0x94, 0xFF, 0x94, 0x94, 0x94, 0xFF, 0x94, 0x94, 0x94, 0xFF,\n\t0x7D, 0x7D, 0x7D, 0xFF, 0x94, 0x94, 0x94, 0xFF, 0x94, 0x94, 0x94, 0xFF, 0x95, 0x95, 0x95, 0xFF,\n\t0x7E, 0x7E, 0x7E, 0xFF, 0x95, 0x95, 0x95, 0xFF, 0x95, 0x95, 0x95, 0xFF, 0x95, 0x95, 0x95, 0xFF,\n\t0x7E, 0x7E, 0x7E, 0xFF, 0xE6, 0xE6, 0xE6, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x5B, 0x5B, 0x5B, 0xFF,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x89, 0x89, 0x89, 0xFF, 0xFD, 0xFD, 0xFD, 0xFF, 0xE0, 0xE0, 0xE0, 0xFF, 0x94, 0x94, 0x94, 0xFF,\n\t0xB0, 0xB0, 0xB0, 0xFF, 0xB0, 0xB0, 0xB0, 0xFF, 0xB1, 0xB1, 0xB1, 0xFF, 0xB2, 0xB2, 0xB2, 0xFF,\n\t0x97, 0x97, 0x97, 0xFF, 0xE2, 0xE2, 0xE2, 0xFF, 0xE3, 0xE3, 0xE3, 0xFF, 0xE3, 0xE3, 0xE3, 0xFF,\n\t0xC0, 0xC0, 0xC0, 0xFF, 0xE4, 0xE4, 0xE4, 0xFF, 0xE4, 0xE4, 0xE4, 0xFF, 0xE5, 0xE5, 0xE5, 0xFF,\n\t0xC1, 0xC1, 0xC1, 0xFF, 0xE5, 0xE5, 0xE5, 0xFF, 0xE5, 0xE5, 0xE5, 0xFF, 0xE5, 0xE5, 0xE5, 0xFF,\n\t0xC1, 0xC1, 0xC1, 0xFF, 0xE8, 0xE8, 0xE8, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x5A, 0x5A, 0x5A, 0xFF,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x86, 0x86, 0x86, 0xFF, 0xFD, 0xFD, 0xFD, 0xFF, 0xE1, 0xE1, 0xE1, 0xFF, 0x7B, 0x7B, 0x7B, 0xFF,\n\t0x92, 0x92, 0x92, 0xFF, 0x93, 0x93, 0x93, 0xFF, 0x94, 0x94, 0x94, 0xFF, 0x94, 0x94, 0x94, 0xFF,\n\t0x7D, 0x7D, 0x7D, 0xFF, 0xBD, 0xBD, 0xBD, 0xFF, 0xBD, 0xBD, 0xBD, 0xFF, 0xBD, 0xBD, 0xBD, 0xFF,\n\t0xA0, 0xA0, 0xA0, 0xFF, 0xBE, 0xBE, 0xBE, 0xFF, 0xBE, 0xBE, 0xBE, 0xFF, 0xBF, 0xBF, 0xBF, 0xFF,\n\t0xA1, 0xA1, 0xA1, 0xFF, 0xBF, 0xBF, 0xBF, 0xFF, 0xBF, 0xBF, 0xBF, 0xFF, 0xBF, 0xBF, 0xBF, 0xFF,\n\t0xA1, 0xA1, 0xA1, 0xFF, 0xE9, 0xE9, 0xE9, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x59, 0x59, 0x59, 0xFF,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x83, 0x83, 0x83, 0xFF, 0xFD, 0xFD, 0xFD, 0xFF, 0xE1, 0xE1, 0xE1, 0xFF, 0x94, 0x94, 0x94, 0xFF,\n\t0xB1, 0xB1, 0xB1, 0xFF, 0xB2, 0xB2, 0xB2, 0xFF, 0xB3, 0xB3, 0xB3, 0xFF, 0xB3, 0xB3, 0xB3, 0xFF,\n\t0x97, 0x97, 0x97, 0xFF, 0xE4, 0xE4, 0xE4, 0xFF, 0xE5, 0xE5, 0xE5, 0xFF, 0xE5, 0xE5, 0xE5, 0xFF,\n\t0xC2, 0xC2, 0xC2, 0xFF, 0xE6, 0xE6, 0xE6, 0xFF, 0xE6, 0xE6, 0xE6, 0xFF, 0xE7, 0xE7, 0xE7, 0xFF,\n\t0xC3, 0xC3, 0xC3, 0xFF, 0xE7, 0xE7, 0xE7, 0xFF, 0xE7, 0xE7, 0xE7, 0xFF, 0xE7, 0xE7, 0xE7, 0xFF,\n\t0xC3, 0xC3, 0xC3, 0xFF, 0xEA, 0xEA, 0xEA, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x58, 0x58, 0x58, 0xFF,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x80, 0x80, 0x80, 0xFF, 0xFC, 0xFC, 0xFC, 0xFF, 0xE2, 0xE2, 0xE2, 0xFF, 0x7C, 0x7C, 0x7C, 0xFF,\n\t0x94, 0x94, 0x94, 0xFF, 0x94, 0x94, 0x94, 0xFF, 0x94, 0x94, 0x94, 0xFF, 0x94, 0x94, 0x94, 0xFF,\n\t0x7E, 0x7E, 0x7E, 0xFF, 0xBE, 0xBE, 0xBE, 0xFF, 0xBE, 0xBE, 0xBE, 0xFF, 0xBF, 0xBF, 0xBF, 0xFF,\n\t0xA2, 0xA2, 0xA2, 0xFF, 0xC0, 0xC0, 0xC0, 0xFF, 0xC0, 0xC0, 0xC0, 0xFF, 0xC1, 0xC1, 0xC1, 0xFF,\n\t0xA3, 0xA3, 0xA3, 0xFF, 0xC1, 0xC1, 0xC1, 0xFF, 0xC1, 0xC1, 0xC1, 0xFF, 0xC1, 0xC1, 0xC1, 0xFF,\n\t0xA3, 0xA3, 0xA3, 0xFF, 0xEB, 0xEB, 0xEB, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x57, 0x57, 0x57, 0xFF,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x7D, 0x7D, 0x7D, 0xFF, 0xFC, 0xFC, 0xFC, 0xFF, 0xE3, 0xE3, 0xE3, 0xFF, 0x96, 0x96, 0x96, 0xFF,\n\t0xB3, 0xB3, 0xB3, 0xFF, 0xB3, 0xB3, 0xB3, 0xFF, 0xB3, 0xB3, 0xB3, 0xFF, 0xB4, 0xB4, 0xB4, 0xFF,\n\t0x99, 0x99, 0x99, 0xFF, 0xE6, 0xE6, 0xE6, 0xFF, 0xE6, 0xE6, 0xE6, 0xFF, 0xE7, 0xE7, 0xE7, 0xFF,\n\t0xC4, 0xC4, 0xC4, 0xFF, 0xE8, 0xE8, 0xE8, 0xFF, 0xE8, 0xE8, 0xE8, 0xFF, 0xE9, 0xE9, 0xE9, 0xFF,\n\t0xC4, 0xC4, 0xC4, 0xFF, 0xE9, 0xE9, 0xE9, 0xFF, 0xE9, 0xE9, 0xE9, 0xFF, 0xE9, 0xE9, 0xE9, 0xFF,\n\t0xC4, 0xC4, 0xC4, 0xFF, 0xEC, 0xEC, 0xEC, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x55, 0x55, 0x55, 0xFF,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x7A, 0x7A, 0x7A, 0xFF, 0xFC, 0xFC, 0xFC, 0xFF, 0xE3, 0xE3, 0xE3, 0xFF, 0x7D, 0x7D, 0x7D, 0xFF,\n\t0x94, 0x94, 0x94, 0xFF, 0x94, 0x94, 0x94, 0xFF, 0x95, 0x95, 0x95, 0xFF, 0x96, 0x96, 0x96, 0xFF,\n\t0x7F, 0x7F, 0x7F, 0xFF, 0xBF, 0xBF, 0xBF, 0xFF, 0xC0, 0xC0, 0xC0, 0xFF, 0xC1, 0xC1, 0xC1, 0xFF,\n\t0xA3, 0xA3, 0xA3, 0xFF, 0xC1, 0xC1, 0xC1, 0xFF, 0xC1, 0xC1, 0xC1, 0xFF, 0xC2, 0xC2, 0xC2, 0xFF,\n\t0xA4, 0xA4, 0xA4, 0xFF, 0xC2, 0xC2, 0xC2, 0xFF, 0xC2, 0xC2, 0xC2, 0xFF, 0xC2, 0xC2, 0xC2, 0xFF,\n\t0xA4, 0xA4, 0xA4, 0xFF, 0xED, 0xED, 0xED, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x54, 0x54, 0x54, 0xFF,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x77, 0x77, 0x77, 0xFF, 0xFB, 0xFB, 0xFB, 0xFF, 0xE4, 0xE4, 0xE4, 0xFF, 0x97, 0x97, 0x97, 0xFF,\n\t0xB3, 0xB3, 0xB3, 0xFF, 0xB4, 0xB4, 0xB4, 0xFF, 0xB5, 0xB5, 0xB5, 0xFF, 0xB6, 0xB6, 0xB6, 0xFF,\n\t0x99, 0x99, 0x99, 0xFF, 0xE7, 0xE7, 0xE7, 0xFF, 0xE8, 0xE8, 0xE8, 0xFF, 0xE9, 0xE9, 0xE9, 0xFF,\n\t0xC4, 0xC4, 0xC4, 0xFF, 0xEA, 0xEA, 0xEA, 0xFF, 0xEA, 0xEA, 0xEA, 0xFF, 0xEB, 0xEB, 0xEB, 0xFF,\n\t0xC6, 0xC6, 0xC6, 0xFF, 0xEB, 0xEB, 0xEB, 0xFF, 0xEB, 0xEB, 0xEB, 0xFF, 0xEB, 0xEB, 0xEB, 0xFF,\n\t0xC6, 0xC6, 0xC6, 0xFF, 0xEE, 0xEE, 0xEE, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0x52, 0x52, 0x52, 0xFF,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x73, 0x73, 0x73, 0xFF, 0xFB, 0xFB, 0xFB, 0xFF, 0xE4, 0xE4, 0xE4, 0xFF, 0x7D, 0x7D, 0x7D, 0xFF,\n\t0x94, 0x94, 0x94, 0xFF, 0x95, 0x95, 0x95, 0xFF, 0x96, 0x96, 0x96, 0xFF, 0x97, 0x97, 0x97, 0xFF,\n\t0x7F, 0x7F, 0x7F, 0xFF, 0xC1, 0xC1, 0xC1, 0xFF, 0xC1, 0xC1, 0xC1, 0xFF, 0xC2, 0xC2, 0xC2, 0xFF,\n\t0xA3, 0xA2, 0xA2, 0xFF, 0xAB, 0x91, 0x91, 0xFF, 0x94, 0x60, 0x60, 0xFF, 0x84, 0x3D, 0x3D, 0xFF,\n\t0x78, 0x25, 0x25, 0xFF, 0x80, 0x27, 0x27, 0xFF, 0x7E, 0x2B, 0x2B, 0xFF, 0x84, 0x3D, 0x3D, 0xFF,\n\t0x86, 0x52, 0x52, 0xFF, 0xCC, 0xB2, 0xB2, 0xFF, 0xF6, 0xF5, 0xF5, 0xFF, 0x50, 0x50, 0x50, 0xFF,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x70, 0x70, 0x70, 0xFF, 0xFB, 0xFB, 0xFB, 0xFF, 0xE5, 0xE5, 0xE5, 0xFF, 0x97, 0x97, 0x97, 0xFF,\n\t0xB4, 0xB4, 0xB4, 0xFF, 0xB5, 0xB5, 0xB5, 0xFF, 0xB6, 0xB6, 0xB6, 0xFF, 0xB6, 0xB6, 0xB6, 0xFF,\n\t0x9A, 0x9A, 0x9A, 0xFF, 0xE9, 0xE9, 0xE9, 0xFF, 0xE6, 0xE4, 0xE4, 0xFF, 0xB1, 0x85, 0x85, 0xFF,\n\t0x7A, 0x1B, 0x1B, 0xFF, 0xA6, 0x2F, 0x2F, 0xFF, 0xD3, 0x50, 0x50, 0xFF, 0xF2, 0x67, 0x67, 0xFF,\n\t0xFF, 0x70, 0x70, 0xFF, 0xFE, 0x6E, 0x6E, 0xFF, 0xFC, 0x69, 0x69, 0xFF, 0xED, 0x5B, 0x5B, 0xFF,\n\t0xCD, 0x43, 0x43, 0xFF, 0xA2, 0x25, 0x25, 0xFF, 0x7E, 0x1D, 0x1D, 0xFF, 0x58, 0x2C, 0x2C, 0xFF,\n\t0x02, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x6D, 0x6D, 0x6D, 0xFF, 0xFB, 0xFB, 0xFB, 0xFF, 0xE5, 0xE5, 0xE5, 0xFF, 0x7D, 0x7D, 0x7D, 0xFF,\n\t0x95, 0x95, 0x95, 0xFF, 0x96, 0x96, 0x96, 0xFF, 0x97, 0x97, 0x97, 0xFF, 0x97, 0x97, 0x97, 0xFF,\n\t0x80, 0x80, 0x80, 0xFF, 0xB7, 0xAB, 0xAB, 0xFF, 0x7B, 0x28, 0x28, 0xFF, 0xAC, 0x34, 0x34, 0xFF,\n\t0xF5, 0x6A, 0x6A, 0xFF, 0xFF, 0x70, 0x70, 0xFF, 0xFF, 0x70, 0x70, 0xFF, 0xFE, 0x6F, 0x6F, 0xFF,\n\t0xFC, 0x6A, 0x6A, 0xFF, 0xFA, 0x65, 0x65, 0xFF, 0xF8, 0x60, 0x60, 0xFF, 0xF5, 0x5C, 0x5C, 0xFF,\n\t0xF3, 0x57, 0x57, 0xFF, 0xF1, 0x52, 0x52, 0xFF, 0xE6, 0x48, 0x48, 0xFF, 0xA1, 0x1F, 0x1F, 0xFF,\n\t0x53, 0x00, 0x00, 0xCB, 0x0C, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x6A, 0x6A, 0x6A, 0xFF, 0xFA, 0xFA, 0xFA, 0xFF, 0xE6, 0xE6, 0xE6, 0xFF, 0x98, 0x98, 0x98, 0xFF,\n\t0xB5, 0xB5, 0xB5, 0xFF, 0xB6, 0xB6, 0xB6, 0xFF, 0xB6, 0xB6, 0xB6, 0xFF, 0xB7, 0xB7, 0xB7, 0xFF,\n\t0x98, 0x92, 0x92, 0xFF, 0x7E, 0x26, 0x26, 0xFF, 0xCB, 0x50, 0x50, 0xFF, 0xFF, 0x74, 0x74, 0xFF,\n\t0xFF, 0x70, 0x70, 0xFF, 0xFF, 0x70, 0x70, 0xFF, 0xFD, 0x6B, 0x6B, 0xFF, 0xFA, 0x67, 0x67, 0xFF,\n\t0xF8, 0x62, 0x62, 0xFF, 0xF6, 0x5D, 0x5D, 0xFF, 0xF4, 0x58, 0x58, 0xFF, 0xF1, 0x53, 0x53, 0xFF,\n\t0xEF, 0x4E, 0x4E, 0xFF, 0xED, 0x49, 0x49, 0xFF, 0xEB, 0x44, 0x44, 0xFF, 0xE9, 0x3F, 0x3F, 0xFF,\n\t0xB6, 0x24, 0x24, 0xFF, 0x57, 0x00, 0x00, 0xD7, 0x06, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x66, 0x66, 0x66, 0xFF, 0xFA, 0xFA, 0xFA, 0xFF, 0xE6, 0xE6, 0xE6, 0xFF, 0x7E, 0x7E, 0x7E, 0xFF,\n\t0x96, 0x96, 0x96, 0xFF, 0x97, 0x97, 0x97, 0xFF, 0x97, 0x97, 0x97, 0xFF, 0x98, 0x98, 0x98, 0xFF,\n\t0x73, 0x3D, 0x3D, 0xFF, 0xA6, 0x37, 0x37, 0xFF, 0xFF, 0x79, 0x79, 0xFF, 0xFF, 0x72, 0x72, 0xFF,\n\t0xFD, 0x6D, 0x6D, 0xFF, 0xFB, 0x68, 0x68, 0xFF, 0xF9, 0x63, 0x63, 0xFF, 0xF6, 0x5E, 0x5E, 0xFF,\n\t0xF4, 0x59, 0x59, 0xFF, 0xF2, 0x54, 0x54, 0xFF, 0xF0, 0x4F, 0x4F, 0xFF, 0xEE, 0x4A, 0x4A, 0xFF,\n\t0xEB, 0x45, 0x45, 0xFF, 0xE9, 0x41, 0x41, 0xFF, 0xE7, 0x3C, 0x3C, 0xFF, 0xE5, 0x37, 0x37, 0xFF,\n\t0xE2, 0x35, 0x35, 0xFF, 0x91, 0x13, 0x13, 0xFF, 0x36, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x63, 0x63, 0x63, 0xFF, 0xFA, 0xFA, 0xFA, 0xFF, 0xE6, 0xE6, 0xE6, 0xFF, 0x98, 0x98, 0x98, 0xFF,\n\t0xB5, 0xB5, 0xB5, 0xFF, 0xB6, 0xB6, 0xB6, 0xFF, 0xB7, 0xB7, 0xB7, 0xFF, 0xB8, 0xB8, 0xB8, 0xFF,\n\t0x6E, 0x15, 0x15, 0xFF, 0xDF, 0x6D, 0x6D, 0xFF, 0xFE, 0x75, 0x75, 0xFF, 0xFB, 0x69, 0x69, 0xFF,\n\t0xF9, 0x64, 0x64, 0xFF, 0xF7, 0x5F, 0x5F, 0xFF, 0xF5, 0x5A, 0x5A, 0xFF, 0xF3, 0x55, 0x55, 0xFF,\n\t0xF0, 0x50, 0x50, 0xFF, 0xEE, 0x4C, 0x4C, 0xFF, 0xEC, 0x47, 0x47, 0xFF, 0xEA, 0x42, 0x42, 0xFF,\n\t0xE7, 0x3D, 0x3D, 0xFF, 0xE5, 0x38, 0x38, 0xFF, 0xE3, 0x33, 0x33, 0xFF, 0xE1, 0x2E, 0x2E, 0xFF,\n\t0xDF, 0x2D, 0x2D, 0xFF, 0xC2, 0x28, 0x28, 0xFF, 0x59, 0x00, 0x00, 0xDE, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x60, 0x60, 0x60, 0xFF, 0xF9, 0xF9, 0xF9, 0xFF, 0xE6, 0xE6, 0xE6, 0xFF, 0x7E, 0x7E, 0x7E, 0xFF,\n\t0x97, 0x97, 0x97, 0xFF, 0x97, 0x97, 0x97, 0xFF, 0x98, 0x98, 0x98, 0xFF, 0x98, 0x98, 0x98, 0xFF,\n\t0x67, 0x01, 0x01, 0xFF, 0xED, 0x7B, 0x7B, 0xFF, 0xFA, 0x6A, 0x6A, 0xFF, 0xF8, 0x60, 0x60, 0xFF,\n\t0xF5, 0x5B, 0x5B, 0xFF, 0xF3, 0x56, 0x56, 0xFF, 0xF1, 0x52, 0x52, 0xFF, 0xEF, 0x4D, 0x4D, 0xFF,\n\t0xEC, 0x48, 0x48, 0xFF, 0xCD, 0x84, 0x78, 0xFF, 0x77, 0x7C, 0xAE, 0xFF, 0x77, 0x7B, 0xAD, 0xFF,\n\t0x72, 0x75, 0xA7, 0xFF, 0x6B, 0x6F, 0xA1, 0xFF, 0x67, 0x69, 0x9B, 0xFF, 0x60, 0x63, 0x96, 0xFF,\n\t0x5E, 0x60, 0x91, 0xFF, 0x7E, 0x5E, 0x82, 0xFF, 0x66, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x02,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x5D, 0x5D, 0x5D, 0xFF, 0xF9, 0xF9, 0xF9, 0xFF, 0xE6, 0xE6, 0xE6, 0xFF, 0x99, 0x99, 0x99, 0xFF,\n\t0xB6, 0xB6, 0xB6, 0xFF, 0xB6, 0xB6, 0xB6, 0xFF, 0xB7, 0xB7, 0xB7, 0xFF, 0xB8, 0xB8, 0xB8, 0xFF,\n\t0x67, 0x00, 0x00, 0xFF, 0xD8, 0x62, 0x62, 0xFF, 0xF7, 0x6D, 0x6D, 0xFF, 0xF4, 0x58, 0x58, 0xFF,\n\t0xF1, 0x53, 0x53, 0xFF, 0xEF, 0x4E, 0x4E, 0xFF, 0xED, 0x49, 0x49, 0xFF, 0xEB, 0x44, 0x44, 0xFF,\n\t0xE8, 0x43, 0x41, 0xFF, 0xC5, 0xE1, 0x8C, 0xFF, 0x8A, 0x93, 0x9F, 0xFF, 0x58, 0x89, 0xC7, 0xFF,\n\t0x51, 0x82, 0xC1, 0xFF, 0x4B, 0x7C, 0xBA, 0xFF, 0x44, 0x75, 0xB4, 0xFF, 0x3D, 0x6E, 0xAE, 0xFF,\n\t0x45, 0x72, 0xAD, 0xFF, 0x6A, 0x60, 0x87, 0xFF, 0x67, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x0E,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x59, 0x59, 0x59, 0xFF, 0xF9, 0xF9, 0xF9, 0xFF, 0xE7, 0xE7, 0xE7, 0xFF, 0x7E, 0x7E, 0x7E, 0xFF,\n\t0x97, 0x97, 0x97, 0xFF, 0x97, 0x97, 0x97, 0xFF, 0x98, 0x98, 0x98, 0xFF, 0x98, 0x98, 0x98, 0xFF,\n\t0x69, 0x0D, 0x0D, 0xFF, 0xA3, 0x24, 0x24, 0xFF, 0xEF, 0x7B, 0x7B, 0xFF, 0xF1, 0x5A, 0x5A, 0xFF,\n\t0xEE, 0x4A, 0x4A, 0xFF, 0xEB, 0x45, 0x45, 0xFF, 0xE9, 0x40, 0x40, 0xFF, 0xE7, 0x3C, 0x3C, 0xFF,\n\t0xD8, 0x6B, 0x4D, 0xFF, 0xAD, 0xE7, 0x73, 0xFF, 0xAB, 0xC3, 0x5F, 0xFF, 0x55, 0x85, 0xC0, 0xFF,\n\t0x4D, 0x7E, 0xBD, 0xFF, 0x47, 0x78, 0xB7, 0xFF, 0x40, 0x71, 0xB0, 0xFF, 0x43, 0x72, 0xAE, 0xFF,\n\t0x6B, 0x7A, 0xA6, 0xFF, 0x54, 0x42, 0x67, 0xFF, 0x5C, 0x00, 0x00, 0xE8, 0x00, 0x00, 0x00, 0x16,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x56, 0x56, 0x56, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0xE7, 0xE7, 0xE7, 0xFF, 0x99, 0x99, 0x99, 0xFF,\n\t0xB6, 0xB6, 0xB6, 0xFF, 0xB6, 0xB6, 0xB6, 0xFF, 0xB7, 0xB7, 0xB7, 0xFF, 0xB8, 0xB8, 0xB8, 0xFF,\n\t0x77, 0x39, 0x39, 0xFF, 0x7A, 0x09, 0x09, 0xFF, 0xBB, 0x32, 0x32, 0xFF, 0xE8, 0x71, 0x71, 0xFF,\n\t0xED, 0x61, 0x61, 0xFF, 0xE8, 0x42, 0x42, 0xFF, 0xE5, 0x38, 0x38, 0xFF, 0xE3, 0x33, 0x33, 0xFF,\n\t0xBA, 0x9F, 0x4E, 0xFF, 0x99, 0xE0, 0x53, 0xFF, 0x8F, 0xDC, 0x43, 0xFF, 0x84, 0x8A, 0x66, 0xFF,\n\t0x49, 0x7A, 0xB9, 0xFF, 0x47, 0x77, 0xB4, 0xFF, 0x58, 0x82, 0xB9, 0xFF, 0x79, 0x7F, 0xA6, 0xFF,\n\t0x45, 0x5E, 0x90, 0xFF, 0x65, 0x17, 0x23, 0xFF, 0x3E, 0x00, 0x00, 0xAC, 0x00, 0x00, 0x00, 0x13,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x53, 0x53, 0x53, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0xE6, 0xE6, 0xE6, 0xFF, 0x7E, 0x7E, 0x7E, 0xFF,\n\t0x97, 0x97, 0x97, 0xFF, 0x97, 0x97, 0x97, 0xFF, 0x98, 0x98, 0x98, 0xFF, 0x98, 0x98, 0x98, 0xFF,\n\t0x76, 0x6B, 0x6B, 0xFF, 0x6D, 0x0E, 0x0E, 0xFF, 0x88, 0x11, 0x11, 0xFF, 0xB0, 0x23, 0x23, 0xFF,\n\t0xCA, 0x45, 0x45, 0xFF, 0xE2, 0x66, 0x66, 0xFF, 0xE8, 0x5D, 0x5D, 0xFF, 0xE2, 0x4F, 0x4B, 0xFF,\n\t0xA1, 0xD6, 0x56, 0xFF, 0x91, 0xDC, 0x47, 0xFF, 0x8B, 0xD9, 0x3C, 0xFF, 0x90, 0xC9, 0x3A, 0xFF,\n\t0x74, 0x94, 0xBB, 0xFF, 0x84, 0x8E, 0xB4, 0xFF, 0x70, 0x68, 0x8E, 0xFF, 0x3B, 0x5E, 0x92, 0xFF,\n\t0x60, 0x29, 0x42, 0xFF, 0x5F, 0x00, 0x00, 0xEF, 0x0B, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x08,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x4F, 0x4F, 0x4F, 0xFF, 0xF8, 0xF8, 0xF8, 0xFF, 0xE6, 0xE6, 0xE6, 0xFF, 0xE5, 0xE5, 0xE5, 0xFF,\n\t0xE6, 0xE6, 0xE6, 0xFF, 0xE7, 0xE7, 0xE7, 0xFF, 0xE8, 0xE8, 0xE8, 0xFF, 0xE9, 0xE9, 0xE9, 0xFF,\n\t0xE6, 0xE6, 0xE6, 0xFF, 0xBD, 0xA9, 0xA9, 0xFF, 0x71, 0x15, 0x15, 0xFF, 0x7B, 0x0C, 0x0C, 0xFF,\n\t0xA2, 0x1D, 0x1D, 0xFF, 0xAD, 0x1B, 0x1B, 0xFF, 0xB6, 0x27, 0x27, 0xFF, 0xB2, 0x4F, 0x33, 0xFF,\n\t0x93, 0xA6, 0x40, 0xFF, 0x96, 0xA8, 0x40, 0xFF, 0x91, 0xA7, 0x3C, 0xFF, 0x86, 0x9F, 0x33, 0xFF,\n\t0x6E, 0x67, 0x5B, 0xFF, 0x37, 0x5C, 0x93, 0xFF, 0x54, 0x4D, 0x75, 0xFF, 0x6B, 0x18, 0x23, 0xFF,\n\t0x5C, 0x00, 0x00, 0xEB, 0x13, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D,\n\t0x4C, 0x4C, 0x4C, 0xF1, 0xF7, 0xF7, 0xF7, 0xFF, 0xF6, 0xF6, 0xF6, 0xFF, 0xF6, 0xF6, 0xF6, 0xFF,\n\t0xF6, 0xF6, 0xF6, 0xFF, 0xF7, 0xF7, 0xF7, 0xFF, 0xF7, 0xF7, 0xF7, 0xFF, 0xF7, 0xF7, 0xF7, 0xFF,\n\t0xF7, 0xF7, 0xF7, 0xFF, 0xF0, 0xF0, 0xF0, 0xFF, 0xD1, 0xC9, 0xC9, 0xFF, 0x90, 0x53, 0x53, 0xFF,\n\t0x6A, 0x05, 0x05, 0xFF, 0x77, 0x0A, 0x0A, 0xFF, 0x8E, 0x14, 0x14, 0xFF, 0x91, 0x33, 0x1C, 0xFF,\n\t0x72, 0x87, 0x23, 0xFF, 0x70, 0x90, 0x24, 0xFF, 0x6E, 0x8F, 0x23, 0xFF, 0x73, 0x7C, 0x1F, 0xFF,\n\t0x70, 0x3C, 0x31, 0xFF, 0x6C, 0x14, 0x1B, 0xFF, 0x6A, 0x05, 0x05, 0xFF, 0x53, 0x16, 0x16, 0xFB,\n\t0x08, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x22,\n\t0x19, 0x19, 0x19, 0x78, 0x53, 0x53, 0x53, 0xF6, 0x56, 0x56, 0x56, 0xFF, 0x56, 0x56, 0x56, 0xFF,\n\t0x56, 0x56, 0x56, 0xFF, 0x56, 0x56, 0x56, 0xFF, 0x56, 0x56, 0x56, 0xFF, 0x56, 0x56, 0x56, 0xFF,\n\t0x56, 0x56, 0x56, 0xFF, 0x56, 0x56, 0x56, 0xFF, 0x55, 0x55, 0x55, 0xFF, 0x4F, 0x4F, 0x4F, 0xFF,\n\t0x4B, 0x43, 0x43, 0xFF, 0x52, 0x28, 0x28, 0xFF, 0x5A, 0x16, 0x16, 0xFF, 0x61, 0x0A, 0x0A, 0xFF,\n\t0x65, 0x04, 0x04, 0xFF, 0x67, 0x00, 0x00, 0xFF, 0x65, 0x04, 0x04, 0xFF, 0x60, 0x09, 0x09, 0xFF,\n\t0x59, 0x15, 0x15, 0xFF, 0x50, 0x26, 0x26, 0xFF, 0x45, 0x3C, 0x3C, 0xF9, 0x15, 0x15, 0x15, 0x93,\n\t0x00, 0x00, 0x00, 0x3A, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1D,\n\t0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x4A,\n\t0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x4A,\n\t0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x4A,\n\t0x00, 0x00, 0x00, 0x4C, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x5E, 0x00, 0x00, 0x00, 0x65,\n\t0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x6B, 0x00, 0x00, 0x00, 0x6B, 0x00, 0x00, 0x00, 0x69,\n\t0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x4B, 0x00, 0x00, 0x00, 0x3B,\n\t0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16,\n\t0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x16,\n\t0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x16,\n\t0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x16,\n\t0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x17,\n\t0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x08,\n\t0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n};\n\nstatic const struct {\n\tconst char *name;\n\tvoid *data;\n\tint width;\n\tint height;\n\tint stride;\n} files[] = {\n\t{ \"andlabs_16x16test_24june2016.png\", dat0, 16, 16, 64 },\n\t{ \"andlabs_32x32test_24june2016.png\", dat1, 32, 32, 128 },\n\t{ \"tango-icon-theme-0.8.90_16x16_x-office-spreadsheet.png\", dat2, 16, 16, 64 },\n\t{ \"tango-icon-theme-0.8.90_32x32_x-office-spreadsheet.png\", dat3, 32, 32, 128 },\n};\n\nvoid appendImageNamed(uiImage *img, const char *name)\n{\n\tint i;\n\n\ti = 0;\n\tfor (;;) {\n\t\tif (strcmp(name, files[i].name) == 0) {\n\t\t\tuiImageAppend(img, files[i].data, files[i].width, files[i].height, files[i].stride);\n\t\t\treturn;\n\t\t}\n\t\ti++;\n\t}\n}\n\n"
  },
  {
    "path": "test/main.c",
    "content": "// 22 april 2015\n#include \"test.h\"\n\n// TODOs\n// - blank page affects menus negatively on Windows\n\nvoid die(const char *fmt, ...)\n{\n\tva_list ap;\n\n\tva_start(ap, fmt);\n\tfprintf(stderr, \"[test program] \");\n\tvfprintf(stderr, fmt, ap);\n\tfprintf(stderr, \"\\n\");\n\tva_end(ap);\n\tabort();\n}\n\nint onClosing(uiWindow *w, void *data)\n{\n\tprintf(\"in onClosing()\\n\");\n\tuiQuit();\n\treturn 1;\n}\n\nint onShouldQuit(void *data)\n{\n\tprintf(\"in onShouldQuit()\\n\");\n\tif (uiMenuItemChecked(shouldQuitItem)) {\n\t\tuiControlDestroy(uiControl(data));\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nuiBox *mainBox;\nuiTab *mainTab;\n\nuiBox *(*newhbox)(void);\nuiBox *(*newvbox)(void);\n\nint main(int argc, char *argv[])\n{\n\tuiInitOptions o;\n\tint i;\n\tconst char *err;\n\tuiWindow *w;\n\tuiBox *page2, *page3, *page4, *page5;\n\tuiBox *page6, *page7, *page8, *page9, *page10;\n\tuiBox *page11, *page12, *page13;\n\tuiTab *page14;\n\tuiBox *page15;\n\tuiBox *page16;\n\tuiTab *outerTab;\n\tuiTab *innerTab;\n\tint nomenus = 0;\n\tint startspaced = 0;\n\tint steps = 0;\n\n\tnewhbox = uiNewHorizontalBox;\n\tnewvbox = uiNewVerticalBox;\n\n\tmemset(&o, 0, sizeof (uiInitOptions));\n\tfor (i = 1; i < argc; i++)\n\t\tif (strcmp(argv[i], \"nomenus\") == 0)\n\t\t\tnomenus = 1;\n\t\telse if (strcmp(argv[i], \"startspaced\") == 0)\n\t\t\tstartspaced = 1;\n\t\telse if (strcmp(argv[i], \"swaphv\") == 0) {\n\t\t\tnewhbox = uiNewVerticalBox;\n\t\t\tnewvbox = uiNewHorizontalBox;\n\t\t} else if (strcmp(argv[i], \"steps\") == 0)\n\t\t\tsteps = 1;\n\t\telse {\n\t\t\tfprintf(stderr, \"%s: unrecognized option %s\\n\", argv[0], argv[i]);\n\t\t\treturn 1;\n\t\t}\n\n\terr = uiInit(&o);\n\tif (err != NULL) {\n\t\tfprintf(stderr, \"error initializing ui: %s\\n\", err);\n\t\tuiFreeInitError(err);\n\t\treturn 1;\n\t}\n\n\tif (!nomenus)\n\t\tinitMenus();\n\n\tw = newWindow(\"Main Window\", 320, 240, 1);\n\tuiWindowOnClosing(w, onClosing, NULL);\n\tprintf(\"main window %p\\n\", (void *) w);\n\n\tuiOnShouldQuit(onShouldQuit, w);\n\n\tmainBox = newHorizontalBox();\n\tuiWindowSetChild(w, uiControl(mainBox));\n\n\touterTab = newTab();\n\tuiBoxAppend(mainBox, uiControl(outerTab), 1);\n\n\tmainTab = newTab();\n\tuiTabAppend(outerTab, \"Pages 1-5\", uiControl(mainTab));\n\n\t// page 1 uses page 2's uiGroup\n\tpage2 = makePage2();\n\n\tmakePage1(w);\n\tuiTabAppend(mainTab, \"Page 1\", uiControl(page1));\n\n\tuiTabAppend(mainTab, \"Page 2\", uiControl(page2));\n\n\tuiTabAppend(mainTab, \"Empty Page\", uiControl(uiNewHorizontalBox()));\n\n\tpage3 = makePage3();\n\tuiTabAppend(mainTab, \"Page 3\", uiControl(page3));\n\n\tpage4 = makePage4();\n\tuiTabAppend(mainTab, \"Page 4\", uiControl(page4));\n\n\tpage5 = makePage5(w);\n\tuiTabAppend(mainTab, \"Page 5\", uiControl(page5));\n\n\tinnerTab = newTab();\n\tuiTabAppend(outerTab, \"Pages 6-10\", uiControl(innerTab));\n\n\tpage6 = makePage6();\n\tuiTabAppend(innerTab, \"Page 6\", uiControl(page6));\n\n\tpage7 = makePage7();\n\tuiTabAppend(innerTab, \"Page 7\", uiControl(page7));\n\n/*\tpage8 = makePage8();\n\tuiTabAppend(innerTab, \"Page 8\", uiControl(page8));\n\n\tpage9 = makePage9();\n\tuiTabAppend(innerTab, \"Page 9\", uiControl(page9));\n\n\tpage10 = makePage10();\n\tuiTabAppend(innerTab, \"Page 10\", uiControl(page10));\n*/\n\tinnerTab = newTab();\n\tuiTabAppend(outerTab, \"Pages 11-15\", uiControl(innerTab));\n\n//\tpage11 = makePage11();\n//\tuiTabAppend(innerTab, \"Page 11\", uiControl(page11));\n\n\tpage12 = makePage12();\n\tuiTabAppend(innerTab, \"Page 12\", uiControl(page12));\n\n\tpage13 = makePage13();\n\tuiTabAppend(innerTab, \"Page 13\", uiControl(page13));\n\n\tpage14 = makePage14();\n\tuiTabAppend(innerTab, \"Page 14\", uiControl(page14));\n\n\tpage15 = makePage15(w);\n\tuiTabAppend(innerTab, \"Page 15\", uiControl(page15));\n\n\tinnerTab = newTab();\n\tuiTabAppend(outerTab, \"Pages 16-?\", uiControl(innerTab));\n\n\tpage16 = makePage16();\n\tuiTabAppend(innerTab, \"Page 16\", uiControl(page16));\n\n\tif (startspaced)\n\t\tsetSpaced(1);\n\n\tuiControlShow(uiControl(w));\n\tif (!steps)\n\t\tuiMain();\n\telse {\n\t\tuiMainSteps();\n\t\twhile (uiMainStep(1))\n\t\t\t;\n\t}\n\tprintf(\"after uiMain()\\n\");\n\tfreePage16();\n\tuiUninit();\n\tprintf(\"after uiUninit()\\n\");\n\treturn 0;\n}\n"
  },
  {
    "path": "test/menus.c",
    "content": "// 23 april 2015\n#include \"test.h\"\n\nuiMenu *fileMenu;\nuiMenuItem *newItem;\nuiMenuItem *openItem;\nuiMenuItem *shouldQuitItem;\nuiMenuItem *quitItem;\nuiMenu *editMenu;\nuiMenuItem *undoItem;\nuiMenuItem *checkItem;\nuiMenuItem *accelItem;\nuiMenuItem *prefsItem;\nuiMenu *testMenu;\nuiMenuItem *enabledItem;\nuiMenuItem *enableThisItem;\nuiMenuItem *forceCheckedItem;\nuiMenuItem *forceUncheckedItem;\nuiMenuItem *whatWindowItem;\nuiMenu *moreTestsMenu;\nuiMenuItem *quitEnabledItem;\nuiMenuItem *prefsEnabledItem;\nuiMenuItem *aboutEnabledItem;\nuiMenuItem *checkEnabledItem;\nuiMenu *multiMenu;\nuiMenu *helpMenu;\nuiMenuItem *helpItem;\nuiMenuItem *aboutItem;\n\nstatic void enableItemTest(uiMenuItem *item, uiWindow *w, void *data)\n{\n\tif (uiMenuItemChecked(item))\n\t\tuiMenuItemEnable(uiMenuItem(data));\n\telse\n\t\tuiMenuItemDisable(uiMenuItem(data));\n}\n\nstatic void forceOn(uiMenuItem *item, uiWindow *w, void *data)\n{\n\tuiMenuItemSetChecked(enabledItem, 1);\n}\n\nstatic void forceOff(uiMenuItem *item, uiWindow *w, void *data)\n{\n\tuiMenuItemSetChecked(enabledItem, 0);\n}\n\nstatic void whatWindow(uiMenuItem *item, uiWindow *w, void *data)\n{\n\tprintf(\"menu item clicked on window %p\\n\", (void *) w);\n}\n\nvoid initMenus(void)\n{\n\tfileMenu = uiNewMenu(\"File\");\n\tnewItem = uiMenuAppendItem(fileMenu, \"New\");\n\topenItem = uiMenuAppendItem(fileMenu, \"Open\");\n\tuiMenuAppendSeparator(fileMenu);\n\tshouldQuitItem = uiMenuAppendCheckItem(fileMenu, \"Should Quit\");\n\tquitItem = uiMenuAppendQuitItem(fileMenu);\n\n\teditMenu = uiNewMenu(\"Edit\");\n\tundoItem = uiMenuAppendItem(editMenu, \"Undo\");\n\tuiMenuItemDisable(undoItem);\n\tuiMenuAppendSeparator(editMenu);\n\tcheckItem = uiMenuAppendCheckItem(editMenu, \"Check Me\\tTest\");\n\taccelItem = uiMenuAppendItem(editMenu, \"A&ccele&&rator T_es__t\");\n\tprefsItem = uiMenuAppendPreferencesItem(editMenu);\n\n\ttestMenu = uiNewMenu(\"Test\");\n\tenabledItem = uiMenuAppendCheckItem(testMenu, \"Enable Below Item\");\n\tuiMenuItemSetChecked(enabledItem, 1);\n\tenableThisItem = uiMenuAppendItem(testMenu, \"This Will Be Enabled\");\n\tuiMenuItemOnClicked(enabledItem, enableItemTest, enableThisItem);\n\tforceCheckedItem = uiMenuAppendItem(testMenu, \"Force Above Checked\");\n\tuiMenuItemOnClicked(forceCheckedItem, forceOn, NULL);\n\tforceUncheckedItem = uiMenuAppendItem(testMenu, \"Force Above Unchecked\");\n\tuiMenuItemOnClicked(forceUncheckedItem, forceOff, NULL);\n\tuiMenuAppendSeparator(testMenu);\n\twhatWindowItem = uiMenuAppendItem(testMenu, \"What Window?\");\n\tuiMenuItemOnClicked(whatWindowItem, whatWindow, NULL);\n\n\tmoreTestsMenu = uiNewMenu(\"More Tests\");\n\tquitEnabledItem = uiMenuAppendCheckItem(moreTestsMenu, \"Quit Item Enabled\");\n\tuiMenuItemSetChecked(quitEnabledItem, 1);\n\tprefsEnabledItem = uiMenuAppendCheckItem(moreTestsMenu, \"Preferences Item Enabled\");\n\tuiMenuItemSetChecked(prefsEnabledItem, 1);\n\taboutEnabledItem = uiMenuAppendCheckItem(moreTestsMenu, \"About Item Enabled\");\n\tuiMenuItemSetChecked(aboutEnabledItem, 1);\n\tuiMenuAppendSeparator(moreTestsMenu);\n\tcheckEnabledItem = uiMenuAppendCheckItem(moreTestsMenu, \"Check Me Item Enabled\");\n\tuiMenuItemSetChecked(checkEnabledItem, 1);\n\n\tmultiMenu = uiNewMenu(\"Multi\");\n\tuiMenuAppendSeparator(multiMenu);\n\tuiMenuAppendSeparator(multiMenu);\n\tuiMenuAppendItem(multiMenu, \"Item && Item && Item\");\n\tuiMenuAppendSeparator(multiMenu);\n\tuiMenuAppendSeparator(multiMenu);\n\tuiMenuAppendItem(multiMenu, \"Item __ Item __ Item\");\n\tuiMenuAppendSeparator(multiMenu);\n\tuiMenuAppendSeparator(multiMenu);\n\n\thelpMenu = uiNewMenu(\"Help\");\n\thelpItem = uiMenuAppendItem(helpMenu, \"Help\");\n\taboutItem = uiMenuAppendAboutItem(helpMenu);\n\n\tuiMenuItemOnClicked(quitEnabledItem, enableItemTest, quitItem);\n\tuiMenuItemOnClicked(prefsEnabledItem, enableItemTest, prefsItem);\n\tuiMenuItemOnClicked(aboutEnabledItem, enableItemTest, aboutItem);\n\tuiMenuItemOnClicked(checkEnabledItem, enableItemTest, checkItem);\n}\n"
  },
  {
    "path": "test/meson.build",
    "content": "# 23 march 2019\n\nlibui_test_sources = [\n\t'drawtests.c',\n\t'images.c',\n\t'main.c',\n\t'menus.c',\n\t'page1.c',\n\t'page2.c',\n\t'page3.c',\n\t'page4.c',\n\t'page5.c',\n\t'page6.c',\n\t'page7.c',\n\t'page7a.c',\n\t'page7b.c',\n\t'page7c.c',\n#\t'page8.c',\n#\t'page9.c',\n#\t'page10.c',\n\t'page11.c',\n\t'page12.c',\n\t'page13.c',\n\t'page14.c',\n\t'page15.c',\n\t'page16.c',\n\t'spaced.c',\n]\n\nif libui_OS == 'windows'\n\tlibui_test_manifest = 'test.manifest'\n\tif libui_mode == 'static'\n\t\tlibui_test_manifest = 'test.static.manifest'\n\tendif\n\tlibui_test_sources += [\n\t\twindows.compile_resources('resources.rc',\n\t\t\targs: libui_manifest_args,\n\t\t\tdepend_files: [libui_test_manifest]),\n\t]\nendif\n\n# TODO meson doesn't let us name this target test, but also doesn't seem to provide a way to override the executable name???? we'll probably need to file a feature request for this\n# TODO once we upgrade to 0.49.0, add pie: true\nexecutable('tester', libui_test_sources,\n\tdependencies: libui_binary_deps,\n\tlink_with: libui_libui,\n\tgui_app: false,\n\tinstall: false)\n"
  },
  {
    "path": "test/page1.c",
    "content": "// 29 april 2015\n#include \"test.h\"\n\nstatic uiEntry *entry;\nstatic uiCheckbox *spaced;\n\n#define TEXT(name, type, getter, setter) \\\n\tstatic void get ## name ## Text(uiButton *b, void *data) \\\n\t{ \\\n\t\tchar *text; \\\n\t\ttext = getter(type(data)); \\\n\t\tuiEntrySetText(entry, text); \\\n\t\tuiFreeText(text); \\\n\t} \\\n\tstatic void set ## name ## Text(uiButton *b, void *data) \\\n\t{ \\\n\t\tchar *text; \\\n\t\ttext = uiEntryText(entry); \\\n\t\tsetter(type(data), text); \\\n\t\tuiFreeText(text); \\\n\t}\nTEXT(Window, uiWindow, uiWindowTitle, uiWindowSetTitle)\nTEXT(Button, uiButton, uiButtonText, uiButtonSetText)\nTEXT(Checkbox, uiCheckbox, uiCheckboxText, uiCheckboxSetText)\nTEXT(Label, uiLabel, uiLabelText, uiLabelSetText)\nTEXT(Group, uiGroup, uiGroupTitle, uiGroupSetTitle)\n\nstatic void onChanged(uiEntry *e, void *data)\n{\n\tprintf(\"onChanged()\\n\");\n}\n\nstatic void toggleSpaced(uiCheckbox *c, void *data)\n{\n\tsetSpaced(uiCheckboxChecked(spaced));\n}\n\nstatic void forceSpaced(uiButton *b, void *data)\n{\n\tuiCheckboxSetChecked(spaced, data != NULL);\n}\n\nstatic void showSpaced(uiButton *b, void *data)\n{\n\tchar s[12];\n\n\tquerySpaced(s);\n\tuiEntrySetText(entry, s);\n}\n\n#define SHED(method, Method) \\\n\tstatic void method ## Control(uiButton *b, void *data) \\\n\t{ \\\n\t\tuiControl ## Method(uiControl(data)); \\\n\t}\nSHED(show, Show)\nSHED(hide, Hide)\nSHED(enable, Enable)\nSHED(disable, Disable)\n\nuiBox *page1;\n\nvoid makePage1(uiWindow *w)\n{\n\tuiButton *getButton, *setButton;\n\tuiBox *hbox;\n\tuiBox *testBox;\n\tuiLabel *label;\n\n\tpage1 = newVerticalBox();\n\n\tentry = uiNewEntry();\n\tuiEntryOnChanged(entry, onChanged, NULL);\n\tuiBoxAppend(page1, uiControl(entry), 0);\n\n\tspaced = uiNewCheckbox(\"Spaced\");\n\tuiCheckboxOnToggled(spaced, toggleSpaced, NULL);\n\tlabel = uiNewLabel(\"Label\");\n\n\thbox = newHorizontalBox();\n\tgetButton = uiNewButton(\"Get Window Text\");\n\tuiButtonOnClicked(getButton, getWindowText, w);\n\tsetButton = uiNewButton(\"Set Window Text\");\n\tuiButtonOnClicked(setButton, setWindowText, w);\n\tuiBoxAppend(hbox, uiControl(getButton), 1);\n\tuiBoxAppend(hbox, uiControl(setButton), 1);\n\tuiBoxAppend(page1, uiControl(hbox), 0);\n\n\thbox = newHorizontalBox();\n\tgetButton = uiNewButton(\"Get Button Text\");\n\tuiButtonOnClicked(getButton, getButtonText, getButton);\n\tsetButton = uiNewButton(\"Set Button Text\");\n\tuiButtonOnClicked(setButton, setButtonText, getButton);\n\tuiBoxAppend(hbox, uiControl(getButton), 1);\n\tuiBoxAppend(hbox, uiControl(setButton), 1);\n\tuiBoxAppend(page1, uiControl(hbox), 0);\n\n\thbox = newHorizontalBox();\n\tgetButton = uiNewButton(\"Get Checkbox Text\");\n\tuiButtonOnClicked(getButton, getCheckboxText, spaced);\n\tsetButton = uiNewButton(\"Set Checkbox Text\");\n\tuiButtonOnClicked(setButton, setCheckboxText, spaced);\n\tuiBoxAppend(hbox, uiControl(getButton), 1);\n\tuiBoxAppend(hbox, uiControl(setButton), 1);\n\tuiBoxAppend(page1, uiControl(hbox), 0);\n\n\thbox = newHorizontalBox();\n\tgetButton = uiNewButton(\"Get Label Text\");\n\tuiButtonOnClicked(getButton, getLabelText, label);\n\tsetButton = uiNewButton(\"Set Label Text\");\n\tuiButtonOnClicked(setButton, setLabelText, label);\n\tuiBoxAppend(hbox, uiControl(getButton), 1);\n\tuiBoxAppend(hbox, uiControl(setButton), 1);\n\tuiBoxAppend(page1, uiControl(hbox), 0);\n\n\thbox = newHorizontalBox();\n\tgetButton = uiNewButton(\"Get Group Text\");\n\tuiButtonOnClicked(getButton, getGroupText, page2group);\n\tsetButton = uiNewButton(\"Set Group Text\");\n\tuiButtonOnClicked(setButton, setGroupText, page2group);\n\tuiBoxAppend(hbox, uiControl(getButton), 1);\n\tuiBoxAppend(hbox, uiControl(setButton), 1);\n\tuiBoxAppend(page1, uiControl(hbox), 0);\n\n\thbox = newHorizontalBox();\n\tuiBoxAppend(hbox, uiControl(spaced), 1);\n\tgetButton = uiNewButton(\"On\");\n\tuiButtonOnClicked(getButton, forceSpaced, getButton);\n\tuiBoxAppend(hbox, uiControl(getButton), 0);\n\tgetButton = uiNewButton(\"Off\");\n\tuiButtonOnClicked(getButton, forceSpaced, NULL);\n\tuiBoxAppend(hbox, uiControl(getButton), 0);\n\tgetButton = uiNewButton(\"Show\");\n\tuiButtonOnClicked(getButton, showSpaced, NULL);\n\tuiBoxAppend(hbox, uiControl(getButton), 0);\n\tuiBoxAppend(page1, uiControl(hbox), 0);\n\n\ttestBox = newHorizontalBox();\n\tsetButton = uiNewButton(\"Button\");\n\tuiBoxAppend(testBox, uiControl(setButton), 1);\n\tgetButton = uiNewButton(\"Show\");\n\tuiButtonOnClicked(getButton, showControl, setButton);\n\tuiBoxAppend(testBox, uiControl(getButton), 0);\n\tgetButton = uiNewButton(\"Hide\");\n\tuiButtonOnClicked(getButton, hideControl, setButton);\n\tuiBoxAppend(testBox, uiControl(getButton), 0);\n\tgetButton = uiNewButton(\"Enable\");\n\tuiButtonOnClicked(getButton, enableControl, setButton);\n\tuiBoxAppend(testBox, uiControl(getButton), 0);\n\tgetButton = uiNewButton(\"Disable\");\n\tuiButtonOnClicked(getButton, disableControl, setButton);\n\tuiBoxAppend(testBox, uiControl(getButton), 0);\n\tuiBoxAppend(page1, uiControl(testBox), 0);\n\n\thbox = newHorizontalBox();\n\tgetButton = uiNewButton(\"Show Box\");\n\tuiButtonOnClicked(getButton, showControl, testBox);\n\tuiBoxAppend(hbox, uiControl(getButton), 1);\n\tgetButton = uiNewButton(\"Hide Box\");\n\tuiButtonOnClicked(getButton, hideControl, testBox);\n\tuiBoxAppend(hbox, uiControl(getButton), 1);\n\tgetButton = uiNewButton(\"Enable Box\");\n\tuiButtonOnClicked(getButton, enableControl, testBox);\n\tuiBoxAppend(hbox, uiControl(getButton), 1);\n\tgetButton = uiNewButton(\"Disable Box\");\n\tuiButtonOnClicked(getButton, disableControl, testBox);\n\tuiBoxAppend(hbox, uiControl(getButton), 1);\n\tuiBoxAppend(page1, uiControl(hbox), 0);\n\n\tuiBoxAppend(page1, uiControl(label), 0);\n}\n"
  },
  {
    "path": "test/page10.c",
    "content": "// 22 december 2015\n#include \"test.h\"\n\nstatic uiEntry *textString;\nstatic uiFontButton *textFontButton;\nstatic uiColorButton *textColorButton;\nstatic uiEntry *textWidth;\nstatic uiButton *textApply;\nstatic uiCheckbox *noZ;\nstatic uiArea *textArea;\nstatic uiAreaHandler textAreaHandler;\n\nstatic double entryDouble(uiEntry *e)\n{\n\tchar *s;\n\tdouble d;\n\n\ts = uiEntryText(e);\n\td = atof(s);\n\tuiFreeText(s);\n\treturn d;\n}\n\nstatic void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *dp)\n{\n\tuiDrawTextFont *font;\n\tuiDrawTextLayout *layout;\n\tdouble r, g, b, al;\n\tchar surrogates[1 + 4 + 1 + 1];\n\tchar composed[2 + 2 + 2 + 3 + 2 + 1];\n\tdouble width, height;\n\n\tfont = uiFontButtonFont(textFontButton);\n\n\tlayout = uiDrawNewTextLayout(\"One two three four\", font, -1);\n\tuiDrawTextLayoutSetColor(layout,\n\t\t4, 7,\n\t\t1, 0, 0, 1);\n\tuiDrawTextLayoutSetColor(layout,\n\t\t8, 14,\n\t\t1, 0, 0.5, 0.5);\n\tuiColorButtonColor(textColorButton, &r, &g, &b, &al);\n\tuiDrawTextLayoutSetColor(layout,\n\t\t14, 18,\n\t\tr, g, b, al);\n\tuiDrawText(dp->Context, 10, 10, layout);\n\tuiDrawTextLayoutExtents(layout, &width, &height);\n\tuiDrawFreeTextLayout(layout);\n\n\tsurrogates[0] = 'x';\n\tsurrogates[1] = 0xF0;\t\t// surrogates D800 DF08\n\tsurrogates[2] = 0x90;\n\tsurrogates[3] = 0x8C;\n\tsurrogates[4] = 0x88;\n\tsurrogates[5] = 'y';\n\tsurrogates[6] = '\\0';\n\n\tlayout = uiDrawNewTextLayout(surrogates, font, -1);\n\tuiDrawTextLayoutSetColor(layout,\n\t\t1, 2,\n\t\t1, 0, 0.5, 0.5);\n\tuiDrawText(dp->Context, 10, 10 + height, layout);\n\tuiDrawFreeTextLayout(layout);\n\n\tcomposed[0] = 'z';\n\tcomposed[1] = 'z';\n\tcomposed[2] = 0xC3;\t\t// 2\n\tcomposed[3] = 0xA9;\n\tcomposed[4] = 'z';\n\tcomposed[5] = 'z';\n\tcomposed[6] = 0x65;\t\t// 5\n\tcomposed[7] = 0xCC;\n\tcomposed[8] = 0x81;\n\tcomposed[9] = 'z';\n\tcomposed[10] = 'z';\n\tcomposed[11] = '\\0';\n\n\tlayout = uiDrawNewTextLayout(composed, font, -1);\n\tuiDrawTextLayoutSetColor(layout,\n\t\t2, 3,\n\t\t1, 0, 0.5, 0.5);\n\tuiDrawTextLayoutSetColor(layout,\n\t\t5, 6,\n\t\t1, 0, 0.5, 0.5);\n\tif (!uiCheckboxChecked(noZ))\n\t\tuiDrawTextLayoutSetColor(layout,\n\t\t\t6, 7,\n\t\t\t0.5, 0, 1, 0.5);\n\tuiDrawText(dp->Context, 10, 10 + height + height, layout);\n\tuiDrawFreeTextLayout(layout);\n\n\tuiDrawFreeTextFont(font);\n}\n\nstatic void handlerMouseEvent(uiAreaHandler *a, uiArea *area, uiAreaMouseEvent *e)\n{\n\t// do nothing\n}\n\nstatic void handlerMouseCrossed(uiAreaHandler *ah, uiArea *a, int left)\n{\n\t// do nothing\n}\n\nstatic void handlerDragBroken(uiAreaHandler *ah, uiArea *a)\n{\n\t// do nothing\n}\n\nstatic int handlerKeyEvent(uiAreaHandler *ah, uiArea *a, uiAreaKeyEvent *e)\n{\n\t// do nothing\n\treturn 0;\n}\n\nstatic void onFontChanged(uiFontButton *b, void *data)\n{\n\tuiAreaQueueRedrawAll(textArea);\n}\n\nstatic void onColorChanged(uiColorButton *b, void *data)\n{\n\tuiAreaQueueRedrawAll(textArea);\n}\n\nstatic void onNoZ(uiCheckbox *b, void *data)\n{\n\tuiAreaQueueRedrawAll(textArea);\n}\n\nuiBox *makePage10(void)\n{\n\tuiBox *page10;\n\tuiBox *vbox;\n\tuiBox *hbox;\n\n\tpage10 = newVerticalBox();\n\tvbox = page10;\n\n\thbox = newHorizontalBox();\n\tuiBoxAppend(vbox, uiControl(hbox), 0);\n\n\ttextString = uiNewEntry();\n\t// TODO make it placeholder\n\tuiEntrySetText(textString, \"Enter text here\");\n\tuiBoxAppend(hbox, uiControl(textString), 1);\n\n\ttextFontButton = uiNewFontButton();\n\tuiFontButtonOnChanged(textFontButton, onFontChanged, NULL);\n\tuiBoxAppend(hbox, uiControl(textFontButton), 1);\n\n\ttextColorButton = uiNewColorButton();\n\tuiColorButtonOnChanged(textColorButton, onColorChanged, NULL);\n\tuiBoxAppend(hbox, uiControl(textColorButton), 1);\n\n\thbox = newHorizontalBox();\n\tuiBoxAppend(vbox, uiControl(hbox), 0);\n\n\ttextApply = uiNewButton(\"Apply\");\n\tuiBoxAppend(hbox, uiControl(textApply), 1);\n\n\ttextWidth = uiNewEntry();\n\tuiEntrySetText(textWidth, \"-1\");\n\tuiBoxAppend(hbox, uiControl(textWidth), 1);\n\n\tnoZ = uiNewCheckbox(\"No Z Color\");\n\tuiCheckboxOnToggled(noZ, onNoZ, NULL);\n\tuiBoxAppend(hbox, uiControl(noZ), 0);\n\n\ttextAreaHandler.Draw = handlerDraw;\n\ttextAreaHandler.MouseEvent = handlerMouseEvent;\n\ttextAreaHandler.MouseCrossed = handlerMouseCrossed;\n\ttextAreaHandler.DragBroken = handlerDragBroken;\n\ttextAreaHandler.KeyEvent = handlerKeyEvent;\n\ttextArea = uiNewArea(&textAreaHandler);\n\tuiBoxAppend(vbox, uiControl(textArea), 1);\n\n\t// dummy objects to test single-activation\n\thbox = newHorizontalBox();\n\tuiBoxAppend(vbox, uiControl(hbox), 0);\n\tuiBoxAppend(hbox, uiControl(uiNewFontButton()), 1);\n\tuiBoxAppend(hbox, uiControl(uiNewColorButton()), 1);\n\n\treturn page10;\n}\n"
  },
  {
    "path": "test/page11.c",
    "content": "// 14 may 2016\n#include \"test.h\"\n\n// TODO add a test for childless windows\n// TODO add tests for contianers with all controls hidden\n\nstatic uiGroup *newg(const char *n, int s)\n{\n\tuiGroup *g;\n\n\tg = uiNewGroup(n);\n\tif (s)\n\t\tuiGroupSetChild(g, NULL);\n\treturn g;\n}\n\nstatic uiTab *newt(int tt)\n{\n\tuiTab *t;\n\n\tt = uiNewTab();\n\tif (tt)\n\t\tuiTabAppend(t, \"Test\", NULL);\n\treturn t;\n}\n\nuiBox *makePage11(void)\n{\n\tuiBox *page11;\n\tuiBox *ns;\n\tuiBox *s;\n\n\tpage11 = newHorizontalBox();\n\n\tns = newVerticalBox();\n\tuiBoxAppend(ns, uiControl(newg(\"\", 0)), 0);\n\tuiBoxAppend(ns, uiControl(newg(\"\", 1)), 0);\n\tuiBoxAppend(ns, uiControl(newg(\"Group\", 0)), 0);\n\tuiBoxAppend(ns, uiControl(newg(\"Group\", 1)), 0);\n\tuiBoxAppend(ns, uiControl(newt(0)), 0);\n\tuiBoxAppend(ns, uiControl(newt(1)), 0);\n\tuiBoxAppend(page11, uiControl(ns), 1);\n\n\ts = newVerticalBox();\n\tuiBoxAppend(s, uiControl(newg(\"\", 0)), 1);\n\tuiBoxAppend(s, uiControl(newg(\"\", 1)), 1);\n\tuiBoxAppend(s, uiControl(newg(\"Group\", 0)), 1);\n\tuiBoxAppend(s, uiControl(newg(\"Group\", 1)), 1);\n\tuiBoxAppend(s, uiControl(newt(0)), 1);\n\tuiBoxAppend(s, uiControl(newt(1)), 1);\n\tuiBoxAppend(page11, uiControl(s), 1);\n\n\treturn page11;\n}\n"
  },
  {
    "path": "test/page12.c",
    "content": "// 22 may 2016\n#include \"test.h\"\n\n// TODO OS X: if the hboxes are empty, the text views don't show up\n\nstatic void meChanged(uiMultilineEntry *e, void *data)\n{\n\tprintf(\"%s changed\\n\", (char *) data);\n}\n\nstatic void setClicked(uiButton *b, void *data)\n{\n\tuiMultilineEntrySetText(uiMultilineEntry(data), \"set\");\n}\n\nstatic void appendClicked(uiButton *b, void *data)\n{\n\tuiMultilineEntryAppend(uiMultilineEntry(data), \"append\\n\");\n}\n\nstatic uiBox *half(uiMultilineEntry *(*mk)(void), const char *which)\n{\n\tuiBox *vbox, *hbox;\n\tuiMultilineEntry *me;\n\tuiButton *button;\n\n\tvbox = newVerticalBox();\n\n\tme = (*mk)();\n\tuiMultilineEntryOnChanged(me, meChanged, (void *) which);\n\tuiBoxAppend(vbox, uiControl(me), 1);\n\n\thbox = newHorizontalBox();\n\tuiBoxAppend(vbox, uiControl(hbox), 0);\n\n\tbutton = uiNewButton(\"Set\");\n\tuiButtonOnClicked(button, setClicked, me);\n\tuiBoxAppend(hbox, uiControl(button), 0);\n\n\tbutton = uiNewButton(\"Append\");\n\tuiButtonOnClicked(button, appendClicked, me);\n\tuiBoxAppend(hbox, uiControl(button), 0);\n\n\treturn vbox;\n}\n\nuiBox *makePage12(void)\n{\n\tuiBox *page12;\n\tuiBox *b;\n\n\tpage12 = newHorizontalBox();\n\n\tb = half(uiNewMultilineEntry, \"wrap\");\n\tuiBoxAppend(page12, uiControl(b), 1);\n\tb = half(uiNewNonWrappingMultilineEntry, \"no wrap\");\n\tuiBoxAppend(page12, uiControl(b), 1);\n\n\treturn page12;\n}\n"
  },
  {
    "path": "test/page13.c",
    "content": "// 28 may 2016\n#include \"test.h\"\n\nstatic int winClose(uiWindow *w, void *data)\n{\n\treturn 1;\n}\n\nstatic void openTestWindow(uiBox *(*mkf)(void))\n{\n\tuiWindow *w;\n\tuiBox *b;\n\tuiCombobox *c;\n\tuiEditableCombobox *e;\n\tuiRadioButtons *r;\n\n\tw = uiNewWindow(\"Test\", 100, 100, 0);\n\tuiWindowOnClosing(w, winClose, NULL);\n\tuiWindowSetMargined(w, 1);\n\tb = (*mkf)();\n\tuiWindowSetChild(w, uiControl(b));\n\n#define BA(x) uiBoxAppend(b, uiControl(x), 0)\n\tBA(uiNewButton(\"\"));\n\tBA(uiNewCheckbox(\"\"));\n\tBA(uiNewEntry());\n\tBA(uiNewLabel(\"\"));\n\tBA(uiNewSpinbox(0, 100));\n\tBA(uiNewProgressBar());\n\tBA(uiNewSlider(0, 100));\n\tBA(uiNewHorizontalSeparator());\n\tc = uiNewCombobox();\n\tuiComboboxAppend(c, \"\");\n\tBA(c);\n\te = uiNewEditableCombobox();\n\tuiEditableComboboxAppend(e, \"\");\n\tBA(e);\n\tr = uiNewRadioButtons();\n\tuiRadioButtonsAppend(r, \"\");\n\tBA(r);\n\tBA(uiNewDateTimePicker());\n\tBA(uiNewDatePicker());\n\tBA(uiNewTimePicker());\n\tBA(uiNewMultilineEntry());\n\t// TODO nonscrolling and scrolling areas?\n\tBA(uiNewFontButton());\n\tBA(uiNewColorButton());\n\tBA(uiNewPasswordEntry());\n\tBA(uiNewSearchEntry());\n\tBA(uiNewVerticalSeparator());\n\n\tuiControlShow(uiControl(w));\n}\n\nstatic void buttonClicked(uiButton *b, void *data)\n{\n\topenTestWindow((uiBox *(*)(void)) data);\n}\n\nstatic void entryChanged(uiEntry *e, void *data)\n{\n\tchar *text;\n\n\ttext = uiEntryText(e);\n\tprintf(\"%s entry changed: %s\\n\", (const char *) data, text);\n\tuiFreeText(text);\n}\n\nstatic void showHide(uiButton *b, void *data)\n{\n\tuiControl *c = uiControl(data);\n\n\tif (uiControlVisible(c))\n\t\tuiControlHide(c);\n\telse\n\t\tuiControlShow(c);\n}\n\nstatic void setIndeterminate(uiButton *b, void *data)\n{\n\tuiProgressBar *p = uiProgressBar(data);\n\tint value;\n\n\tvalue = uiProgressBarValue(p);\n\tif (value == -1)\n\t\tvalue = 50;\n\telse\n\t\tvalue = -1;\n\tuiProgressBarSetValue(p, value);\n}\n\nstatic void deleteFirst(uiButton *b, void *data)\n{\n\tuiForm *f = uiForm(data);\n\n\tuiFormDelete(f, 0);\n}\n\nuiBox *makePage13(void)\n{\n\tuiBox *page13;\n\tuiRadioButtons *rb;\n\tuiButton *b;\n\tuiForm *f;\n\tuiEntry *e;\n\tuiProgressBar *p;\n\n\tpage13 = newVerticalBox();\n\n\trb = uiNewRadioButtons();\n\tuiRadioButtonsAppend(rb, \"Item 1\");\n\tuiRadioButtonsAppend(rb, \"Item 2\");\n\tuiRadioButtonsAppend(rb, \"Item 3\");\n\tuiBoxAppend(page13, uiControl(rb), 0);\n\n\trb = uiNewRadioButtons();\n\tuiRadioButtonsAppend(rb, \"Item A\");\n\tuiRadioButtonsAppend(rb, \"Item B\");\n\tuiBoxAppend(page13, uiControl(rb), 0);\n\n\tb = uiNewButton(\"Horizontal\");\n\tuiButtonOnClicked(b, buttonClicked, uiNewHorizontalBox);\n\tuiBoxAppend(page13, uiControl(b), 0);\n\n\tb = uiNewButton(\"Vertical\");\n\tuiButtonOnClicked(b, buttonClicked, uiNewVerticalBox);\n\tuiBoxAppend(page13, uiControl(b), 0);\n\n\tf = newForm();\n\n\te = uiNewPasswordEntry();\n\tuiEntryOnChanged(e, entryChanged, \"password\");\n\tuiFormAppend(f, \"Password Entry\", uiControl(e), 0);\n\n\te = uiNewSearchEntry();\n\tuiEntryOnChanged(e, entryChanged, \"search\");\n\tuiFormAppend(f, \"Search Box\", uiControl(e), 0);\n\n\tuiFormAppend(f, \"MLE\", uiControl(uiNewMultilineEntry()), 1);\n\n\tp = uiNewProgressBar();\n\tuiProgressBarSetValue(p, 50);\n\tuiBoxAppend(page13, uiControl(p), 0);\n\tb = uiNewButton(\"Toggle Indeterminate\");\n\tuiButtonOnClicked(b, setIndeterminate, p);\n\tuiBoxAppend(page13, uiControl(b), 0);\n\n\tb = uiNewButton(\"Show/Hide\");\n\tuiButtonOnClicked(b, showHide, e);\n\tuiBoxAppend(page13, uiControl(b), 0);\n\tb = uiNewButton(\"Delete First\");\n\tuiButtonOnClicked(b, deleteFirst, f);\n\tuiBoxAppend(page13, uiControl(b), 0);\n\tuiBoxAppend(page13, uiControl(f), 1);\n\n\treturn page13;\n}\n"
  },
  {
    "path": "test/page14.c",
    "content": "// 9 june 2016\n#include \"test.h\"\n\n// TODOs:\n// - GTK+ - make all expanding controls the same size, to match the other OSs? will they match the other OSs?\n\nenum {\n\tred,\n\tgreen,\n\tblue,\n\tyellow,\n\twhite,\n\tmagenta,\n\torange,\n\tpurple,\n\tcyan,\n};\n\nstatic const struct {\n\tdouble r;\n\tdouble g;\n\tdouble b;\n} colors[] = {\n\t{ 1, 0, 0 },\n\t{ 0, 0.5, 0 },\n\t{ 0, 0, 1 },\n\t{ 1, 1, 0 },\n\t{ 1, 1, 1 },\n\t{ 1, 0, 1 },\n\t{ 1, 0.65, 0 },\n\t{ 0.5, 0, 0.5 },\n\t{ 0, 1, 1 },\n};\n\nstatic uiControl *testControl(const char *label, int color)\n{\n\tuiColorButton *b;\n\n\tb = uiNewColorButton();\n\tuiColorButtonSetColor(b, colors[color].r, colors[color].g, colors[color].b, 1.0);\n\treturn uiControl(b);\n}\n\nstatic uiControl *simpleGrid(void)\n{\n\tuiGrid *g;\n\tuiControl *t4;\n\n\tg = newGrid();\n\tuiGridAppend(g, testControl(\"1\", red),\n\t\t0, 0, 1, 1,\n\t\t0, uiAlignFill, 0, uiAlignFill);\n\tuiGridAppend(g, testControl(\"2\", green),\n\t\t1, 0, 1, 1,\n\t\t0, uiAlignFill, 0, uiAlignFill);\n\tuiGridAppend(g, testControl(\"3\", blue),\n\t\t2, 0, 1, 1,\n\t\t0, uiAlignFill, 0, uiAlignFill);\n\tt4 = testControl(\"4\", green);\n\tuiGridAppend(g, t4,\n\t\t0, 1, 1, 1,\n\t\t0, uiAlignFill, 1, uiAlignFill);\n\tuiGridInsertAt(g, testControl(\"5\", blue),\n\t\tt4, uiAtTrailing, 2, 1,\n\t\t0, uiAlignFill, 0, uiAlignFill);\n\tuiGridAppend(g, testControl(\"6\", yellow),\n\t\t-1, 0, 1, 2,\n\t\t1, uiAlignFill, 0, uiAlignFill);\n\treturn uiControl(g);\n}\n\nstatic uiControl *boxComparison(void)\n{\n\tuiBox *vbox;\n\tuiGrid *g;\n\tuiBox *hbox;\n\n\tvbox = newVerticalBox();\n\tuiBoxAppend(vbox, uiControl(uiNewLabel(\"Above\")), 0);\n\tuiBoxAppend(vbox, uiControl(uiNewHorizontalSeparator()), 0);\n\n\thbox = newHorizontalBox();\n\tuiBoxAppend(vbox, uiControl(hbox), 0);\n\tuiBoxAppend(hbox, testControl(\"1\", white), 0);\n\tuiBoxAppend(hbox, uiControl(uiNewLabel(\"A label\")), 1);\n\tuiBoxAppend(hbox, testControl(\"2\", green), 0);\n\tuiBoxAppend(hbox, uiControl(uiNewLabel(\"Another label\")), 1);\n\tuiBoxAppend(hbox, testControl(\"3\", red), 0);\n\n\tuiBoxAppend(vbox, uiControl(uiNewHorizontalSeparator()), 0);\n\n\tg = newGrid();\n\tuiBoxAppend(vbox, uiControl(g), 0);\n\tuiGridAppend(g, testControl(\"1\", white),\n\t\t0, 0, 1, 1,\n\t\t0, uiAlignFill, 0, uiAlignFill);\n\tuiGridAppend(g, uiControl(uiNewLabel(\"A label\")),\n\t\t1, 0, 1, 1,\n\t\t1, uiAlignFill, 0, uiAlignFill);\n\tuiGridAppend(g, testControl(\"2\", green),\n\t\t2, 0, 1, 1,\n\t\t0, uiAlignFill, 0, uiAlignFill);\n\tuiGridAppend(g, uiControl(uiNewLabel(\"Another label\")),\n\t\t3, 0, 1, 1,\n\t\t1, uiAlignFill, 0, uiAlignFill);\n\tuiGridAppend(g, testControl(\"3\", red),\n\t\t4, 0, 1, 1,\n\t\t0, uiAlignFill, 0, uiAlignFill);\n\n\tuiBoxAppend(vbox, uiControl(uiNewHorizontalSeparator()), 0);\n\tuiBoxAppend(vbox, uiControl(uiNewLabel(\"Below\")), 0);\n\treturn uiControl(vbox);\n}\n\nstatic uiControl *emptyLine(void)\n{\n\tuiGrid *g;\n\n\tg = newGrid();\n\tuiGridAppend(g, testControl(\"(0, 0)\", red),\n\t\t0, 0, 1, 1,\n\t\t1, uiAlignFill, 1, uiAlignFill);\n\tuiGridAppend(g, testControl(\"(0, 1)\", blue),\n\t\t0, 1, 1, 1,\n\t\t0, uiAlignFill, 0, uiAlignFill);\n\tuiGridAppend(g, testControl(\"(10, 0)\", green),\n\t\t10, 0, 1, 1,\n\t\t0, uiAlignFill, 0, uiAlignFill);\n\tuiGridAppend(g, testControl(\"(10, 1)\", magenta),\n\t\t10, 1, 1, 1,\n\t\t0, uiAlignFill, 0, uiAlignFill);\n\treturn uiControl(g);\n}\n\nstatic uiControl *emptyGrid(void)\n{\n\tuiGrid *g;\n\tuiControl *t;\n\n\tg = newGrid();\n\tt = testControl(\"(0, 0)\", red);\n\tuiGridAppend(g, t,\n\t\t0, 0, 1, 1,\n\t\t1, uiAlignFill, 1, uiAlignFill);\n\tuiControlHide(t);\n\treturn uiControl(g);\n}\n\n// TODO insert (need specialized insert/delete)\n\nstatic uiControl *spanningGrid(void)\n{\n\tuiGrid *g;\n\n\tg = newGrid();\n\tuiGridAppend(g, testControl(\"0\", blue),\n\t\t0, 4, 4, 1,\n\t\t1, uiAlignFill, 0, uiAlignFill);\n\tuiGridAppend(g, testControl(\"1\", green),\n\t\t4, 0, 1, 4,\n\t\t0, uiAlignFill, 1, uiAlignFill);\n\tuiGridAppend(g, testControl(\"2\", red),\n\t\t3, 3, 1, 1,\n\t\t1, uiAlignFill, 1, uiAlignFill);\n\tuiGridAppend(g, testControl(\"3\", yellow),\n\t\t0, 3, 2, 1,\n\t\t0, uiAlignFill, 0, uiAlignFill);\n\tuiGridAppend(g, testControl(\"4\", orange),\n\t\t3, 0, 1, 2,\n\t\t0, uiAlignFill, 0, uiAlignFill);\n\tuiGridAppend(g, testControl(\"5\", purple),\n\t\t1, 1, 1, 1,\n\t\t0, uiAlignFill, 0, uiAlignFill);\n\tuiGridAppend(g, testControl(\"6\", white),\n\t\t0, 1, 1, 1,\n\t\t0, uiAlignFill, 0, uiAlignFill);\n\tuiGridAppend(g, testControl(\"7\", cyan),\n\t\t1, 0, 1, 1,\n\t\t0, uiAlignFill, 0, uiAlignFill);\n\treturn uiControl(g);\n}\n\n// TODO make non-global\nstatic uiButton *hideOne, *one, *showOne;\n\nstatic void onHideOne(uiButton *b, void *data)\n{\n\tuiControlHide(uiControl(one));\n}\n\nstatic void onShowOne(uiButton *b, void *data)\n{\n\tuiControlShow(uiControl(one));\n}\n\nstatic void onHideAll(uiButton *b, void *data)\n{\n\tuiControlHide(uiControl(hideOne));\n\tuiControlHide(uiControl(one));\n\tuiControlHide(uiControl(showOne));\n}\n\nstatic void onShowAll(uiButton *b, void *data)\n{\n\tuiControlShow(uiControl(hideOne));\n\tuiControlShow(uiControl(one));\n\tuiControlShow(uiControl(showOne));\n}\n\n#define AT(x) static void onInsert ## x(uiButton *b, void *data) \\\n\t{ \\\n\t\tuiGrid *g = uiGrid(data); \\\n\t\tuiGridInsertAt(g, uiControl(uiNewButton(\"Button\")), \\\n\t\t\tuiControl(b), uiAt ## x, 1, 1, \\\n\t\t\t0, uiAlignFill, 0, uiAlignFill); \\\n\t}\nAT(Leading)\nAT(Top)\nAT(Trailing)\nAT(Bottom)\n\nstatic uiControl *assorted(void)\n{\n\tuiGrid *outergrid;\n\tuiGrid *innergrid;\n\tuiButton *b;\n\n\toutergrid = newGrid();\n\n\tinnergrid = newGrid();\n\tone = uiNewButton(\"Test\");\n\tuiGridAppend(innergrid, uiControl(one),\n\t\t1, 1, 1, 1,\n\t\t0, uiAlignFill, 0, uiAlignFill);\n\thideOne = uiNewButton(\"Hide One\");\n\tuiButtonOnClicked(hideOne, onHideOne, NULL);\n\tuiGridAppend(innergrid, uiControl(hideOne),\n\t\t0, 1, 1, 1,\n\t\t0, uiAlignFill, 0, uiAlignFill);\n\tshowOne = uiNewButton(\"Show One\");\n\tuiButtonOnClicked(showOne, onShowOne, NULL);\n\tuiGridAppend(innergrid, uiControl(showOne),\n\t\t2, 1, 1, 1,\n\t\t0, uiAlignFill, 0, uiAlignFill);\n\tb = uiNewButton(\"Hide All\");\n\tuiButtonOnClicked(b, onHideAll, NULL);\n\tuiGridAppend(innergrid, uiControl(b),\n\t\t1, 0, 1, 1,\n\t\t0, uiAlignFill, 0, uiAlignFill);\n\tb = uiNewButton(\"Show All\");\n\tuiButtonOnClicked(b, onShowAll, NULL);\n\tuiGridAppend(innergrid, uiControl(b),\n\t\t1, 2, 1, 1,\n\t\t0, uiAlignFill, 0, uiAlignFill);\n\tuiGridAppend(outergrid, uiControl(innergrid),\n\t\t0, 0, 1, 1,\n\t\t1, uiAlignFill, 1, uiAlignFill);\n\n\tinnergrid = newGrid();\n\tb = uiNewButton(\"Insert Trailing\");\n\tuiButtonOnClicked(b, onInsertTrailing, innergrid);\n\tuiGridAppend(innergrid, uiControl(b),\n\t\t0, 0, 1, 1,\n\t\t1, uiAlignFill, 0, uiAlignFill);\n\tb = uiNewButton(\"Insert Bottom\");\n\tuiButtonOnClicked(b, onInsertBottom, innergrid);\n\tuiGridAppend(innergrid, uiControl(b),\n\t\t1, 0, 1, 1,\n\t\t1, uiAlignFill, 0, uiAlignFill);\n\tb = uiNewButton(\"Insert Leading\");\n\tuiButtonOnClicked(b, onInsertLeading, innergrid);\n\tuiGridAppend(innergrid, uiControl(b),\n\t\t1, 1, 1, 1,\n\t\t1, uiAlignFill, 0, uiAlignFill);\n\tb = uiNewButton(\"Insert Top\");\n\tuiButtonOnClicked(b, onInsertTop, innergrid);\n\tuiGridAppend(innergrid, uiControl(b),\n\t\t0, 1, 1, 1,\n\t\t1, uiAlignFill, 0, uiAlignFill);\n\tuiGridAppend(outergrid, uiControl(innergrid),\n\t\t1, 0, 1, 1,\n\t\t1, uiAlignFill, 1, uiAlignFill);\n\n\tinnergrid = newGrid();\n\tuiGridAppend(innergrid, uiControl(uiNewColorButton()),\n\t\t0, 0, 1, 1,\n\t\t1, uiAlignFill, 0, uiAlignFill);\n\tuiGridAppend(innergrid, uiControl(uiNewColorButton()),\n\t\t0, 1, 1, 1,\n\t\t1, uiAlignStart, 0, uiAlignFill);\n\tuiGridAppend(innergrid, uiControl(uiNewColorButton()),\n\t\t0, 2, 1, 1,\n\t\t1, uiAlignCenter, 0, uiAlignFill);\n\tuiGridAppend(innergrid, uiControl(uiNewColorButton()),\n\t\t0, 3, 1, 1,\n\t\t1, uiAlignEnd, 0, uiAlignFill);\n\tuiGridAppend(outergrid, uiControl(innergrid),\n\t\t0, 1, 1, 1,\n\t\t1, uiAlignFill, 1, uiAlignFill);\n\n\t// TODO with only this, wrong size on OS X — expand sizing thing?\n\tinnergrid = newGrid();\n\tuiGridAppend(innergrid, uiControl(uiNewColorButton()),\n\t\t0, 0, 1, 1,\n\t\t0, uiAlignFill, 1, uiAlignFill);\n\tuiGridAppend(innergrid, uiControl(uiNewColorButton()),\n\t\t1, 0, 1, 1,\n\t\t0, uiAlignFill, 1, uiAlignStart);\n\tuiGridAppend(innergrid, uiControl(uiNewColorButton()),\n\t\t2, 0, 1, 1,\n\t\t0, uiAlignFill, 1, uiAlignCenter);\n\tuiGridAppend(innergrid, uiControl(uiNewColorButton()),\n\t\t3, 0, 1, 1,\n\t\t0, uiAlignFill, 1, uiAlignEnd);\n\tuiGridAppend(outergrid, uiControl(innergrid),\n\t\t1, 1, 1, 1,\n\t\t1, uiAlignFill, 1, uiAlignFill);\n\n\treturn uiControl(outergrid);\n}\n\nstatic const struct {\n\tconst char *name;\n\tuiControl *(*f)(void);\n} pages[] = {\n\t// based on GTK+ test/testgrid.c\n\t{ \"Simple Grid\", simpleGrid },\n\t{ \"Box Comparison\", boxComparison },\n\t{ \"Empty Line\", emptyLine },\n\t{ \"Empty Grid\", emptyGrid },\n\t{ \"Spanning Grid\", spanningGrid },\n\t// my own\n\t{ \"Assorted\", assorted },\n\t{ NULL, NULL },\n};\n\nuiTab *makePage14(void)\n{\n\tuiTab *page14;\n\tint i;\n\n\tpage14 = newTab();\n\n\tfor (i = 0; pages[i].name != NULL; i++)\n\t\tuiTabAppend(page14,\n\t\t\tpages[i].name,\n\t\t\t(*(pages[i].f))());\n\n\treturn page14;\n}\n"
  },
  {
    "path": "test/page15.c",
    "content": "// 15 june 2016\n#include \"test.h\"\n\nstatic uiAreaHandler borderAH;\nstatic int borderAHInit = 0;\nstatic double lastx = -1, lasty = -1;\n\nstruct trect {\n\tdouble left;\n\tdouble top;\n\tdouble right;\n\tdouble bottom;\n\tint in;\n};\n\n#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\n\nstruct tareas {\n\tstruct trect move;\n\tstruct trect alsomove;\n\tstruct trect leftresize;\n\tstruct trect topresize;\n\tstruct trect rightresize;\n\tstruct trect bottomresize; \n\tstruct trect topleftresize;\n\tstruct trect toprightresize;\n\tstruct trect bottomleftresize;\n\tstruct trect bottomrightresize;\n\tstruct trect close;\n};\n\nstatic void filltareas(double awid, double aht, struct tareas *ta)\n{\n\ttsetrect(ta->move, 20, 20, awid - 20, 20 + 30);\n\ttsetrect(ta->alsomove, 30, 200, 100, 270);\n\ttsetrect(ta->leftresize, 5, 20, 15, aht - 20);\n\ttsetrect(ta->topresize, 20, 5, awid - 20, 15);\n\ttsetrect(ta->rightresize, awid - 15, 20, awid - 5, aht - 20);\n\ttsetrect(ta->bottomresize, 20, aht - 15, awid - 20, aht - 5);\n\ttsetrect(ta->topleftresize, 5, 5, 15, 15);\n\ttsetrect(ta->toprightresize, awid - 15, 5, awid - 5, 15);\n\ttsetrect(ta->bottomleftresize, 5, aht - 15, 15, aht - 5);\n\ttsetrect(ta->bottomrightresize, awid - 15, aht - 15, awid - 5, aht - 5);\n\ttsetrect(ta->close, 130, 200, 200, 270);\n}\n\nstatic void drawtrect(uiDrawContext *c, struct trect tr, double r, double g, double bl)\n{\n\tuiDrawPath *p;\n\tuiDrawBrush b;\n\n\tmemset(&b, 0, sizeof (uiDrawBrush));\n\tb.Type = uiDrawBrushTypeSolid;\n\tb.R = r;\n\tb.G = g;\n\tb.B = bl;\n\tb.A = 1.0;\n\tif (tr.in) {\n\t\tb.R += b.R * 0.75;\n\t\tb.G += b.G * 0.75;\n\t\tb.B += b.B * 0.75;\n\t}\n\tp = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathAddRectangle(p,\n\t\ttr.left,\n\t\ttr.top,\n\t\ttr.right - tr.left,\n\t\ttr.bottom - tr.top);\n\tuiDrawPathEnd(p);\n\tuiDrawFill(c, p, &b);\n\tuiDrawFreePath(p);\n}\n\nstatic void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *p)\n{\n\tstruct tareas ta;\n\n\tfilltareas(p->AreaWidth, p->AreaHeight, &ta);\n\tdrawtrect(p->Context, ta.move, 0, 0.5, 0);\n\tdrawtrect(p->Context, ta.alsomove, 0, 0.5, 0);\n\tdrawtrect(p->Context, ta.leftresize, 0, 0, 0.5);\n\tdrawtrect(p->Context, ta.topresize, 0, 0, 0.5);\n\tdrawtrect(p->Context, ta.rightresize, 0, 0, 0.5);\n\tdrawtrect(p->Context, ta.bottomresize, 0, 0, 0.5);\n\tdrawtrect(p->Context, ta.topleftresize, 0, 0.5, 0.5);\n\tdrawtrect(p->Context, ta.toprightresize, 0, 0.5, 0.5);\n\tdrawtrect(p->Context, ta.bottomleftresize, 0, 0.5, 0.5);\n\tdrawtrect(p->Context, ta.bottomrightresize, 0, 0.5, 0.5);\n\tdrawtrect(p->Context, ta.close, 0.5, 0, 0);\n\n\t// TODO add current position prints here\n}\n\nstatic void handlerMouseEvent(uiAreaHandler *a, uiArea *area, uiAreaMouseEvent *e)\n{\n\tstruct tareas ta;\n\n\tlastx = e->X;\n\tlasty = e->Y;\n\tfilltareas(e->AreaWidth, e->AreaHeight, &ta);\n\t// redraw our highlighted rect\n\tuiAreaQueueRedrawAll(area);\n\tif (e->Down != 1)\n\t\treturn;\n\tif (ta.move.in || ta.alsomove.in) {\n\t\tuiAreaBeginUserWindowMove(area);\n\t\treturn;\n\t}\n#define resize(cond, edge) if (cond) { uiAreaBeginUserWindowResize(area, edge); return; }\n\tresize(ta.leftresize.in, uiWindowResizeEdgeLeft)\n\tresize(ta.topresize.in, uiWindowResizeEdgeTop)\n\tresize(ta.rightresize.in, uiWindowResizeEdgeRight)\n\tresize(ta.bottomresize.in, uiWindowResizeEdgeBottom)\n\tresize(ta.topleftresize.in, uiWindowResizeEdgeTopLeft)\n\tresize(ta.toprightresize.in, uiWindowResizeEdgeTopRight)\n\tresize(ta.bottomleftresize.in, uiWindowResizeEdgeBottomLeft)\n\tresize(ta.bottomrightresize.in, uiWindowResizeEdgeBottomRight)\n\tif (ta.close.in) {\n\t\t// TODO\n\t\treturn;\n\t}\n}\n\nstatic void handlerMouseCrossed(uiAreaHandler *ah, uiArea *a, int left)\n{\n}\n\nstatic void handlerDragBroken(uiAreaHandler *ah, uiArea *a)\n{\n}\n\nstatic int handlerKeyEvent(uiAreaHandler *ah, uiArea *a, uiAreaKeyEvent *e)\n{\n\treturn 0;\n}\n\nstatic void borderWindowOpen(uiButton *b, void *data)\n{\n\tuiWindow *w;\n\tuiArea *a;\n\n\tif (!borderAHInit) {\n\t\tborderAH.Draw = handlerDraw;\n\t\tborderAH.MouseEvent = handlerMouseEvent;\n\t\tborderAH.MouseCrossed = handlerMouseCrossed;\n\t\tborderAH.DragBroken = handlerDragBroken;\n\t\tborderAH.KeyEvent = handlerKeyEvent;\n\t\tborderAHInit = 1;\n\t}\n\n\tw = uiNewWindow(\"Border Resize Test\", 300, 500, 0);\n\tuiWindowSetBorderless(w, 1);\n\n\ta = uiNewArea(&borderAH);\n//\tuiWindowSetChild(w, uiControl(a));\n{uiBox *b;\nb=uiNewHorizontalBox();\nuiBoxAppend(b,uiControl(a),1);\nuiWindowSetChild(w,uiControl(b));}\n//TODO why is this hack needed? GTK+ issue\n\n\tuiControlShow(uiControl(w));\n}\n\nstatic uiSpinbox *width, *height;\nstatic uiCheckbox *fullscreen;\n\nstatic void sizeWidth(uiSpinbox *s, void *data)\n{\n\tuiWindow *w = uiWindow(data);\n\tint xp, yp;\n\n\tuiWindowContentSize(w, &xp, &yp);\n\txp = uiSpinboxValue(width);\n\tuiWindowSetContentSize(w, xp, yp);\n}\n\nstatic void sizeHeight(uiSpinbox *s, void *data)\n{\n\tuiWindow *w = uiWindow(data);\n\tint xp, yp;\n\n\tuiWindowContentSize(w, &xp, &yp);\n\typ = uiSpinboxValue(height);\n\tuiWindowSetContentSize(w, xp, yp);\n}\n\nstatic void updatesize(uiWindow *w)\n{\n\tint xp, yp;\n\n\tuiWindowContentSize(w, &xp, &yp);\n\tuiSpinboxSetValue(width, xp);\n\tuiSpinboxSetValue(height, yp);\n\t// TODO on OS X this is updated AFTER sending the size change, not before\n\tuiCheckboxSetChecked(fullscreen, uiWindowFullscreen(w));\n}\n\nvoid onSize(uiWindow *w, void *data)\n{\n\tprintf(\"size\\n\");\n\tupdatesize(w);\n}\n\nvoid setFullscreen(uiCheckbox *cb, void *data)\n{\n\tuiWindow *w = uiWindow(data);\n\n\tuiWindowSetFullscreen(w, uiCheckboxChecked(fullscreen));\n\tupdatesize(w);\n}\n\nstatic void borderless(uiCheckbox *c, void *data)\n{\n\tuiWindow *w = uiWindow(data);\n\n\tuiWindowSetBorderless(w, uiCheckboxChecked(c));\n}\n\nuiBox *makePage15(uiWindow *w)\n{\n\tuiBox *page15;\n\tuiBox *hbox;\n\tuiButton *button;\n\tuiCheckbox *checkbox;\n\n\tpage15 = newVerticalBox();\n\n\thbox = newHorizontalBox();\n\tuiBoxAppend(page15, uiControl(hbox), 0);\n\n\tuiBoxAppend(hbox, uiControl(uiNewLabel(\"Size\")), 0);\n\twidth = uiNewSpinbox(INT_MIN, INT_MAX);\n\tuiBoxAppend(hbox, uiControl(width), 1);\n\theight = uiNewSpinbox(INT_MIN, INT_MAX);\n\tuiBoxAppend(hbox, uiControl(height), 1);\n\tfullscreen = uiNewCheckbox(\"Fullscreen\");\n\tuiBoxAppend(hbox, uiControl(fullscreen), 0);\n\n\tuiSpinboxOnChanged(width, sizeWidth, w);\n\tuiSpinboxOnChanged(height, sizeHeight, w);\n\tuiCheckboxOnToggled(fullscreen, setFullscreen, w);\n\tuiWindowOnContentSizeChanged(w, onSize, NULL);\n\tupdatesize(w);\n\n\tcheckbox = uiNewCheckbox(\"Borderless\");\n\tuiCheckboxOnToggled(checkbox, borderless, w);\n\tuiBoxAppend(page15, uiControl(checkbox), 0);\n\n\tbutton = uiNewButton(\"Borderless Resizes\");\n\tuiButtonOnClicked(button, borderWindowOpen, NULL);\n\tuiBoxAppend(page15, uiControl(button), 0);\n\n\thbox = newHorizontalBox();\n\tuiBoxAppend(page15, uiControl(hbox), 1);\n\n\tuiBoxAppend(hbox, uiControl(uiNewVerticalSeparator()), 0);\n\n\treturn page15;\n}\n"
  },
  {
    "path": "test/page16.c",
    "content": "// 21 june 2016\n#include \"test.h\"\n\nstatic uiTableModelHandler mh;\n\nstatic int modelNumColumns(uiTableModelHandler *mh, uiTableModel *m)\n{\n\treturn 9;\n}\n\nstatic uiTableValueType modelColumnType(uiTableModelHandler *mh, uiTableModel *m, int column)\n{\n\tif (column == 3 || column == 4)\n\t\treturn uiTableValueTypeColor;\n\tif (column == 5)\n\t\treturn uiTableValueTypeImage;\n\tif (column == 7 || column == 8)\n\t\treturn uiTableValueTypeInt;\n\treturn uiTableValueTypeString;\n}\n\nstatic int modelNumRows(uiTableModelHandler *mh, uiTableModel *m)\n{\n\treturn 15;\n}\n\nstatic uiImage *img[2];\nstatic char row9text[1024];\nstatic int yellowRow = -1;\nstatic int checkStates[15];\n\nstatic uiTableValue *modelCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, int col)\n{\n\tchar buf[256];\n\n\tif (col == 3) {\n\t\tif (row == yellowRow)\n\t\t\treturn uiNewTableValueColor(1, 1, 0, 1);\n\t\tif (row == 3)\n\t\t\treturn uiNewTableValueColor(1, 0, 0, 1);\n\t\tif (row == 11)\n\t\t\treturn uiNewTableValueColor(0, 0.5, 1, 0.5);\n\t\treturn NULL;\n\t}\n\tif (col == 4) {\n\t\tif ((row % 2) == 1)\n\t\t\treturn uiNewTableValueColor(0.5, 0, 0.75, 1);\n\t\treturn NULL;\n\t}\n\tif (col == 5) {\n\t\tif (row < 8)\n\t\t\treturn uiNewTableValueImage(img[0]);\n\t\treturn uiNewTableValueImage(img[1]);\n\t}\n\tif (col == 7)\n\t\treturn uiNewTableValueInt(checkStates[row]);\n\tif (col == 8) {\n\t\tif (row == 0)\n\t\t\treturn uiNewTableValueInt(0);\n\t\tif (row == 13)\n\t\t\treturn uiNewTableValueInt(100);\n\t\tif (row == 14)\n\t\t\treturn uiNewTableValueInt(-1);\n\t\treturn uiNewTableValueInt(50);\n\t}\n\tswitch (col) {\n\tcase 0:\n\t\tsprintf(buf, \"Row %d\", row);\n\t\tbreak;\n\tcase 2:\n\t\tif (row == 9)\n\t\t\treturn uiNewTableValueString(row9text);\n\t\t// fall through\n\tcase 1:\n\t\tstrcpy(buf, \"Part\");\n\t\tbreak;\n\tcase 6:\n\t\tstrcpy(buf, \"Make Yellow\");\n\t\tbreak;\n\t}\n\treturn uiNewTableValueString(buf);\n}\n\nstatic void modelSetCellValue(uiTableModelHandler *mh, uiTableModel *m, int row, int col, const uiTableValue *val)\n{\n\tif (row == 9 && col == 2)\n\t\tstrcpy(row9text, uiTableValueString(val));\n\tif (col == 6) {\n\t\tint prevYellowRow;\n\n\t\tprevYellowRow = yellowRow;\n\t\tyellowRow = row;\n\t\tif (prevYellowRow != -1)\n\t\t\tuiTableModelRowChanged(m, prevYellowRow);\n\t\tuiTableModelRowChanged(m, yellowRow);\n\t}\n\tif (col == 7)\n\t\tcheckStates[row] = uiTableValueInt(val);\n}\n\nstatic uiTableModel *m;\n\nuiBox *makePage16(void)\n{\n\tuiBox *page16;\n\tuiTable *t;\n\tuiTableParams p;\n\tuiTableTextColumnOptionalParams tp;\n\n\timg[0] = uiNewImage(16, 16);\n\tappendImageNamed(img[0], \"andlabs_16x16test_24june2016.png\");\n\tappendImageNamed(img[0], \"andlabs_32x32test_24june2016.png\");\n\timg[1] = uiNewImage(16, 16);\n\tappendImageNamed(img[1], \"tango-icon-theme-0.8.90_16x16_x-office-spreadsheet.png\");\n\tappendImageNamed(img[1], \"tango-icon-theme-0.8.90_32x32_x-office-spreadsheet.png\");\n\n\tstrcpy(row9text, \"Part\");\n\n\tmemset(checkStates, 0, 15 * sizeof (int));\n\n\tpage16 = newVerticalBox();\n\n\tmh.NumColumns = modelNumColumns;\n\tmh.ColumnType = modelColumnType;\n\tmh.NumRows = modelNumRows;\n\tmh.CellValue = modelCellValue;\n\tmh.SetCellValue = modelSetCellValue;\n\tm = uiNewTableModel(&mh);\n\n\tmemset(&p, 0, sizeof (uiTableParams));\n\tp.Model = m;\n\tp.RowBackgroundColorModelColumn = 3;\n\tt = uiNewTable(&p);\n\tuiBoxAppend(page16, uiControl(t), 1);\n\n\tuiTableAppendTextColumn(t, \"Column 1\",\n\t\t0, uiTableModelColumnNeverEditable, NULL);\n\n\tmemset(&tp, 0, sizeof (uiTableTextColumnOptionalParams));\n\ttp.ColorModelColumn = 4;\n\tuiTableAppendImageTextColumn(t, \"Column 2\",\n\t\t5,\n\t\t1, uiTableModelColumnNeverEditable, &tp);\n\tuiTableAppendTextColumn(t, \"Editable\",\n\t\t2, uiTableModelColumnAlwaysEditable, NULL);\n\n\tuiTableAppendCheckboxColumn(t, \"Checkboxes\",\n\t\t7, uiTableModelColumnAlwaysEditable);\n\tuiTableAppendButtonColumn(t, \"Buttons\",\n\t\t6, uiTableModelColumnAlwaysEditable);\n\n\tuiTableAppendProgressBarColumn(t, \"Progress Bar\",\n\t\t8);\n\n\treturn page16;\n}\n\nvoid freePage16(void)\n{\n\tuiFreeTableModel(m);\n\tuiFreeImage(img[1]);\n\tuiFreeImage(img[0]);\n}\n"
  },
  {
    "path": "test/page2.c",
    "content": "// 29 april 2015\n#include \"test.h\"\n\nuiGroup *page2group;\n\nstatic uiLabel *movingLabel;\nstatic uiBox *movingBoxes[2];\nstatic int movingCurrent;\n\nstatic void moveLabel(uiButton *b, void *data)\n{\n\tint from, to;\n\n\tfrom = movingCurrent;\n\tto = 0;\n\tif (from == 0)\n\t\tto = 1;\n\tuiBoxDelete(movingBoxes[from], 0);\n\tuiBoxAppend(movingBoxes[to], uiControl(movingLabel), 0);\n\tmovingCurrent = to;\n}\n\nstatic int moveBack;\n#define moveOutText \"Move Page 1 Out\"\n#define moveBackText \"Move Page 1 Back\"\n\nstatic void movePage1(uiButton *b, void *data)\n{\n\tif (moveBack) {\n\t\tuiBoxDelete(mainBox, 1);\n\t\tuiTabInsertAt(mainTab, \"Page 1\", 0, uiControl(page1));\n\t\tuiButtonSetText(b, moveOutText);\n\t\tmoveBack = 0;\n\t\treturn;\n\t}\n\tuiTabDelete(mainTab, 0);\n\tuiBoxAppend(mainBox, uiControl(page1), 1);\n\tuiButtonSetText(b, moveBackText);\n\tmoveBack = 1;\n}\n\nstatic void openAnotherWindow(uiButton *bb, void *data)\n{\n\tuiWindow *w;\n\tuiBox *b;\n\n\tw = uiNewWindow(\"Another Window\", 100, 100, data != NULL);\n\tif (data != NULL) {\n\t\tb = uiNewVerticalBox();\n\t\tuiBoxAppend(b, uiControl(uiNewEntry()), 0);\n\t\tuiBoxAppend(b, uiControl(uiNewButton(\"Button\")), 0);\n\t\tuiBoxSetPadded(b, 1);\n\t\tuiWindowSetChild(w, uiControl(b));\n\t} else\n\t\tuiWindowSetChild(w, uiControl(makePage6()));\n\tuiWindowSetMargined(w, 1);\n\tuiControlShow(uiControl(w));\n}\n\nstatic void openAnotherDisabledWindow(uiButton *b, void *data)\n{\n\tuiWindow *w;\n\n\tw = uiNewWindow(\"Another Window\", 100, 100, data != NULL);\n\tuiControlDisable(uiControl(w));\n\tuiControlShow(uiControl(w));\n}\n\n#define SHED(method, Method) \\\n\tstatic void method ## Control(uiButton *b, void *data) \\\n\t{ \\\n\t\tuiControl ## Method(uiControl(data)); \\\n\t}\nSHED(show, Show)\nSHED(enable, Enable)\nSHED(disable, Disable)\n\nstatic void echoReadOnlyText(uiEntry *e, void *data)\n{\n\tchar *text;\n\n\ttext = uiEntryText(e);\n\tuiEntrySetText(uiEntry(data), text);\n\tuiFreeText(text);\n}\n\nuiBox *makePage2(void)\n{\n\tuiBox *page2;\n\tuiBox *hbox;\n\tuiGroup *group;\n\tuiBox *vbox;\n\tuiButton *button;\n\tuiBox *nestedBox;\n\tuiBox *innerhbox;\n\tuiBox *innerhbox2;\n\tuiBox *innerhbox3;\n\tuiTab *disabledTab;\n\tuiEntry *entry;\n\tuiEntry *readonly;\n\tuiButton *button2;\n\n\tpage2 = newVerticalBox();\n\n\tgroup = newGroup(\"Moving Label\");\n\tpage2group = group;\n\tuiBoxAppend(page2, uiControl(group), 0);\n\tvbox = newVerticalBox();\n\tuiGroupSetChild(group, uiControl(vbox));\n\n\thbox = newHorizontalBox();\n\tbutton = uiNewButton(\"Move the Label!\");\n\tuiButtonOnClicked(button, moveLabel, NULL);\n\tuiBoxAppend(hbox, uiControl(button), 1);\n\t// have a blank label for space\n\tuiBoxAppend(hbox, uiControl(uiNewLabel(\"\")), 1);\n\tuiBoxAppend(vbox, uiControl(hbox), 0);\n\n\thbox = newHorizontalBox();\n\tmovingBoxes[0] = newVerticalBox();\n\tuiBoxAppend(hbox, uiControl(movingBoxes[0]), 1);\n\tmovingBoxes[1] = newVerticalBox();\n\tuiBoxAppend(hbox, uiControl(movingBoxes[1]), 1);\n\tuiBoxAppend(vbox, uiControl(hbox), 0);\n\n\tmovingCurrent = 0;\n\tmovingLabel = uiNewLabel(\"This label moves!\");\n\tuiBoxAppend(movingBoxes[movingCurrent], uiControl(movingLabel), 0);\n\n\thbox = newHorizontalBox();\n\tbutton = uiNewButton(moveOutText);\n\tuiButtonOnClicked(button, movePage1, NULL);\n\tuiBoxAppend(hbox, uiControl(button), 0);\n\tuiBoxAppend(page2, uiControl(hbox), 0);\n\tmoveBack = 0;\n\n\thbox = newHorizontalBox();\n\tuiBoxAppend(hbox, uiControl(uiNewLabel(\"Label Alignment Test\")), 0);\n\tbutton = uiNewButton(\"Open Menued Window\");\n\tuiButtonOnClicked(button, openAnotherWindow, button);\n\tuiBoxAppend(hbox, uiControl(button), 0);\n\tbutton = uiNewButton(\"Open Menuless Window\");\n\tuiButtonOnClicked(button, openAnotherWindow, NULL);\n\tuiBoxAppend(hbox, uiControl(button), 0);\n\tbutton = uiNewButton(\"Disabled Menued\");\n\tuiButtonOnClicked(button, openAnotherDisabledWindow, button);\n\tuiBoxAppend(hbox, uiControl(button), 0);\n\tbutton = uiNewButton(\"Disabled Menuless\");\n\tuiButtonOnClicked(button, openAnotherDisabledWindow, NULL);\n\tuiBoxAppend(hbox, uiControl(button), 0);\n\tuiBoxAppend(page2, uiControl(hbox), 0);\n\n\tnestedBox = newHorizontalBox();\n\tinnerhbox = newHorizontalBox();\n\tuiBoxAppend(innerhbox, uiControl(uiNewButton(\"These\")), 0);\n\tbutton = uiNewButton(\"buttons\");\n\tuiControlDisable(uiControl(button));\n\tuiBoxAppend(innerhbox, uiControl(button), 0);\n\tuiBoxAppend(nestedBox, uiControl(innerhbox), 0);\n\tinnerhbox = newHorizontalBox();\n\tuiBoxAppend(innerhbox, uiControl(uiNewButton(\"are\")), 0);\n\tinnerhbox2 = newHorizontalBox();\n\tbutton = uiNewButton(\"in\");\n\tuiControlDisable(uiControl(button));\n\tuiBoxAppend(innerhbox2, uiControl(button), 0);\n\tuiBoxAppend(innerhbox, uiControl(innerhbox2), 0);\n\tuiBoxAppend(nestedBox, uiControl(innerhbox), 0);\n\tinnerhbox = newHorizontalBox();\n\tinnerhbox2 = newHorizontalBox();\n\tuiBoxAppend(innerhbox2, uiControl(uiNewButton(\"nested\")), 0);\n\tinnerhbox3 = newHorizontalBox();\n\tbutton = uiNewButton(\"boxes\");\n\tuiControlDisable(uiControl(button));\n\tuiBoxAppend(innerhbox3, uiControl(button), 0);\n\tuiBoxAppend(innerhbox2, uiControl(innerhbox3), 0);\n\tuiBoxAppend(innerhbox, uiControl(innerhbox2), 0);\n\tuiBoxAppend(nestedBox, uiControl(innerhbox), 0);\n\tuiBoxAppend(page2, uiControl(nestedBox), 0);\n\n\thbox = newHorizontalBox();\n\tbutton = uiNewButton(\"Enable Nested Box\");\n\tuiButtonOnClicked(button, enableControl, nestedBox);\n\tuiBoxAppend(hbox, uiControl(button), 0);\n\tbutton = uiNewButton(\"Disable Nested Box\");\n\tuiButtonOnClicked(button, disableControl, nestedBox);\n\tuiBoxAppend(hbox, uiControl(button), 0);\n\tuiBoxAppend(page2, uiControl(hbox), 0);\n\n\tdisabledTab = newTab();\n\tuiTabAppend(disabledTab, \"Disabled\", uiControl(uiNewButton(\"Button\")));\n\tuiTabAppend(disabledTab, \"Tab\", uiControl(uiNewLabel(\"Label\")));\n\tuiControlDisable(uiControl(disabledTab));\n\tuiBoxAppend(page2, uiControl(disabledTab), 1);\n\n\tentry = uiNewEntry();\n\treadonly = uiNewEntry();\n\tuiEntryOnChanged(entry, echoReadOnlyText, readonly);\n\tuiEntrySetText(readonly, \"If you can see this, uiEntryReadOnly() isn't working properly.\");\n\tuiEntrySetReadOnly(readonly, 1);\n\tif (uiEntryReadOnly(readonly))\n\t\tuiEntrySetText(readonly, \"\");\n\tuiBoxAppend(page2, uiControl(entry), 0);\n\tuiBoxAppend(page2, uiControl(readonly), 0);\n\n\thbox = newHorizontalBox();\n\tbutton = uiNewButton(\"Show Button 2\");\n\tbutton2 = uiNewButton(\"Button 2\");\n\tuiButtonOnClicked(button, showControl, button2);\n\tuiControlHide(uiControl(button2));\n\tuiBoxAppend(hbox, uiControl(button), 1);\n\tuiBoxAppend(hbox, uiControl(button2), 0);\n\tuiBoxAppend(page2, uiControl(hbox), 0);\n\n\treturn page2;\n}\n"
  },
  {
    "path": "test/page3.c",
    "content": "// 7 may 2015\n#include \"test.h\"\n\nstatic uiBox *makeSet(int omit, int hidden, int stretch)\n{\n\tuiBox *hbox;\n\tuiButton *buttons[4];\n\n\t// don't use newHorizontalBox()\n\t// the point of this test is to test hidden controls and padded\n\thbox = (*newhbox)();\n\tuiBoxSetPadded(hbox, 1);\n\tif (omit != 0) {\n\t\tbuttons[0] = uiNewButton(\"First\");\n\t\tuiBoxAppend(hbox, uiControl(buttons[0]), stretch);\n\t}\n\tif (omit != 1) {\n\t\tbuttons[1] = uiNewButton(\"Second\");\n\t\tuiBoxAppend(hbox, uiControl(buttons[1]), stretch);\n\t}\n\tif (omit != 2) {\n\t\tbuttons[2] = uiNewButton(\"Third\");\n\t\tuiBoxAppend(hbox, uiControl(buttons[2]), stretch);\n\t}\n\tif (omit != 3) {\n\t\tbuttons[3] = uiNewButton(\"Fourth\");\n\t\tuiBoxAppend(hbox, uiControl(buttons[3]), stretch);\n\t}\n\tif (hidden != -1)\n\t\tuiControlHide(uiControl(buttons[hidden]));\n\treturn hbox;\n}\n\nuiBox *makePage3(void)\n{\n\tuiBox *page3;\n\tuiBox *hbox;\n\tuiBox *hbox2;\n\tuiBox *vbox;\n\tint hidden;\n\n\tpage3 = newVerticalBox();\n\n\t// first the non-stretchy type\n\tfor (hidden = 0; hidden < 4; hidden++) {\n\t\t// these two must stay unpadded as well, otherwise the test isn't meaningful\n\t\thbox2 = (*newhbox)();\n\t\tvbox = (*newvbox)();\n\t\t// reference set\n\t\thbox = makeSet(hidden, -1, 0);\n\t\tuiBoxAppend(vbox, uiControl(hbox), 0);\n\t\t// real thing\n\t\thbox = makeSet(-1, hidden, 0);\n\t\tuiBoxAppend(vbox, uiControl(hbox), 0);\n\t\t// pack vbox in\n\t\tuiBoxAppend(hbox2, uiControl(vbox), 0);\n\t\t// and have a button in there for showing right margins\n\t\tuiBoxAppend(hbox2, uiControl(uiNewButton(\"Right Margin Test\")), 1);\n\t\tuiBoxAppend(page3, uiControl(hbox2), 0);\n\t}\n\n\t// then the stretchy type\n\tfor (hidden = 0; hidden < 4; hidden++) {\n\t\thbox = makeSet(-1, hidden, 1);\n\t\tuiBoxAppend(page3, uiControl(hbox), 0);\n\t}\n\n\treturn page3;\n}\n"
  },
  {
    "path": "test/page4.c",
    "content": "// 19 may 2015\n#include \"test.h\"\n\nstatic uiSpinbox *spinbox;\nstatic uiSlider *slider;\nstatic uiProgressBar *pbar;\n\n#define CHANGED(what) \\\n\tstatic void on ## what ## Changed(ui ## what *this, void *data) \\\n\t{ \\\n\t\tint value; \\\n\t\tprintf(\"on %s changed\\n\", #what); \\\n\t\tvalue = ui ## what ## Value(this); \\\n\t\tuiSpinboxSetValue(spinbox, value); \\\n\t\tuiSliderSetValue(slider, value); \\\n\t\tuiProgressBarSetValue(pbar, value); \\\n\t}\nCHANGED(Spinbox)\nCHANGED(Slider)\n\n#define SETTOO(what, name, n) \\\n\tstatic void set ## what ## Too ## name(uiButton *this, void *data) \\\n\t{ \\\n\t\tui ## what ## SetValue(ui ## what(data), n); \\\n\t}\nSETTOO(Spinbox, Low, -80)\nSETTOO(Spinbox, High, 80)\nSETTOO(Slider, Low, -80)\nSETTOO(Slider, High, 80)\n\nstatic uiCombobox *cbox;\nstatic uiEditableCombobox *editable;\nstatic uiRadioButtons *rb;\n\nstatic void appendCBRB(uiButton *b, void *data)\n{\n\tuiComboboxAppend(cbox, \"New Item\");\n\tuiEditableComboboxAppend(editable, \"New Item\");\n\tuiRadioButtonsAppend(rb, \"New Item\");\n}\n\nstatic void onCBChanged(uiCombobox *c, void *data)\n{\n\tprintf(\"%s combobox changed to %d\\n\",\n\t\t(char *) data,\n\t\t(int) uiComboboxSelected(c));\n\tuiEditableComboboxSetText(editable, \"changed\");\n}\n\nstatic void onECBChanged(uiEditableCombobox *c, void *data)\n{\n\tchar *t;\n\n\tt = uiEditableComboboxText(c);\n\tprintf(\"%s combobox changed to %s\\n\",\n\t\t(char *) data,\n\t\tt);\n\tuiFreeText(t);\n}\n\nstatic void onRBSelected(uiRadioButtons *r, void *data)\n{\n\tprintf(\"radio buttons %d\\n\", uiRadioButtonsSelected(r));\n}\n\nstatic void selectSecond(uiButton *b, void *data)\n{\n\t// TODO combobox, editable\n\tuiRadioButtonsSetSelected(rb, 1);\n}\n\nstatic void selectNone(uiButton *b, void *data)\n{\n\t// TODO combobox, editable\n\tuiRadioButtonsSetSelected(rb, -1);\n}\n\nuiBox *makePage4(void)\n{\n\tuiBox *page4;\n\tuiBox *hbox;\n\tuiSpinbox *xsb;\n\tuiButton *b;\n\tuiSlider *xsl;\n\n\tpage4 = newVerticalBox();\n\n\tspinbox = uiNewSpinbox(0, 100);\n\tuiSpinboxOnChanged(spinbox, onSpinboxChanged, NULL);\n\tuiBoxAppend(page4, uiControl(spinbox), 0);\n\n\tslider = uiNewSlider(0, 100);\n\tuiSliderOnChanged(slider, onSliderChanged, NULL);\n\tuiBoxAppend(page4, uiControl(slider), 0);\n\n\tpbar = uiNewProgressBar();\n\tuiBoxAppend(page4, uiControl(pbar), 0);\n\n\tuiBoxAppend(page4, uiControl(uiNewHorizontalSeparator()), 0);\n\n\thbox = newHorizontalBox();\n\txsb = uiNewSpinbox(-40, 40);\n\tuiBoxAppend(hbox, uiControl(xsb), 0);\n\tb = uiNewButton(\"Bad Low\");\n\tuiButtonOnClicked(b, setSpinboxTooLow, xsb);\n\tuiBoxAppend(hbox, uiControl(b), 0);\n\tb = uiNewButton(\"Bad High\");\n\tuiButtonOnClicked(b, setSpinboxTooHigh, xsb);\n\tuiBoxAppend(hbox, uiControl(b), 0);\n\tuiBoxAppend(page4, uiControl(hbox), 0);\n\n\thbox = newHorizontalBox();\n\txsl = uiNewSlider(-40, 40);\n\tuiBoxAppend(hbox, uiControl(xsl), 0);\n\tb = uiNewButton(\"Bad Low\");\n\tuiButtonOnClicked(b, setSliderTooLow, xsl);\n\tuiBoxAppend(hbox, uiControl(b), 0);\n\tb = uiNewButton(\"Bad High\");\n\tuiButtonOnClicked(b, setSliderTooHigh, xsl);\n\tuiBoxAppend(hbox, uiControl(b), 0);\n\tuiBoxAppend(page4, uiControl(hbox), 0);\n\n\tuiBoxAppend(page4, uiControl(uiNewHorizontalSeparator()), 0);\n\n\tcbox = uiNewCombobox();\n\tuiComboboxAppend(cbox, \"Item 1\");\n\tuiComboboxAppend(cbox, \"Item 2\");\n\tuiComboboxAppend(cbox, \"Item 3\");\n\tuiComboboxOnSelected(cbox, onCBChanged, \"noneditable\");\n\tuiBoxAppend(page4, uiControl(cbox), 0);\n\n\teditable = uiNewEditableCombobox();\n\tuiEditableComboboxAppend(editable, \"Editable Item 1\");\n\tuiEditableComboboxAppend(editable, \"Editable Item 2\");\n\tuiEditableComboboxAppend(editable, \"Editable Item 3\");\n\tuiEditableComboboxOnChanged(editable, onECBChanged, \"editable\");\n\tuiBoxAppend(page4, uiControl(editable), 0);\n\n\trb = uiNewRadioButtons();\n\tuiRadioButtonsAppend(rb, \"Item 1\");\n\tuiRadioButtonsAppend(rb, \"Item 2\");\n\tuiRadioButtonsAppend(rb, \"Item 3\");\n\tuiRadioButtonsOnSelected(rb, onRBSelected, NULL);\n\tuiBoxAppend(page4, uiControl(rb), 0);\n\n\thbox = newHorizontalBox();\n\tb = uiNewButton(\"Append\");\n\tuiButtonOnClicked(b, appendCBRB, NULL);\n\tuiBoxAppend(hbox, uiControl(b), 0);\n\tb = uiNewButton(\"Second\");\n\tuiButtonOnClicked(b, selectSecond, NULL);\n\tuiBoxAppend(hbox, uiControl(b), 0);\n\tb = uiNewButton(\"None\");\n\tuiButtonOnClicked(b, selectNone, NULL);\n\tuiBoxAppend(hbox, uiControl(b), 0);\n\tuiBoxAppend(page4, uiControl(hbox), 0);\n\n\tuiBoxAppend(page4, uiControl(uiNewHorizontalSeparator()), 0);\n\n\tuiBoxAppend(page4, uiControl(uiNewDateTimePicker()), 0);\n\tuiBoxAppend(page4, uiControl(uiNewDatePicker()), 0);\n\tuiBoxAppend(page4, uiControl(uiNewTimePicker()), 0);\n\n\treturn page4;\n}\n"
  },
  {
    "path": "test/page5.c",
    "content": "// 22 may 2015\n#include \"test.h\"\n\nstatic uiWindow *parent;\n\nstatic void openFile(uiButton *b, void *data)\n{\n\tchar *fn;\n\n\tfn = uiOpenFile(parent);\n\tif (fn == NULL)\n\t\tuiLabelSetText(uiLabel(data), \"(cancelled)\");\n\telse {\n\t\tuiLabelSetText(uiLabel(data), fn);\n\t\tuiFreeText(fn);\n\t}\n}\n\nstatic void saveFile(uiButton *b, void *data)\n{\n\tchar *fn;\n\n\tfn = uiSaveFile(parent);\n\tif (fn == NULL)\n\t\tuiLabelSetText(uiLabel(data), \"(cancelled)\");\n\telse {\n\t\tuiLabelSetText(uiLabel(data), fn);\n\t\tuiFreeText(fn);\n\t}\n}\n\nstatic uiEntry *title, *description;\n\nstatic void msgBox(uiButton *b, void *data)\n{\n\tchar *t, *d;\n\n\tt = uiEntryText(title);\n\td = uiEntryText(description);\n\tuiMsgBox(parent, t, d);\n\tuiFreeText(d);\n\tuiFreeText(t);\n}\n\nstatic void msgBoxError(uiButton *b, void *data)\n{\n\tchar *t, *d;\n\n\tt = uiEntryText(title);\n\td = uiEntryText(description);\n\tuiMsgBoxError(parent, t, d);\n\tuiFreeText(d);\n\tuiFreeText(t);\n}\n\nuiBox *makePage5(uiWindow *pw)\n{\n\tuiBox *page5;\n\tuiBox *hbox;\n\tuiButton *button;\n\tuiLabel *label;\n\n\tparent = pw;\n\n\tpage5 = newVerticalBox();\n\n#define D(n, f) \\\n\thbox = newHorizontalBox(); \\\n\tbutton = uiNewButton(n); \\\n\tlabel = uiNewLabel(\"\"); \\\n\tuiButtonOnClicked(button, f, label); \\\n\tuiBoxAppend(hbox, uiControl(button), 0); \\\n\tuiBoxAppend(hbox, uiControl(label), 0); \\\n\tuiBoxAppend(page5, uiControl(hbox), 0);\n\n\tD(\"Open File\", openFile);\n\tD(\"Save File\", saveFile);\n\n\ttitle = uiNewEntry();\n\tuiEntrySetText(title, \"Title\");\n\tdescription = uiNewEntry();\n\tuiEntrySetText(description, \"Description\");\n\n\thbox = newHorizontalBox();\n\tbutton = uiNewButton(\"Message Box\");\n\tuiButtonOnClicked(button, msgBox, NULL);\n\tuiBoxAppend(hbox, uiControl(button), 0);\n\tuiBoxAppend(hbox, uiControl(title), 0);\n\tuiBoxAppend(page5, uiControl(hbox), 0);\n\n\thbox = newHorizontalBox();\n\tbutton = uiNewButton(\"Error Box\");\n\tuiButtonOnClicked(button, msgBoxError, NULL);\n\tuiBoxAppend(hbox, uiControl(button), 0);\n\tuiBoxAppend(hbox, uiControl(description), 0);\n\tuiBoxAppend(page5, uiControl(hbox), 0);\n\n\treturn page5;\n}\n"
  },
  {
    "path": "test/page6.c",
    "content": "// 8 october 2015\n#include <inttypes.h>\n#include \"test.h\"\n\nstatic uiArea *area;\nstatic uiCombobox *which;\nstatic uiCheckbox *swallowKeys;\n\nstruct handler {\n\tuiAreaHandler ah;\n};\n\nstatic struct handler handler;\n\nstatic void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *p)\n{\n\trunDrawTest(uiComboboxSelected(which), p);\n}\n\nstatic void handlerMouseEvent(uiAreaHandler *a, uiArea *area, uiAreaMouseEvent *e)\n{\n\tprintf(\"mouse (%g,%g):(%g,%g) down:%d up:%d count:%d mods:%x held:0x%\" PRIX64 \"\\n\",\n\t\te->X,\n\t\te->Y,\n\t\te->AreaWidth,\n\t\te->AreaHeight,\n\t\t(int) e->Down,\n\t\t(int) e->Up,\n\t\t(int) e->Count,\n\t\t(uint32_t) e->Modifiers,\n\t\te->Held1To64);\n}\n\nstatic void handlerMouseCrossed(uiAreaHandler *ah, uiArea *a, int left)\n{\n\tprintf(\"mouse crossed %d\\n\", left);\n}\n\nstatic void handlerDragBroken(uiAreaHandler *ah, uiArea *a)\n{\n\tprintf(\"drag broken\\n\");\n}\n\nstatic int handlerKeyEvent(uiAreaHandler *ah, uiArea *a, uiAreaKeyEvent *e)\n{\n\tchar k[4];\n\n\tk[0] = '\\'';\n\tk[1] = e->Key;\n\tk[2] = '\\'';\n\tk[3] = '\\0';\n\tif (e->Key == 0) {\n\t\tk[0] = '0';\n\t\tk[1] = '\\0';\n\t}\n\tprintf(\"key key:%s extkey:%d mod:%d mods:%d up:%d\\n\",\n\t\tk,\n\t\t(int) e->ExtKey,\n\t\t(int) e->Modifier,\n\t\t(int) e->Modifiers,\n\t\te->Up);\n\treturn uiCheckboxChecked(swallowKeys);\n}\n\nstatic void shouldntHappen(uiCombobox *c, void *data)\n{\n\tfprintf(stderr, \"YOU SHOULD NOT SEE THIS. If you do, uiComboboxSetSelected() is triggering uiComboboxOnSelected(), which it should not.\\n\");\n}\n\nstatic void redraw(uiCombobox *c, void *data)\n{\n\tuiAreaQueueRedrawAll(area);\n}\n\nstatic void enableArea(uiButton *b, void *data)\n{\n\tif (data != NULL)\n\t\tuiControlEnable(uiControl(area));\n\telse\n\t\tuiControlDisable(uiControl(area));\n}\n\nuiBox *makePage6(void)\n{\n\tuiBox *page6;\n\tuiBox *hbox;\n\tuiButton *button;\n\n\thandler.ah.Draw = handlerDraw;\n\thandler.ah.MouseEvent = handlerMouseEvent;\n\thandler.ah.MouseCrossed = handlerMouseCrossed;\n\thandler.ah.DragBroken = handlerDragBroken;\n\thandler.ah.KeyEvent = handlerKeyEvent;\n\n\tpage6 = newVerticalBox();\n\n\thbox = newHorizontalBox();\n\tuiBoxAppend(page6, uiControl(hbox), 0);\n\n\twhich = uiNewCombobox();\n\tpopulateComboboxWithTests(which);\n\t// this is to make sure that uiComboboxOnSelected() doesn't trigger with uiComboboxSetSelected()\n\tuiComboboxOnSelected(which, shouldntHappen, NULL);\n\tuiComboboxSetSelected(which, 0);\n\tuiComboboxOnSelected(which, redraw, NULL);\n\tuiBoxAppend(hbox, uiControl(which), 0);\n\n\tarea = uiNewArea((uiAreaHandler *) (&handler));\n\tuiBoxAppend(page6, uiControl(area), 1);\n\n\thbox = newHorizontalBox();\n\tuiBoxAppend(page6, uiControl(hbox), 0);\n\n\tswallowKeys = uiNewCheckbox(\"Consider key events handled\");\n\tuiBoxAppend(hbox, uiControl(swallowKeys), 1);\n\n\tbutton = uiNewButton(\"Enable\");\n\tuiButtonOnClicked(button, enableArea, button);\n\tuiBoxAppend(hbox, uiControl(button), 0);\n\n\tbutton = uiNewButton(\"Disable\");\n\tuiButtonOnClicked(button, enableArea, NULL);\n\tuiBoxAppend(hbox, uiControl(button), 0);\n\n\treturn page6;\n}\n"
  },
  {
    "path": "test/page7.c",
    "content": "// 13 october 2015\n#include \"test.h\"\n\nuiBox *makePage7(void)\n{\n\tuiBox *page7;\n\tuiGroup *group;\n\tuiBox *box2;\n\n\tpage7 = newHorizontalBox();\n\n\tgroup = makePage7a();\n\tuiBoxAppend(page7, uiControl(group), 1);\n\n\tbox2 = newVerticalBox();\n\tuiBoxAppend(page7, uiControl(box2), 1);\n\n\tgroup = makePage7b();\n\tuiBoxAppend(box2, uiControl(group), 1);\n\n\tgroup = makePage7c();\n\tuiBoxAppend(box2, uiControl(group), 1);\n\n\treturn page7;\n}\n"
  },
  {
    "path": "test/page7a.c",
    "content": "// 13 october 2015\n#include \"test.h\"\n\nstatic uiArea *area;\nstatic uiEntry *startAngle;\nstatic uiEntry *sweep;\nstatic uiCheckbox *negative;\nstatic uiCheckbox *radians;\n\nstruct handler {\n\tuiAreaHandler ah;\n};\n\nstatic struct handler handler;\n\n// based on the cairo arc sample\nstatic void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *p)\n{\n\tdouble xc = 128.0;\n\tdouble yc = 128.0;\n\tdouble radius = 100.0;\n\tuiDrawBrush source;\n\tuiDrawStrokeParams sp;\n\tuiDrawPath *path;\n\tchar *startText;\n\tchar *sweepText;\n\tdouble factor;\n\n\tsource.Type = uiDrawBrushTypeSolid;\n\tsource.R = 0;\n\tsource.G = 0;\n\tsource.B = 0;\n\tsource.A = 1;\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\tsp.Dashes = NULL;\n\tsp.NumDashes = 0;\n\tsp.DashPhase = 0;\n\n\tstartText = uiEntryText(startAngle);\n\tsweepText = uiEntryText(sweep);\n\n\tfactor = uiPi / 180;\n\tif (uiCheckboxChecked(radians))\n\t\tfactor = 1;\n\n\tsp.Thickness = 10.0;\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigure(path, xc, yc);\n\tuiDrawPathArcTo(path,\n\t\txc, yc,\n\t\tradius,\n\t\tatof(startText) * factor,\n\t\tatof(sweepText) * factor,\n\t\tuiCheckboxChecked(negative));\n\tuiDrawPathEnd(path);\n\tuiDrawStroke(p->Context, path, &source, &sp);\n\tuiDrawFreePath(path);\n\n\tuiFreeText(startText);\n\tuiFreeText(sweepText);\n}\n\nstatic void handlerMouseEvent(uiAreaHandler *a, uiArea *area, uiAreaMouseEvent *e)\n{\n\t// do nothing\n}\n\nstatic void handlerMouseCrossed(uiAreaHandler *ah, uiArea *a, int left)\n{\n\t// do nothing\n}\n\nstatic void handlerDragBroken(uiAreaHandler *ah, uiArea *a)\n{\n\t// do nothing\n}\n\nstatic int handlerKeyEvent(uiAreaHandler *ah, uiArea *a, uiAreaKeyEvent *e)\n{\n\treturn 0;\n}\n\nstatic void entryChanged(uiEntry *e, void *data)\n{\n\tuiAreaQueueRedrawAll(area);\n}\n\nstatic void checkboxToggled(uiCheckbox *c, void *data)\n{\n\tuiAreaQueueRedrawAll(area);\n}\n\nuiGroup *makePage7a(void)\n{\n\tuiGroup *group;\n\tuiBox *box, *box2;\n\n\thandler.ah.Draw = handlerDraw;\n\thandler.ah.MouseEvent = handlerMouseEvent;\n\thandler.ah.MouseCrossed = handlerMouseCrossed;\n\thandler.ah.DragBroken = handlerDragBroken;\n\thandler.ah.KeyEvent = handlerKeyEvent;\n\n\tgroup = newGroup(\"Arc Test\");\n\n\tbox = newVerticalBox();\n\tuiGroupSetChild(group, uiControl(box));\n\n\tarea = uiNewArea((uiAreaHandler *) (&handler));\n\tuiBoxAppend(box, uiControl(area), 1);\n\n\tbox2 = newHorizontalBox();\n\tuiBoxAppend(box, uiControl(box2), 0);\n\n\tuiBoxAppend(box2, uiControl(uiNewLabel(\"Start Angle\")), 0);\n\tstartAngle = uiNewEntry();\n\tuiEntryOnChanged(startAngle, entryChanged, NULL);\n\tuiBoxAppend(box2, uiControl(startAngle), 1);\n\n\tbox2 = newHorizontalBox();\n\tuiBoxAppend(box, uiControl(box2), 0);\n\n\tuiBoxAppend(box2, uiControl(uiNewLabel(\"Sweep\")), 0);\n\tsweep = uiNewEntry();\n\tuiEntryOnChanged(sweep, entryChanged, NULL);\n\tuiBoxAppend(box2, uiControl(sweep), 1);\n\n\tnegative = uiNewCheckbox(\"Negative\");\n\tuiCheckboxOnToggled(negative, checkboxToggled, NULL);\n\tuiBoxAppend(box, uiControl(negative), 0);\n\n\tradians = uiNewCheckbox(\"Radians\");\n\tuiCheckboxOnToggled(radians, checkboxToggled, NULL);\n\tuiBoxAppend(box, uiControl(radians), 0);\n\n\treturn group;\n}\n"
  },
  {
    "path": "test/page7b.c",
    "content": "// 13 october 2015\n#include \"test.h\"\n\nstatic uiArea *area;\nstatic uiCheckbox *label;\n\nstruct handler {\n\tuiAreaHandler ah;\n};\n\nstatic struct handler handler;\n\nstatic void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *p)\n{\n\t// do nothing\n}\n\nstatic void handlerMouseEvent(uiAreaHandler *a, uiArea *area, uiAreaMouseEvent *e)\n{\n\tchar pos[128];\n\n\t// wonderful, vanilla snprintf() isn't in visual studio 2013 - http://blogs.msdn.com/b/vcblog/archive/2013/07/19/c99-library-support-in-visual-studio-2013.aspx\n\t// we can't use _snprintf() in the test suite because that's msvc-only, so oops. sprintf() it is.\n\tsprintf(pos, \"X %g Y %g\", e->X, e->Y);\n\tuiCheckboxSetText(label, pos);\n}\n\nstatic void handlerMouseCrossed(uiAreaHandler *ah, uiArea *a, int left)\n{\nprintf(\"%d %d\\n\", left, !left);\n\tuiCheckboxSetChecked(label, !left);\n}\n\nstatic void handlerDragBroken(uiAreaHandler *ah, uiArea *a)\n{\n\t// do nothing\n}\n\nstatic int handlerKeyEvent(uiAreaHandler *ah, uiArea *a, uiAreaKeyEvent *e)\n{\n\tif (e->Key == 'h' && !e->Up) {\n\t\t// TODO hide the widget momentarily on the h key\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nuiGroup *makePage7b(void)\n{\n\tuiGroup *group;\n\tuiBox *box;\n\n\thandler.ah.Draw = handlerDraw;\n\thandler.ah.MouseEvent = handlerMouseEvent;\n\thandler.ah.MouseCrossed = handlerMouseCrossed;\n\thandler.ah.DragBroken = handlerDragBroken;\n\thandler.ah.KeyEvent = handlerKeyEvent;\n\n\tgroup = newGroup(\"Scrolling Mouse Test\");\n\n\tbox = newVerticalBox();\n\tuiGroupSetChild(group, uiControl(box));\n\n\tarea = uiNewScrollingArea((uiAreaHandler *) (&handler), 5000, 5000);\n\tuiBoxAppend(box, uiControl(area), 1);\n\n\tlabel = uiNewCheckbox(\"\");\n\tuiBoxAppend(box, uiControl(label), 0);\n\n\treturn group;\n}\n"
  },
  {
    "path": "test/page7c.c",
    "content": "// 13 october 2015\n#include \"test.h\"\n\nstatic uiArea *area;\n\nstruct handler {\n\tuiAreaHandler ah;\n};\n\nstatic struct handler handler;\n\n#define areaSize 250\n#define borderThickness 1\n#define padding 30\n#define circleRadius ((areaSize - padding) / 2)\n\nstatic void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *dp)\n{\n\tuiDrawPath *path;\n\tuiDrawBrush brush;\n\tuiDrawStrokeParams sp;\n\tuiDrawBrushGradientStop stops[2];\n\n\tmemset(&brush, 0, sizeof (uiDrawBrush));\n\tmemset(&sp, 0, sizeof (uiDrawStrokeParams));\n\n\t// add some buffering to detect scrolls that aren't on the dot and draws that are outside the scroll area on Windows\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathAddRectangle(path,\n\t\t-50, -50,\n\t\tareaSize + 100, areaSize + 100);\n\tuiDrawPathEnd(path);\n\tbrush.Type = uiDrawBrushTypeSolid;\n\tbrush.R = 0;\n\tbrush.G = 1;\n\tbrush.B = 0;\n\tbrush.A = 1;\n\tuiDrawFill(dp->Context, path, &brush);\n\tuiDrawFreePath(path);\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathAddRectangle(path,\n\t\t0, 0,\n\t\tareaSize, areaSize);\n\tuiDrawPathEnd(path);\n\tbrush.Type = uiDrawBrushTypeSolid;\n\tbrush.R = 1;\n\tbrush.G = 1;\n\tbrush.B = 1;\n\tbrush.A = 1;\n\tuiDrawFill(dp->Context, path, &brush);\n\tbrush.Type = uiDrawBrushTypeSolid;\n\tbrush.R = 1;\n\tbrush.G = 0;\n\tbrush.B = 0;\n\tbrush.A = 1;\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.Thickness = 1;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\tuiDrawStroke(dp->Context, path, &brush, &sp);\n\tuiDrawFreePath(path);\n\n\tpath = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigureWithArc(path,\n\t\tareaSize / 2, areaSize / 2,\n\t\tcircleRadius,\n\t\t0, 2 * uiPi,\n\t\t0);\n\tuiDrawPathEnd(path);\n\tstops[0].Pos =0.0;\n\tstops[0].R = 0.0;\n\tstops[0].G = 1.0;\n\tstops[0].B = 1.0;\n\tstops[0].A = 1.0;\n\tstops[1].Pos = 1.0;\n\tstops[1].R = 0.0;\n\tstops[1].G = 0.0;\n\tstops[1].B = 1.0;\n\tstops[1].A = 1.0;\n\tbrush.Type = uiDrawBrushTypeLinearGradient;\n\tbrush.X0 = areaSize / 2;\n\tbrush.Y0 = padding;\n\tbrush.X1 = areaSize / 2;\n\tbrush.Y1 = areaSize - padding;\n\tbrush.Stops = stops;\n\tbrush.NumStops = 2;\n\tuiDrawFill(dp->Context, path, &brush);\n\tuiDrawFreePath(path);\n}\n\nstatic void handlerMouseEvent(uiAreaHandler *a, uiArea *area, uiAreaMouseEvent *e)\n{\n\t// do nothing\n}\n\nstatic void handlerMouseCrossed(uiAreaHandler *ah, uiArea *a, int left)\n{\n\t// do nothing\n}\n\nstatic void handlerDragBroken(uiAreaHandler *ah, uiArea *a)\n{\n\t// do nothing\n}\n\nstatic int handlerKeyEvent(uiAreaHandler *ah, uiArea *a, uiAreaKeyEvent *e)\n{\n\tif (e->Key == 'h' && !e->Up) {\n\t\t// TODO hide the widget momentarily on the h key\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nuiGroup *makePage7c(void)\n{\n\tuiGroup *group;\n\n\thandler.ah.Draw = handlerDraw;\n\thandler.ah.MouseEvent = handlerMouseEvent;\n\thandler.ah.MouseCrossed = handlerMouseCrossed;\n\thandler.ah.DragBroken = handlerDragBroken;\n\thandler.ah.KeyEvent = handlerKeyEvent;\n\n\tgroup = newGroup(\"Scrolling Drawing Test\");\n\n\tarea = uiNewScrollingArea((uiAreaHandler *) (&handler),\n\t\tareaSize, areaSize);\n\tuiGroupSetChild(group, uiControl(area));\n\n\treturn group;\n}\n"
  },
  {
    "path": "test/page8.c",
    "content": "// 22 december 2015\n#include \"test.h\"\n\nstatic void onListFonts(uiButton *b, void *data)\n{\n\tuiDrawFontFamilies *ff;\n\tchar *this;\n\tint i, n;\n\n\tuiMultilineEntrySetText(uiMultilineEntry(data), \"\");\n\tff = uiDrawListFontFamilies();\n\tn = uiDrawFontFamiliesNumFamilies(ff);\n\tfor (i = 0; i < n; i++) {\n\t\tthis = uiDrawFontFamiliesFamily(ff, i);\n\t\tuiMultilineEntryAppend(uiMultilineEntry(data), this);\n\t\tuiMultilineEntryAppend(uiMultilineEntry(data), \"\\n\");\n\t\tuiFreeText(this);\n\t}\n\tuiDrawFreeFontFamilies(ff);\n}\n\nuiBox *makePage8(void)\n{\n\tuiBox *page8;\n\tuiGroup *group;\n\tuiBox *vbox;\n\tuiMultilineEntry *me;\n\tuiButton *button;\n\n\tpage8 = newHorizontalBox();\n\n\tgroup = newGroup(\"Font Families\");\n\tuiBoxAppend(page8, uiControl(group), 1);\n\n\tvbox = newVerticalBox();\n\tuiGroupSetChild(group, uiControl(vbox));\n\n\tme = uiNewMultilineEntry();\n\tuiBoxAppend(vbox, uiControl(me), 1);\n\n\tbutton = uiNewButton(\"List Font Families\");\n\tuiButtonOnClicked(button, onListFonts, me);\n\tuiBoxAppend(vbox, uiControl(button), 0);\n\n\treturn page8;\n}\n"
  },
  {
    "path": "test/page9.c",
    "content": "// 22 december 2015\n#include \"test.h\"\n\nstatic uiEntry *textString;\nstatic uiEntry *textFont;\nstatic uiEntry *textSize;\nstatic uiCombobox *textWeight;\nstatic uiCombobox *textItalic;\nstatic uiCheckbox *textSmallCaps;\nstatic uiCombobox *textStretch;\nstatic uiEntry *textWidth;\nstatic uiButton *textApply;\nstatic uiCheckbox *addLeading;\nstatic uiArea *textArea;\nstatic uiAreaHandler textAreaHandler;\n\nstatic double entryDouble(uiEntry *e)\n{\n\tchar *s;\n\tdouble d;\n\n\ts = uiEntryText(e);\n\td = atof(s);\n\tuiFreeText(s);\n\treturn d;\n}\n\nstatic void drawGuides(uiDrawContext *c, uiDrawTextFontMetrics *m)\n{\n\tuiDrawPath *p;\n\tuiDrawBrush b;\n\tuiDrawStrokeParams sp;\n\tdouble leading;\n\tdouble y;\n\n\tleading = 0;\n\tif (uiCheckboxChecked(addLeading))\n\t\tleading = m->Leading;\n\n\tmemset(&b, 0, sizeof (uiDrawBrush));\n\tb.Type = uiDrawBrushTypeSolid;\n\tmemset(&sp, 0, sizeof (uiDrawStrokeParams));\n\tsp.Cap = uiDrawLineCapFlat;\n\tsp.Join = uiDrawLineJoinMiter;\n\tsp.MiterLimit = uiDrawDefaultMiterLimit;\n\tsp.Thickness = 2;\n\n\tuiDrawSave(c);\n\n\tp = uiDrawNewPath(uiDrawFillModeWinding);\n\ty = 10;\n\tuiDrawPathNewFigure(p, 8, y);\n\ty += m->Ascent;\n\tuiDrawPathLineTo(p, 8, y);\n\tuiDrawPathEnd(p);\n\tb.R = 0.94;\n\tb.G = 0.5;\n\tb.B = 0.5;\n\tb.A = 1.0;\n\tuiDrawStroke(c, p, &b, &sp);\n\tuiDrawFreePath(p);\n\n\tp = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigure(p, 8, y);\n\ty += m->Descent;\n\tuiDrawPathLineTo(p, 8, y);\n\tuiDrawPathEnd(p);\n\tb.R = 0.12;\n\tb.G = 0.56;\n\tb.B = 1.0;\n\tb.A = 1.0;\n\tuiDrawStroke(c, p, &b, &sp);\n\tuiDrawFreePath(p);\n\n\t// and again for the second line\n\tp = uiDrawNewPath(uiDrawFillModeWinding);\n\ty += leading;\n\tuiDrawPathNewFigure(p, 8, y);\n\ty += m->Ascent;\n\tuiDrawPathLineTo(p, 8, y);\n\tuiDrawPathEnd(p);\n\tb.R = 0.94;\n\tb.G = 0.5;\n\tb.B = 0.5;\n\tb.A = 0.75;\n\tuiDrawStroke(c, p, &b, &sp);\n\tuiDrawFreePath(p);\n\n\tp = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathNewFigure(p, 8, y);\n\ty += m->Descent;\n\tuiDrawPathLineTo(p, 8, y);\n\tuiDrawPathEnd(p);\n\tb.R = 0.12;\n\tb.G = 0.56;\n\tb.B = 1.0;\n\tb.A = 0.75;\n\tuiDrawStroke(c, p, &b, &sp);\n\tuiDrawFreePath(p);\n\n\t// and a box to text layout top-left corners\n\tp = uiDrawNewPath(uiDrawFillModeWinding);\n\tuiDrawPathAddRectangle(p, 0, 0, 10, 10);\n\tuiDrawPathEnd(p);\n\tuiDrawClip(c, p);\n\tb.R = 0.85;\n\tb.G = 0.65;\n\tb.B = 0.13;\n\tb.A = 1.0;\n\tuiDrawStroke(c, p, &b, &sp);\n\tuiDrawFreePath(p);\n\n\tuiDrawRestore(c);\n}\n\nstatic void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *dp)\n{\n\tuiDrawTextFontDescriptor desc;\n\tuiDrawTextFont *font;\n\tchar *s;\n\tchar *family;\t\t// make compiler happy\n\tuiDrawTextLayout *layout;\n\tuiDrawTextFontMetrics metrics;\n\tdouble ypos;\n\tdouble width;\n\tdouble height;\n\n\tmemset(&desc, 0, sizeof (uiDrawTextFontDescriptor));\n\tfamily = uiEntryText(textFont);\n\tdesc.Family = family;\n\tdesc.Size = entryDouble(textSize);\n\tdesc.Weight = uiComboboxSelected(textWeight);\n\tdesc.Italic = uiComboboxSelected(textItalic);\n\tdesc.Stretch = uiComboboxSelected(textStretch);\n\tfont = uiDrawLoadClosestFont(&desc);\n\tuiFreeText(family);\n\tuiDrawTextFontGetMetrics(font, &metrics);\n\n\twidth = entryDouble(textWidth);\n\n\tdrawGuides(dp->Context, &metrics);\n\n\ts = uiEntryText(textString);\n\tlayout = uiDrawNewTextLayout(s, font, width);\n\tuiFreeText(s);\n\tif (uiCheckboxChecked(textSmallCaps))\n\t\t;\t// TODO\n\typos = 10;\n\tuiDrawText(dp->Context, 10, ypos, layout);\n\t// TODO make these optional?\n\tuiDrawTextLayoutExtents(layout, &width, &height);\n\tuiDrawFreeTextLayout(layout);\n\n\tlayout = uiDrawNewTextLayout(\"This is a second line\", font, -1);\n\tif (/*TODO reuse width*/entryDouble(textWidth) < 0) {\n\t\tdouble ad;\n\n\t\tad = metrics.Ascent + metrics.Descent;\n\t\tprintf(\"ad:%g extent:%g\\n\", ad, height);\n\t}\n\typos += height;\n\tif (uiCheckboxChecked(addLeading))\n\t\typos += metrics.Leading;\n\tuiDrawText(dp->Context, 10, ypos, layout);\n\tuiDrawFreeTextLayout(layout);\n\n\tuiDrawFreeTextFont(font);\n}\n\nstatic void handlerMouseEvent(uiAreaHandler *a, uiArea *area, uiAreaMouseEvent *e)\n{\n\t// do nothing\n}\n\nstatic void handlerMouseCrossed(uiAreaHandler *ah, uiArea *a, int left)\n{\n\t// do nothing\n}\n\nstatic void handlerDragBroken(uiAreaHandler *ah, uiArea *a)\n{\n\t// do nothing\n}\n\nstatic int handlerKeyEvent(uiAreaHandler *ah, uiArea *a, uiAreaKeyEvent *e)\n{\n\t// do nothing\n\treturn 0;\n}\n\nstatic void onTextApply(uiButton *b, void *data)\n{\n\tuiAreaQueueRedrawAll(textArea);\n}\n\nuiBox *makePage9(void)\n{\n\tuiBox *page9;\n\tuiBox *vbox;\n\tuiBox *hbox;\n\n\tpage9 = newVerticalBox();\n\tvbox = page9;\n\n\thbox = newHorizontalBox();\n\tuiBoxAppend(vbox, uiControl(hbox), 0);\n\n\ttextString = uiNewEntry();\n\t// TODO make it placeholder\n\tuiEntrySetText(textString, \"Enter text here\");\n\tuiBoxAppend(hbox, uiControl(textString), 1);\n\n\ttextFont = uiNewEntry();\n\tuiEntrySetText(textFont, \"Arial\");\n\tuiBoxAppend(hbox, uiControl(textFont), 1);\n\n\ttextSize = uiNewEntry();\n\tuiEntrySetText(textSize, \"10\");\n\tuiBoxAppend(hbox, uiControl(textSize), 1);\n\n\thbox = newHorizontalBox();\n\tuiBoxAppend(vbox, uiControl(hbox), 0);\n\n\ttextWeight = uiNewCombobox();\n\tuiComboboxAppend(textWeight, \"Thin\");\n\tuiComboboxAppend(textWeight, \"Ultra Light\");\n\tuiComboboxAppend(textWeight, \"Light\");\n\tuiComboboxAppend(textWeight, \"Book\");\n\tuiComboboxAppend(textWeight, \"Normal\");\n\tuiComboboxAppend(textWeight, \"Medium\");\n\tuiComboboxAppend(textWeight, \"Semi Bold\");\n\tuiComboboxAppend(textWeight, \"Bold\");\n\tuiComboboxAppend(textWeight, \"Ultra Bold\");\n\tuiComboboxAppend(textWeight, \"Heavy\");\n\tuiComboboxAppend(textWeight, \"Ultra Heavy\");\n\tuiComboboxSetSelected(textWeight, uiDrawTextWeightNormal);\n\tuiBoxAppend(hbox, uiControl(textWeight), 1);\n\n\ttextItalic = uiNewCombobox();\n\tuiComboboxAppend(textItalic, \"Normal\");\n\tuiComboboxAppend(textItalic, \"Oblique\");\n\tuiComboboxAppend(textItalic, \"Italic\");\n\tuiComboboxSetSelected(textItalic, uiDrawTextItalicNormal);\n\tuiBoxAppend(hbox, uiControl(textItalic), 1);\n\n\ttextSmallCaps = uiNewCheckbox(\"Small Caps\");\n\tuiBoxAppend(hbox, uiControl(textSmallCaps), 1);\n\n\thbox = newHorizontalBox();\n\tuiBoxAppend(vbox, uiControl(hbox), 0);\n\n\ttextStretch = uiNewCombobox();\n\tuiComboboxAppend(textStretch, \"Ultra Condensed\");\n\tuiComboboxAppend(textStretch, \"Extra Condensed\");\n\tuiComboboxAppend(textStretch, \"Condensed\");\n\tuiComboboxAppend(textStretch, \"Semi Condensed\");\n\tuiComboboxAppend(textStretch, \"Normal\");\n\tuiComboboxAppend(textStretch, \"Semi Expanded\");\n\tuiComboboxAppend(textStretch, \"Expanded\");\n\tuiComboboxAppend(textStretch, \"Extra Expanded\");\n\tuiComboboxAppend(textStretch, \"Ultra Expanded\");\n\tuiComboboxSetSelected(textStretch, uiDrawTextStretchNormal);\n\tuiBoxAppend(hbox, uiControl(textStretch), 1);\n\n\ttextWidth = uiNewEntry();\n\tuiEntrySetText(textWidth, \"-1\");\n\tuiBoxAppend(hbox, uiControl(textWidth), 1);\n\n\thbox = newHorizontalBox();\n\tuiBoxAppend(vbox, uiControl(hbox), 0);\n\n\ttextApply = uiNewButton(\"Apply\");\n\tuiButtonOnClicked(textApply, onTextApply, NULL);\n\tuiBoxAppend(hbox, uiControl(textApply), 1);\n\n\taddLeading = uiNewCheckbox(\"Add Leading\");\n\tuiCheckboxSetChecked(addLeading, 1);\n\tuiBoxAppend(hbox, uiControl(addLeading), 0);\n\n\ttextAreaHandler.Draw = handlerDraw;\n\ttextAreaHandler.MouseEvent = handlerMouseEvent;\n\ttextAreaHandler.MouseCrossed = handlerMouseCrossed;\n\ttextAreaHandler.DragBroken = handlerDragBroken;\n\ttextAreaHandler.KeyEvent = handlerKeyEvent;\n\ttextArea = uiNewArea(&textAreaHandler);\n\tuiBoxAppend(vbox, uiControl(textArea), 1);\n\n\treturn page9;\n}\n"
  },
  {
    "path": "test/resources.rc",
    "content": "// 30 may 2015\n\n// this is a UTF-8 file\n#pragma code_page(65001)\n\n// this is the Common Controls 6 manifest\n// TODO set up the string values here\n// 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\n#ifndef _UI_STATIC\n1 24 \"test.manifest\"\n#else\n1 24 \"test.static.manifest\"\n#endif\n"
  },
  {
    "path": "test/spaced.c",
    "content": "// 22 april 2015\n#include \"test.h\"\n\nstruct thing {\n\tvoid *ptr;\n\tint type;\n};\n\nstatic struct thing *things = NULL;\nstatic size_t len = 0;\nstatic size_t cap = 0;\n\n#define grow 32\n\nstatic void *append(void *thing, int type)\n{\n\tif (len >= cap) {\n\t\tcap += grow;\n\t\tthings = (struct thing *) realloc(things, cap * sizeof (struct thing));\n\t\tif (things == NULL)\n\t\t\tdie(\"reallocating things array in test/spaced.c append()\");\n\t}\n\tthings[len].ptr = thing;\n\tthings[len].type = type;\n\tlen++;\n\treturn things[len - 1].ptr;\n}\n\nenum types {\n\twindow,\n\tbox,\n\ttab,\n\tgroup,\n\tform,\n\tgrid,\n};\n\nvoid setSpaced(int spaced)\n{\n\tsize_t i;\n\tvoid *p;\n\tsize_t j, n;\n\n\tfor (i = 0; i < len; i++) {\n\t\tp = things[i].ptr;\n\t\tswitch (things[i].type) {\n\t\tcase window:\n\t\t\tuiWindowSetMargined(uiWindow(p), spaced);\n\t\t\tbreak;\n\t\tcase box:\n\t\t\tuiBoxSetPadded(uiBox(p), spaced);\n\t\t\tbreak;\n\t\tcase tab:\n\t\t\tn = uiTabNumPages(uiTab(p));\n\t\t\tfor (j = 0; j < n; j++)\n\t\t\t\tuiTabSetMargined(uiTab(p), j, spaced);\n\t\t\tbreak;\n\t\tcase group:\n\t\t\tuiGroupSetMargined(uiGroup(p), spaced);\n\t\t\tbreak;\n\t\tcase form:\n\t\t\tuiFormSetPadded(uiForm(p), spaced);\n\t\t\tbreak;\n\t\tcase grid:\n\t\t\tuiGridSetPadded(uiGrid(p), spaced);\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nvoid querySpaced(char out[12])\t\t// more than enough\n{\n\tint m = 0;\n\tint p = 0;\n\tsize_t i;\n\tvoid *pp;\n\tsize_t j, n;\n\n\tfor (i = 0; i < len; i++) {\n\t\tpp = things[i].ptr;\n\t\tswitch (things[i].type) {\n\t\tcase window:\n\t\t\tif (uiWindowMargined(uiWindow(pp)))\n\t\t\t\tm++;\n\t\t\tbreak;\n\t\tcase box:\n\t\t\tp = uiBoxPadded(uiBox(pp));\n\t\t\tbreak;\n\t\tcase tab:\n\t\t\tn = uiTabNumPages(uiTab(pp));\n\t\t\tfor (j = 0; j < n; j++)\n\t\t\t\tif (uiTabMargined(uiTab(pp), j))\n\t\t\t\t\tm++;\n\t\t\tbreak;\n\t\tcase group:\n\t\t\tif (uiGroupMargined(uiGroup(pp)))\n\t\t\t\tm++;\n\t\t\tbreak;\n\t\t// TODO form\n\t\t// TODO grid\n\t\t}\n\t}\n\n\tout[0] = 'm';\n\tout[1] = ' ';\n\tout[2] = '0' + m;\n\tout[3] = ' ';\n\tout[4] = 'p';\n\tout[5] = ' ';\n\tout[6] = '0';\n\tif (p)\n\t\tout[6] = '1';\n\tout[7] = '\\0';\n}\n\nuiWindow *newWindow(const char *title, int width, int height, int hasMenubar)\n{\n\tuiWindow *w;\n\n\tw = uiNewWindow(title, width, height, hasMenubar);\n\tappend(w, window);\n\treturn w;\n}\n\nuiBox *newHorizontalBox(void)\n{\n\tuiBox *b;\n\n\tb = (*newhbox)();\n\tappend(b, box);\n\treturn b;\n}\n\nuiBox *newVerticalBox(void)\n{\n\tuiBox *b;\n\n\tb = (*newvbox)();\n\tappend(b, box);\n\treturn b;\n}\n\nuiTab *newTab(void)\n{\n\tuiTab *t;\n\n\tt = uiNewTab();\n\tappend(t, tab);\n\treturn t;\n}\n\nuiGroup *newGroup(const char *text)\n{\n\tuiGroup *g;\n\n\tg = uiNewGroup(text);\n\tappend(g, group);\n\treturn g;\n}\n\nuiForm *newForm(void)\n{\n\tuiForm *f;\n\n\tf = uiNewForm();\n\tappend(f, form);\n\treturn f;\n}\n\nuiGrid *newGrid(void)\n{\n\tuiGrid *g;\n\n\tg = uiNewGrid();\n\tappend(g, grid);\n\treturn g;\n}\n"
  },
  {
    "path": "test/test.h",
    "content": "// 22 april 2015\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <stdarg.h>\n#include <string.h>\n#include <math.h>\n#include <limits.h>\n#include \"../ui.h\"\n\n// main.c\nextern void die(const char *, ...);\nextern uiBox *mainBox;\nextern uiTab *mainTab;\nextern uiBox *(*newhbox)(void);\nextern uiBox *(*newvbox)(void);\n\n// spaced.c\nextern void setSpaced(int);\nextern void querySpaced(char[12]);\nextern uiWindow *newWindow(const char *title, int width, int height, int hasMenubar);\nextern uiBox *newHorizontalBox(void);\nextern uiBox *newVerticalBox(void);\nextern uiTab *newTab(void);\nextern uiGroup *newGroup(const char *);\nextern uiForm *newForm(void);\nextern uiGrid *newGrid(void);\n\n// menus.c\nextern uiMenuItem *shouldQuitItem;\nextern void initMenus(void);\n\n// page1.c\nextern uiBox *page1;\nextern void makePage1(uiWindow *);\n\n// page2.c\nextern uiGroup *page2group;\nextern uiBox *makePage2(void);\n\n// page3.c\nextern uiBox *makePage3(void);\n\n// page4.c\nextern uiBox *makePage4(void);\n\n// page5.c\nextern uiBox *makePage5(uiWindow *);\n\n// page6.c\nextern uiBox *makePage6(void);\n\n// drawtests.c\nextern void runDrawTest(int, uiAreaDrawParams *);\nextern void populateComboboxWithTests(uiCombobox *);\n\n// page7.c\nextern uiBox *makePage7(void);\n\n// page7a.c\nextern uiGroup *makePage7a(void);\n\n// page7b.c\nextern uiGroup *makePage7b(void);\n\n// page7c.c\nextern uiGroup *makePage7c(void);\n\n// page8.c\nextern uiBox *makePage8(void);\n\n// page9.c\nextern uiBox *makePage9(void);\n\n// page10.c\nextern uiBox *makePage10(void);\n\n// page11.c\nextern uiBox *makePage11(void);\n\n// page12.c\nextern uiBox *makePage12(void);\n\n// page13.c\nextern uiBox *makePage13(void);\n\n// page14.c\nextern uiTab *makePage14(void);\n\n// page15.c\nextern uiBox *makePage15(uiWindow *);\n\n// page16.c\nextern uiBox *makePage16(void);\nextern void freePage16(void);\n\n// images.c\nextern void appendImageNamed(uiImage *img, const char *name);\n"
  },
  {
    "path": "test/test.manifest",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">\n<assemblyIdentity\n    version=\"1.0.0.0\"\n    processorArchitecture=\"*\"\n    name=\"CompanyName.ProductName.YourApplication\"\n    type=\"win32\"\n/>\n<description>Your application description here.</description>\n<!-- do NOT include the comctl6 dependency here; this lets us find bugs related to theming -->\n<compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\">\n    <application>\n        <!--The ID below indicates application support for Windows Vista -->\n        <supportedOS Id=\"{e2011457-1546-43c5-a5fe-008deee3d3f0}\"/>\n        <!--The ID below indicates application support for Windows 7 -->\n        <supportedOS Id=\"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}\"/>\n    </application>\n</compatibility>\n</assembly>\n\n"
  },
  {
    "path": "test/test.static.manifest",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">\n<assemblyIdentity\n    version=\"1.0.0.0\"\n    processorArchitecture=\"*\"\n    name=\"CompanyName.ProductName.YourApplication\"\n    type=\"win32\"\n/>\n<description>Your application description here.</description>\n<!-- we DO need comctl6 in the static case -->\n<dependency>\n    <dependentAssembly>\n        <assemblyIdentity\n            type=\"win32\"\n            name=\"Microsoft.Windows.Common-Controls\"\n            version=\"6.0.0.0\"\n            processorArchitecture=\"*\"\n            publicKeyToken=\"6595b64144ccf1df\"\n            language=\"*\"\n        />\n    </dependentAssembly>\n</dependency>\n<compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\">\n    <application>\n        <!--The ID below indicates application support for Windows Vista -->\n        <supportedOS Id=\"{e2011457-1546-43c5-a5fe-008deee3d3f0}\"/>\n        <!--The ID below indicates application support for Windows 7 -->\n        <supportedOS Id=\"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}\"/>\n    </application>\n</compatibility>\n</assembly>\n\n"
  },
  {
    "path": "ui.h",
    "content": "// 6 april 2015\n\n// TODO add a uiVerifyControlType() function that can be used by control implementations to verify controls\n\n// TODOs\n// - make getters that return whether something exists accept a NULL pointer to discard the value (and thus only return that the thing exists?)\n// - const-correct everything\n// - normalize documentation between typedefs and structs\n\n#ifndef __LIBUI_UI_H__\n#define __LIBUI_UI_H__\n\n#include <stddef.h>\n#include <stdint.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n// this macro is generated by cmake\n#ifdef libui_EXPORTS\n#ifdef _WIN32\n#define _UI_EXTERN __declspec(dllexport) extern\n#else\n#define _UI_EXTERN __attribute__((visibility(\"default\"))) extern\n#endif\n#else\n// TODO add __declspec(dllimport) on windows, but only if not static\n#define _UI_EXTERN extern\n#endif\n\n// C++ is really really really really really really dumb about enums, so screw that and just make them anonymous\n// This has the advantage of being ABI-able should we ever need an ABI...\n#define _UI_ENUM(s) typedef unsigned int s; enum\n\n// This constant is provided because M_PI is nonstandard.\n// This comes from Go's math.Pi, which in turn comes from http://oeis.org/A000796.\n#define uiPi 3.14159265358979323846264338327950288419716939937510582097494459\n\n// TODO uiBool?\n\n// uiForEach represents the return value from one of libui's various ForEach functions.\n_UI_ENUM(uiForEach) {\n\tuiForEachContinue,\n\tuiForEachStop,\n};\n\ntypedef struct uiInitOptions uiInitOptions;\n\nstruct uiInitOptions {\n\tsize_t Size;\n};\n\n_UI_EXTERN const char *uiInit(uiInitOptions *options);\n_UI_EXTERN void uiUninit(void);\n_UI_EXTERN void uiFreeInitError(const char *err);\n\n_UI_EXTERN void uiMain(void);\n_UI_EXTERN void uiMainSteps(void);\n_UI_EXTERN int uiMainStep(int wait);\n_UI_EXTERN void uiQuit(void);\n\n_UI_EXTERN void uiQueueMain(void (*f)(void *data), void *data);\n\n// TODO standardize the looping behavior return type, either with some enum or something, and the test expressions throughout the code\n// 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\n// TODO (also in the above link) document that this cannot be called from any thread, unlike uiQueueMain()\n// TODO document that the minimum exact timing, either accuracy (timer burst, etc.) or granularity (15ms on Windows, etc.), is OS-defined\n// TODO also figure out how long until the initial tick is registered on all platforms to document\n// TODO also add a comment about how useful this could be in bindings, depending on the language being bound to\n_UI_EXTERN void uiTimer(int milliseconds, int (*f)(void *data), void *data);\n\n_UI_EXTERN void uiOnShouldQuit(int (*f)(void *data), void *data);\n\n_UI_EXTERN void uiFreeText(char *text);\n\ntypedef struct uiControl uiControl;\n\nstruct uiControl {\n\tuint32_t Signature;\n\tuint32_t OSSignature;\n\tuint32_t TypeSignature;\n\tvoid (*Destroy)(uiControl *);\n\tuintptr_t (*Handle)(uiControl *);\n\tuiControl *(*Parent)(uiControl *);\n\tvoid (*SetParent)(uiControl *, uiControl *);\n\tint (*Toplevel)(uiControl *);\n\tint (*Visible)(uiControl *);\n\tvoid (*Show)(uiControl *);\n\tvoid (*Hide)(uiControl *);\n\tint (*Enabled)(uiControl *);\n\tvoid (*Enable)(uiControl *);\n\tvoid (*Disable)(uiControl *);\n};\n// TOOD add argument names to all arguments\n#define uiControl(this) ((uiControl *) (this))\n_UI_EXTERN void uiControlDestroy(uiControl *);\n_UI_EXTERN uintptr_t uiControlHandle(uiControl *);\n_UI_EXTERN uiControl *uiControlParent(uiControl *);\n_UI_EXTERN void uiControlSetParent(uiControl *, uiControl *);\n_UI_EXTERN int uiControlToplevel(uiControl *);\n_UI_EXTERN int uiControlVisible(uiControl *);\n_UI_EXTERN void uiControlShow(uiControl *);\n_UI_EXTERN void uiControlHide(uiControl *);\n_UI_EXTERN int uiControlEnabled(uiControl *);\n_UI_EXTERN void uiControlEnable(uiControl *);\n_UI_EXTERN void uiControlDisable(uiControl *);\n\n_UI_EXTERN uiControl *uiAllocControl(size_t n, uint32_t OSsig, uint32_t typesig, const char *typenamestr);\n_UI_EXTERN void uiFreeControl(uiControl *);\n\n// TODO make sure all controls have these\n_UI_EXTERN void uiControlVerifySetParent(uiControl *, uiControl *);\n_UI_EXTERN int uiControlEnabledToUser(uiControl *);\n\n_UI_EXTERN void uiUserBugCannotSetParentOnToplevel(const char *type);\n\ntypedef struct uiWindow uiWindow;\n#define uiWindow(this) ((uiWindow *) (this))\n_UI_EXTERN char *uiWindowTitle(uiWindow *w);\n_UI_EXTERN void uiWindowSetTitle(uiWindow *w, const char *title);\n_UI_EXTERN void uiWindowContentSize(uiWindow *w, int *width, int *height);\n_UI_EXTERN void uiWindowSetContentSize(uiWindow *w, int width, int height);\n_UI_EXTERN int uiWindowFullscreen(uiWindow *w);\n_UI_EXTERN void uiWindowSetFullscreen(uiWindow *w, int fullscreen);\n_UI_EXTERN void uiWindowOnContentSizeChanged(uiWindow *w, void (*f)(uiWindow *, void *), void *data);\n_UI_EXTERN void uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *w, void *data), void *data);\n_UI_EXTERN int uiWindowBorderless(uiWindow *w);\n_UI_EXTERN void uiWindowSetBorderless(uiWindow *w, int borderless);\n_UI_EXTERN void uiWindowSetChild(uiWindow *w, uiControl *child);\n_UI_EXTERN int uiWindowMargined(uiWindow *w);\n_UI_EXTERN void uiWindowSetMargined(uiWindow *w, int margined);\n_UI_EXTERN uiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar);\n\ntypedef struct uiButton uiButton;\n#define uiButton(this) ((uiButton *) (this))\n_UI_EXTERN char *uiButtonText(uiButton *b);\n_UI_EXTERN void uiButtonSetText(uiButton *b, const char *text);\n_UI_EXTERN void uiButtonOnClicked(uiButton *b, void (*f)(uiButton *b, void *data), void *data);\n_UI_EXTERN uiButton *uiNewButton(const char *text);\n\ntypedef struct uiBox uiBox;\n#define uiBox(this) ((uiBox *) (this))\n_UI_EXTERN void uiBoxAppend(uiBox *b, uiControl *child, int stretchy);\n_UI_EXTERN void uiBoxDelete(uiBox *b, int index);\n_UI_EXTERN int uiBoxPadded(uiBox *b);\n_UI_EXTERN void uiBoxSetPadded(uiBox *b, int padded);\n_UI_EXTERN uiBox *uiNewHorizontalBox(void);\n_UI_EXTERN uiBox *uiNewVerticalBox(void);\n\ntypedef struct uiCheckbox uiCheckbox;\n#define uiCheckbox(this) ((uiCheckbox *) (this))\n_UI_EXTERN char *uiCheckboxText(uiCheckbox *c);\n_UI_EXTERN void uiCheckboxSetText(uiCheckbox *c, const char *text);\n_UI_EXTERN void uiCheckboxOnToggled(uiCheckbox *c, void (*f)(uiCheckbox *c, void *data), void *data);\n_UI_EXTERN int uiCheckboxChecked(uiCheckbox *c);\n_UI_EXTERN void uiCheckboxSetChecked(uiCheckbox *c, int checked);\n_UI_EXTERN uiCheckbox *uiNewCheckbox(const char *text);\n\ntypedef struct uiEntry uiEntry;\n#define uiEntry(this) ((uiEntry *) (this))\n_UI_EXTERN char *uiEntryText(uiEntry *e);\n_UI_EXTERN void uiEntrySetText(uiEntry *e, const char *text);\n_UI_EXTERN void uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *e, void *data), void *data);\n_UI_EXTERN int uiEntryReadOnly(uiEntry *e);\n_UI_EXTERN void uiEntrySetReadOnly(uiEntry *e, int readonly);\n_UI_EXTERN uiEntry *uiNewEntry(void);\n_UI_EXTERN uiEntry *uiNewPasswordEntry(void);\n_UI_EXTERN uiEntry *uiNewSearchEntry(void);\n\ntypedef struct uiLabel uiLabel;\n#define uiLabel(this) ((uiLabel *) (this))\n_UI_EXTERN char *uiLabelText(uiLabel *l);\n_UI_EXTERN void uiLabelSetText(uiLabel *l, const char *text);\n_UI_EXTERN uiLabel *uiNewLabel(const char *text);\n\ntypedef struct uiTab uiTab;\n#define uiTab(this) ((uiTab *) (this))\n_UI_EXTERN void uiTabAppend(uiTab *t, const char *name, uiControl *c);\n_UI_EXTERN void uiTabInsertAt(uiTab *t, const char *name, int before, uiControl *c);\n_UI_EXTERN void uiTabDelete(uiTab *t, int index);\n_UI_EXTERN int uiTabNumPages(uiTab *t);\n_UI_EXTERN int uiTabMargined(uiTab *t, int page);\n_UI_EXTERN void uiTabSetMargined(uiTab *t, int page, int margined);\n_UI_EXTERN uiTab *uiNewTab(void);\n\ntypedef struct uiGroup uiGroup;\n#define uiGroup(this) ((uiGroup *) (this))\n_UI_EXTERN char *uiGroupTitle(uiGroup *g);\n_UI_EXTERN void uiGroupSetTitle(uiGroup *g, const char *title);\n_UI_EXTERN void uiGroupSetChild(uiGroup *g, uiControl *c);\n_UI_EXTERN int uiGroupMargined(uiGroup *g);\n_UI_EXTERN void uiGroupSetMargined(uiGroup *g, int margined);\n_UI_EXTERN uiGroup *uiNewGroup(const char *title);\n\n// spinbox/slider rules:\n// setting value outside of range will automatically clamp\n// initial value is minimum\n// complaint if min >= max?\n\ntypedef struct uiSpinbox uiSpinbox;\n#define uiSpinbox(this) ((uiSpinbox *) (this))\n_UI_EXTERN int uiSpinboxValue(uiSpinbox *s);\n_UI_EXTERN void uiSpinboxSetValue(uiSpinbox *s, int value);\n_UI_EXTERN void uiSpinboxOnChanged(uiSpinbox *s, void (*f)(uiSpinbox *s, void *data), void *data);\n_UI_EXTERN uiSpinbox *uiNewSpinbox(int min, int max);\n\ntypedef struct uiSlider uiSlider;\n#define uiSlider(this) ((uiSlider *) (this))\n_UI_EXTERN int uiSliderValue(uiSlider *s);\n_UI_EXTERN void uiSliderSetValue(uiSlider *s, int value);\n_UI_EXTERN void uiSliderOnChanged(uiSlider *s, void (*f)(uiSlider *s, void *data), void *data);\n_UI_EXTERN uiSlider *uiNewSlider(int min, int max);\n\ntypedef struct uiProgressBar uiProgressBar;\n#define uiProgressBar(this) ((uiProgressBar *) (this))\n_UI_EXTERN int uiProgressBarValue(uiProgressBar *p);\n_UI_EXTERN void uiProgressBarSetValue(uiProgressBar *p, int n);\n_UI_EXTERN uiProgressBar *uiNewProgressBar(void);\n\ntypedef struct uiSeparator uiSeparator;\n#define uiSeparator(this) ((uiSeparator *) (this))\n_UI_EXTERN uiSeparator *uiNewHorizontalSeparator(void);\n_UI_EXTERN uiSeparator *uiNewVerticalSeparator(void);\n\ntypedef struct uiCombobox uiCombobox;\n#define uiCombobox(this) ((uiCombobox *) (this))\n_UI_EXTERN void uiComboboxAppend(uiCombobox *c, const char *text);\n_UI_EXTERN int uiComboboxSelected(uiCombobox *c);\n_UI_EXTERN void uiComboboxSetSelected(uiCombobox *c, int n);\n_UI_EXTERN void uiComboboxOnSelected(uiCombobox *c, void (*f)(uiCombobox *c, void *data), void *data);\n_UI_EXTERN uiCombobox *uiNewCombobox(void);\n\ntypedef struct uiEditableCombobox uiEditableCombobox;\n#define uiEditableCombobox(this) ((uiEditableCombobox *) (this))\n_UI_EXTERN void uiEditableComboboxAppend(uiEditableCombobox *c, const char *text);\n_UI_EXTERN char *uiEditableComboboxText(uiEditableCombobox *c);\n_UI_EXTERN void uiEditableComboboxSetText(uiEditableCombobox *c, const char *text);\n// 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\n_UI_EXTERN void uiEditableComboboxOnChanged(uiEditableCombobox *c, void (*f)(uiEditableCombobox *c, void *data), void *data);\n_UI_EXTERN uiEditableCombobox *uiNewEditableCombobox(void);\n\ntypedef struct uiRadioButtons uiRadioButtons;\n#define uiRadioButtons(this) ((uiRadioButtons *) (this))\n_UI_EXTERN void uiRadioButtonsAppend(uiRadioButtons *r, const char *text);\n_UI_EXTERN int uiRadioButtonsSelected(uiRadioButtons *r);\n_UI_EXTERN void uiRadioButtonsSetSelected(uiRadioButtons *r, int n);\n_UI_EXTERN void uiRadioButtonsOnSelected(uiRadioButtons *r, void (*f)(uiRadioButtons *, void *), void *data);\n_UI_EXTERN uiRadioButtons *uiNewRadioButtons(void);\n\nstruct tm;\ntypedef struct uiDateTimePicker uiDateTimePicker;\n#define uiDateTimePicker(this) ((uiDateTimePicker *) (this))\n// TODO document that tm_wday and tm_yday are undefined, and tm_isdst should be -1\n// TODO document that for both sides\n// TODO document time zone conversions or lack thereof\n// TODO for Time: define what values are returned when a part is missing\n_UI_EXTERN void uiDateTimePickerTime(uiDateTimePicker *d, struct tm *time);\n_UI_EXTERN void uiDateTimePickerSetTime(uiDateTimePicker *d, const struct tm *time);\n_UI_EXTERN void uiDateTimePickerOnChanged(uiDateTimePicker *d, void (*f)(uiDateTimePicker *, void *), void *data);\n_UI_EXTERN uiDateTimePicker *uiNewDateTimePicker(void);\n_UI_EXTERN uiDateTimePicker *uiNewDatePicker(void);\n_UI_EXTERN uiDateTimePicker *uiNewTimePicker(void);\n\n// TODO provide a facility for entering tab stops?\ntypedef struct uiMultilineEntry uiMultilineEntry;\n#define uiMultilineEntry(this) ((uiMultilineEntry *) (this))\n_UI_EXTERN char *uiMultilineEntryText(uiMultilineEntry *e);\n_UI_EXTERN void uiMultilineEntrySetText(uiMultilineEntry *e, const char *text);\n_UI_EXTERN void uiMultilineEntryAppend(uiMultilineEntry *e, const char *text);\n_UI_EXTERN void uiMultilineEntryOnChanged(uiMultilineEntry *e, void (*f)(uiMultilineEntry *e, void *data), void *data);\n_UI_EXTERN int uiMultilineEntryReadOnly(uiMultilineEntry *e);\n_UI_EXTERN void uiMultilineEntrySetReadOnly(uiMultilineEntry *e, int readonly);\n_UI_EXTERN uiMultilineEntry *uiNewMultilineEntry(void);\n_UI_EXTERN uiMultilineEntry *uiNewNonWrappingMultilineEntry(void);\n\ntypedef struct uiMenuItem uiMenuItem;\n#define uiMenuItem(this) ((uiMenuItem *) (this))\n_UI_EXTERN void uiMenuItemEnable(uiMenuItem *m);\n_UI_EXTERN void uiMenuItemDisable(uiMenuItem *m);\n_UI_EXTERN void uiMenuItemOnClicked(uiMenuItem *m, void (*f)(uiMenuItem *sender, uiWindow *window, void *data), void *data);\n_UI_EXTERN int uiMenuItemChecked(uiMenuItem *m);\n_UI_EXTERN void uiMenuItemSetChecked(uiMenuItem *m, int checked);\n\ntypedef struct uiMenu uiMenu;\n#define uiMenu(this) ((uiMenu *) (this))\n_UI_EXTERN uiMenuItem *uiMenuAppendItem(uiMenu *m, const char *name);\n_UI_EXTERN uiMenuItem *uiMenuAppendCheckItem(uiMenu *m, const char *name);\n_UI_EXTERN uiMenuItem *uiMenuAppendQuitItem(uiMenu *m);\n_UI_EXTERN uiMenuItem *uiMenuAppendPreferencesItem(uiMenu *m);\n_UI_EXTERN uiMenuItem *uiMenuAppendAboutItem(uiMenu *m);\n_UI_EXTERN void uiMenuAppendSeparator(uiMenu *m);\n_UI_EXTERN uiMenu *uiNewMenu(const char *name);\n\n_UI_EXTERN char *uiOpenFile(uiWindow *parent);\n_UI_EXTERN char *uiSaveFile(uiWindow *parent);\n_UI_EXTERN void uiMsgBox(uiWindow *parent, const char *title, const char *description);\n_UI_EXTERN void uiMsgBoxError(uiWindow *parent, const char *title, const char *description);\n\ntypedef struct uiArea uiArea;\ntypedef struct uiAreaHandler uiAreaHandler;\ntypedef struct uiAreaDrawParams uiAreaDrawParams;\ntypedef struct uiAreaMouseEvent uiAreaMouseEvent;\ntypedef struct uiAreaKeyEvent uiAreaKeyEvent;\n\ntypedef struct uiDrawContext uiDrawContext;\n\nstruct uiAreaHandler {\n\tvoid (*Draw)(uiAreaHandler *, uiArea *, uiAreaDrawParams *);\n\t// TODO document that resizes cause a full redraw for non-scrolling areas; implementation-defined for scrolling areas\n\tvoid (*MouseEvent)(uiAreaHandler *, uiArea *, uiAreaMouseEvent *);\n\t// TODO document that on first show if the mouse is already in the uiArea then one gets sent with left=0\n\t// TODO what about when the area is hidden and then shown again?\n\tvoid (*MouseCrossed)(uiAreaHandler *, uiArea *, int left);\n\tvoid (*DragBroken)(uiAreaHandler *, uiArea *);\n\tint (*KeyEvent)(uiAreaHandler *, uiArea *, uiAreaKeyEvent *);\n};\n\n// TODO RTL layouts?\n// TODO reconcile edge and corner naming\n_UI_ENUM(uiWindowResizeEdge) {\n\tuiWindowResizeEdgeLeft,\n\tuiWindowResizeEdgeTop,\n\tuiWindowResizeEdgeRight,\n\tuiWindowResizeEdgeBottom,\n\tuiWindowResizeEdgeTopLeft,\n\tuiWindowResizeEdgeTopRight,\n\tuiWindowResizeEdgeBottomLeft,\n\tuiWindowResizeEdgeBottomRight,\n\t// TODO have one for keyboard resizes?\n\t// TODO GDK doesn't seem to have any others, including for keyboards...\n\t// TODO way to bring up the system menu instead?\n};\n\n#define uiArea(this) ((uiArea *) (this))\n// TODO give a better name\n// TODO document the types of width and height\n_UI_EXTERN void uiAreaSetSize(uiArea *a, int width, int height);\n// TODO uiAreaQueueRedraw()\n_UI_EXTERN void uiAreaQueueRedrawAll(uiArea *a);\n_UI_EXTERN void uiAreaScrollTo(uiArea *a, double x, double y, double width, double height);\n// TODO document these can only be called within Mouse() handlers\n// TODO should these be allowed on scrolling areas?\n// TODO decide which mouse events should be accepted; Down is the only one guaranteed to work right now\n// TODO what happens to events after calling this up to and including the next mouse up?\n// TODO release capture?\n_UI_EXTERN void uiAreaBeginUserWindowMove(uiArea *a);\n_UI_EXTERN void uiAreaBeginUserWindowResize(uiArea *a, uiWindowResizeEdge edge);\n_UI_EXTERN uiArea *uiNewArea(uiAreaHandler *ah);\n_UI_EXTERN uiArea *uiNewScrollingArea(uiAreaHandler *ah, int width, int height);\n\nstruct uiAreaDrawParams {\n\tuiDrawContext *Context;\n\n\t// TODO document that this is only defined for nonscrolling areas\n\tdouble AreaWidth;\n\tdouble AreaHeight;\n\n\tdouble ClipX;\n\tdouble ClipY;\n\tdouble ClipWidth;\n\tdouble ClipHeight;\n};\n\ntypedef struct uiDrawPath uiDrawPath;\ntypedef struct uiDrawBrush uiDrawBrush;\ntypedef struct uiDrawStrokeParams uiDrawStrokeParams;\ntypedef struct uiDrawMatrix uiDrawMatrix;\n\ntypedef struct uiDrawBrushGradientStop uiDrawBrushGradientStop;\n\n_UI_ENUM(uiDrawBrushType) {\n\tuiDrawBrushTypeSolid,\n\tuiDrawBrushTypeLinearGradient,\n\tuiDrawBrushTypeRadialGradient,\n\tuiDrawBrushTypeImage,\n};\n\n_UI_ENUM(uiDrawLineCap) {\n\tuiDrawLineCapFlat,\n\tuiDrawLineCapRound,\n\tuiDrawLineCapSquare,\n};\n\n_UI_ENUM(uiDrawLineJoin) {\n\tuiDrawLineJoinMiter,\n\tuiDrawLineJoinRound,\n\tuiDrawLineJoinBevel,\n};\n\n// this is the default for botoh cairo and Direct2D (in the latter case, from the C++ helper functions)\n// Core Graphics doesn't explicitly specify a default, but NSBezierPath allows you to choose one, and this is the initial value\n// so we're good to use it too!\n#define uiDrawDefaultMiterLimit 10.0\n\n_UI_ENUM(uiDrawFillMode) {\n\tuiDrawFillModeWinding,\n\tuiDrawFillModeAlternate,\n};\n\nstruct uiDrawMatrix {\n\tdouble M11;\n\tdouble M12;\n\tdouble M21;\n\tdouble M22;\n\tdouble M31;\n\tdouble M32;\n};\n\nstruct uiDrawBrush {\n\tuiDrawBrushType Type;\n\n\t// solid brushes\n\tdouble R;\n\tdouble G;\n\tdouble B;\n\tdouble A;\n\n\t// gradient brushes\n\tdouble X0;\t\t// linear: start X, radial: start X\n\tdouble Y0;\t\t// linear: start Y, radial: start Y\n\tdouble X1;\t\t// linear: end X, radial: outer circle center X\n\tdouble Y1;\t\t// linear: end Y, radial: outer circle center Y\n\tdouble OuterRadius;\t\t// radial gradients only\n\tuiDrawBrushGradientStop *Stops;\n\tsize_t NumStops;\n\t// TODO extend mode\n\t// cairo: none, repeat, reflect, pad; no individual control\n\t// Direct2D: repeat, reflect, pad; no individual control\n\t// Core Graphics: none, pad; before and after individually\n\t// TODO cairo documentation is inconsistent about pad\n\n\t// TODO images\n\n\t// TODO transforms\n};\n\nstruct uiDrawBrushGradientStop {\n\tdouble Pos;\n\tdouble R;\n\tdouble G;\n\tdouble B;\n\tdouble A;\n};\n\nstruct uiDrawStrokeParams {\n\tuiDrawLineCap Cap;\n\tuiDrawLineJoin Join;\n\t// TODO what if this is 0? on windows there will be a crash with dashing\n\tdouble Thickness;\n\tdouble MiterLimit;\n\tdouble *Dashes;\n\t// TOOD what if this is 1 on Direct2D?\n\t// TODO what if a dash is 0 on Cairo or Quartz?\n\tsize_t NumDashes;\n\tdouble DashPhase;\n};\n\n_UI_EXTERN uiDrawPath *uiDrawNewPath(uiDrawFillMode fillMode);\n_UI_EXTERN void uiDrawFreePath(uiDrawPath *p);\n\n_UI_EXTERN void uiDrawPathNewFigure(uiDrawPath *p, double x, double y);\n_UI_EXTERN void uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative);\n_UI_EXTERN void uiDrawPathLineTo(uiDrawPath *p, double x, double y);\n// notes: angles are both relative to 0 and go counterclockwise\n// TODO is the initial line segment on cairo and OS X a proper join?\n// TODO what if sweep < 0?\n_UI_EXTERN void uiDrawPathArcTo(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative);\n_UI_EXTERN void uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, double c2y, double endX, double endY);\n// TODO quadratic bezier\n_UI_EXTERN void uiDrawPathCloseFigure(uiDrawPath *p);\n\n// TODO effect of these when a figure is already started\n_UI_EXTERN void uiDrawPathAddRectangle(uiDrawPath *p, double x, double y, double width, double height);\n\n_UI_EXTERN void uiDrawPathEnd(uiDrawPath *p);\n\n_UI_EXTERN void uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStrokeParams *p);\n_UI_EXTERN void uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b);\n\n// TODO primitives:\n// - rounded rectangles\n// - elliptical arcs\n// - quadratic bezier curves\n\n_UI_EXTERN void uiDrawMatrixSetIdentity(uiDrawMatrix *m);\n_UI_EXTERN void uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y);\n_UI_EXTERN void uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x, double y);\n_UI_EXTERN void uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount);\n_UI_EXTERN void uiDrawMatrixSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount);\n_UI_EXTERN void uiDrawMatrixMultiply(uiDrawMatrix *dest, uiDrawMatrix *src);\n_UI_EXTERN int uiDrawMatrixInvertible(uiDrawMatrix *m);\n_UI_EXTERN int uiDrawMatrixInvert(uiDrawMatrix *m);\n_UI_EXTERN void uiDrawMatrixTransformPoint(uiDrawMatrix *m, double *x, double *y);\n_UI_EXTERN void uiDrawMatrixTransformSize(uiDrawMatrix *m, double *x, double *y);\n\n_UI_EXTERN void uiDrawTransform(uiDrawContext *c, uiDrawMatrix *m);\n\n// TODO add a uiDrawPathStrokeToFill() or something like that\n_UI_EXTERN void uiDrawClip(uiDrawContext *c, uiDrawPath *path);\n\n_UI_EXTERN void uiDrawSave(uiDrawContext *c);\n_UI_EXTERN void uiDrawRestore(uiDrawContext *c);\n\n// uiAttribute stores information about an attribute in a\n// uiAttributedString.\n//\n// You do not create uiAttributes directly; instead, you create a\n// uiAttribute of a given type using the specialized constructor\n// functions. For every Unicode codepoint in the uiAttributedString,\n// at most one value of each attribute type can be applied.\n//\n// uiAttributes are immutable and the uiAttributedString takes\n// ownership of the uiAttribute object once assigned, copying its\n// contents as necessary.\ntypedef struct uiAttribute uiAttribute;\n\n// @role uiAttribute destructor\n// uiFreeAttribute() frees a uiAttribute. You generally do not need to\n// call this yourself, as uiAttributedString does this for you. In fact,\n// it is an error to call this function on a uiAttribute that has been\n// given to a uiAttributedString. You can call this, however, if you\n// created a uiAttribute that you aren't going to use later.\n_UI_EXTERN void uiFreeAttribute(uiAttribute *a);\n\n// uiAttributeType holds the possible uiAttribute types that may be\n// returned by uiAttributeGetType(). Refer to the documentation for\n// each type's constructor function for details on each type.\n_UI_ENUM(uiAttributeType) {\n\tuiAttributeTypeFamily,\n\tuiAttributeTypeSize,\n\tuiAttributeTypeWeight,\n\tuiAttributeTypeItalic,\n\tuiAttributeTypeStretch,\n\tuiAttributeTypeColor,\n\tuiAttributeTypeBackground,\n\tuiAttributeTypeUnderline,\n\tuiAttributeTypeUnderlineColor,\n\tuiAttributeTypeFeatures,\n};\n\n// uiAttributeGetType() returns the type of a.\n// TODO I don't like this name\n_UI_EXTERN uiAttributeType uiAttributeGetType(const uiAttribute *a);\n\n// uiNewFamilyAttribute() creates a new uiAttribute that changes the\n// font family of the text it is applied to. family is copied; you do not\n// need to keep it alive after uiNewFamilyAttribute() returns. Font\n// family names are case-insensitive.\n_UI_EXTERN uiAttribute *uiNewFamilyAttribute(const char *family);\n\n// uiAttributeFamily() returns the font family stored in a. The\n// returned string is owned by a. It is an error to call this on a\n// uiAttribute that does not hold a font family.\n_UI_EXTERN const char *uiAttributeFamily(const uiAttribute *a);\n\n// uiNewSizeAttribute() creates a new uiAttribute that changes the\n// size of the text it is applied to, in typographical points.\n_UI_EXTERN uiAttribute *uiNewSizeAttribute(double size);\n\n// uiAttributeSize() returns the font size stored in a. It is an error to\n// call this on a uiAttribute that does not hold a font size.\n_UI_EXTERN double uiAttributeSize(const uiAttribute *a);\n\n// uiTextWeight represents possible text weights. These roughly\n// map to the OS/2 text weight field of TrueType and OpenType\n// fonts, or to CSS weight numbers. The named constants are\n// nominal values; the actual values may vary by font and by OS,\n// though this isn't particularly likely. Any value between\n// uiTextWeightMinimum and uiTextWeightMaximum, inclusive,\n// is allowed.\n//\n// Note that due to restrictions in early versions of Windows, some\n// fonts have \"special\" weights be exposed in many programs as\n// separate font families. This is perhaps most notable with\n// Arial Black. libui does not do this, even on Windows (because the\n// DirectWrite API libui uses on Windows does not do this); to\n// specify Arial Black, use family Arial and weight uiTextWeightBlack.\n_UI_ENUM(uiTextWeight) {\n\tuiTextWeightMinimum = 0,\n\tuiTextWeightThin = 100,\n\tuiTextWeightUltraLight = 200,\n\tuiTextWeightLight = 300,\n\tuiTextWeightBook = 350,\n\tuiTextWeightNormal = 400,\n\tuiTextWeightMedium = 500,\n\tuiTextWeightSemiBold = 600,\n\tuiTextWeightBold = 700,\n\tuiTextWeightUltraBold = 800,\n\tuiTextWeightHeavy = 900,\n\tuiTextWeightUltraHeavy = 950,\n\tuiTextWeightMaximum = 1000,\n};\n\n// uiNewWeightAttribute() creates a new uiAttribute that changes the\n// weight of the text it is applied to. It is an error to specify a weight\n// outside the range [uiTextWeightMinimum,\n// uiTextWeightMaximum].\n_UI_EXTERN uiAttribute *uiNewWeightAttribute(uiTextWeight weight);\n\n// uiAttributeWeight() returns the font weight stored in a. It is an error\n// to call this on a uiAttribute that does not hold a font weight.\n_UI_EXTERN uiTextWeight uiAttributeWeight(const uiAttribute *a);\n\n// uiTextItalic represents possible italic modes for a font. Italic\n// represents \"true\" italics where the slanted glyphs have custom\n// shapes, whereas oblique represents italics that are merely slanted\n// versions of the normal glyphs. Most fonts usually have one or the\n// other.\n_UI_ENUM(uiTextItalic) {\n\tuiTextItalicNormal,\n\tuiTextItalicOblique,\n\tuiTextItalicItalic,\n};\n\n// uiNewItalicAttribute() creates a new uiAttribute that changes the\n// italic mode of the text it is applied to. It is an error to specify an\n// italic mode not specified in uiTextItalic.\n_UI_EXTERN uiAttribute *uiNewItalicAttribute(uiTextItalic italic);\n\n// uiAttributeItalic() returns the font italic mode stored in a. It is an\n// error to call this on a uiAttribute that does not hold a font italic\n// mode.\n_UI_EXTERN uiTextItalic uiAttributeItalic(const uiAttribute *a);\n\n// uiTextStretch represents possible stretches (also called \"widths\")\n// of a font.\n//\n// Note that due to restrictions in early versions of Windows, some\n// fonts have \"special\" stretches be exposed in many programs as\n// separate font families. This is perhaps most notable with\n// Arial Condensed. libui does not do this, even on Windows (because\n// the DirectWrite API libui uses on Windows does not do this); to\n// specify Arial Condensed, use family Arial and stretch\n// uiTextStretchCondensed.\n_UI_ENUM(uiTextStretch) {\n\tuiTextStretchUltraCondensed,\n\tuiTextStretchExtraCondensed,\n\tuiTextStretchCondensed,\n\tuiTextStretchSemiCondensed,\n\tuiTextStretchNormal,\n\tuiTextStretchSemiExpanded,\n\tuiTextStretchExpanded,\n\tuiTextStretchExtraExpanded,\n\tuiTextStretchUltraExpanded,\n};\n\n// uiNewStretchAttribute() creates a new uiAttribute that changes the\n// stretch of the text it is applied to. It is an error to specify a strech\n// not specified in uiTextStretch.\n_UI_EXTERN uiAttribute *uiNewStretchAttribute(uiTextStretch stretch);\n\n// uiAttributeStretch() returns the font stretch stored in a. It is an\n// error to call this on a uiAttribute that does not hold a font stretch.\n_UI_EXTERN uiTextStretch uiAttributeStretch(const uiAttribute *a);\n\n// uiNewColorAttribute() creates a new uiAttribute that changes the\n// color of the text it is applied to. It is an error to specify an invalid\n// color.\n_UI_EXTERN uiAttribute *uiNewColorAttribute(double r, double g, double b, double a);\n\n// uiAttributeColor() returns the text color stored in a. It is an\n// error to call this on a uiAttribute that does not hold a text color.\n_UI_EXTERN void uiAttributeColor(const uiAttribute *a, double *r, double *g, double *b, double *alpha);\n\n// uiNewBackgroundAttribute() creates a new uiAttribute that\n// changes the background color of the text it is applied to. It is an\n// error to specify an invalid color.\n_UI_EXTERN uiAttribute *uiNewBackgroundAttribute(double r, double g, double b, double a);\n\n// TODO reuse uiAttributeColor() for background colors, or make a new function...\n\n// uiUnderline specifies a type of underline to use on text.\n_UI_ENUM(uiUnderline) {\n\tuiUnderlineNone,\n\tuiUnderlineSingle,\n\tuiUnderlineDouble,\n\tuiUnderlineSuggestion,\t\t// wavy or dotted underlines used for spelling/grammar checkers\n};\n\n// uiNewUnderlineAttribute() creates a new uiAttribute that changes\n// the type of underline on the text it is applied to. It is an error to\n// specify an underline type not specified in uiUnderline.\n_UI_EXTERN uiAttribute *uiNewUnderlineAttribute(uiUnderline u);\n\n// uiAttributeUnderline() returns the underline type stored in a. It is\n// an error to call this on a uiAttribute that does not hold an underline\n// style.\n_UI_EXTERN uiUnderline uiAttributeUnderline(const uiAttribute *a);\n\n// uiUnderlineColor specifies the color of any underline on the text it\n// is applied to, regardless of the type of underline. In addition to\n// being able to specify a custom color, you can explicitly specify\n// platform-specific colors for suggestion underlines; to use them\n// correctly, pair them with uiUnderlineSuggestion (though they can\n// be used on other types of underline as well).\n// \n// If an underline type is applied but no underline color is\n// specified, the text color is used instead. If an underline color\n// is specified without an underline type, the underline color\n// attribute is ignored, but not removed from the uiAttributedString.\n_UI_ENUM(uiUnderlineColor) {\n\tuiUnderlineColorCustom,\n\tuiUnderlineColorSpelling,\n\tuiUnderlineColorGrammar,\n\tuiUnderlineColorAuxiliary,\t\t// for instance, the color used by smart replacements on macOS or in Microsoft Office\n};\n\n// uiNewUnderlineColorAttribute() creates a new uiAttribute that\n// changes the color of the underline on the text it is applied to.\n// It is an error to specify an underline color not specified in\n// uiUnderlineColor.\n//\n// If the specified color type is uiUnderlineColorCustom, it is an\n// error to specify an invalid color value. Otherwise, the color values\n// are ignored and should be specified as zero.\n_UI_EXTERN uiAttribute *uiNewUnderlineColorAttribute(uiUnderlineColor u, double r, double g, double b, double a);\n\n// uiAttributeUnderlineColor() returns the underline color stored in\n// a. It is an error to call this on a uiAttribute that does not hold an\n// underline color.\n_UI_EXTERN void uiAttributeUnderlineColor(const uiAttribute *a, uiUnderlineColor *u, double *r, double *g, double *b, double *alpha);\n\n// uiOpenTypeFeatures represents a set of OpenType feature\n// tag-value pairs, for applying OpenType features to text.\n// OpenType feature tags are four-character codes defined by\n// OpenType that cover things from design features like small\n// caps and swashes to language-specific glyph shapes and\n// beyond. Each tag may only appear once in any given\n// uiOpenTypeFeatures instance. Each value is a 32-bit integer,\n// often used as a Boolean flag, but sometimes as an index to choose\n// a glyph shape to use.\n// \n// If a font does not support a certain feature, that feature will be\n// ignored. (TODO verify this on all OSs)\n// \n// See the OpenType specification at\n// https://www.microsoft.com/typography/otspec/featuretags.htm\n// for the complete list of available features, information on specific\n// features, and how to use them.\n// TODO invalid features\ntypedef struct uiOpenTypeFeatures uiOpenTypeFeatures;\n\n// uiOpenTypeFeaturesForEachFunc is the type of the function\n// invoked by uiOpenTypeFeaturesForEach() for every OpenType\n// feature in otf. Refer to that function's documentation for more\n// details.\ntypedef uiForEach (*uiOpenTypeFeaturesForEachFunc)(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value, void *data);\n\n// @role uiOpenTypeFeatures constructor\n// uiNewOpenTypeFeatures() returns a new uiOpenTypeFeatures\n// instance, with no tags yet added.\n_UI_EXTERN uiOpenTypeFeatures *uiNewOpenTypeFeatures(void);\n\n// @role uiOpenTypeFeatures destructor\n// uiFreeOpenTypeFeatures() frees otf.\n_UI_EXTERN void uiFreeOpenTypeFeatures(uiOpenTypeFeatures *otf);\n\n// uiOpenTypeFeaturesClone() makes a copy of otf and returns it.\n// Changing one will not affect the other.\n_UI_EXTERN uiOpenTypeFeatures *uiOpenTypeFeaturesClone(const uiOpenTypeFeatures *otf);\n\n// uiOpenTypeFeaturesAdd() adds the given feature tag and value\n// to otf. The feature tag is specified by a, b, c, and d. If there is\n// already a value associated with the specified tag in otf, the old\n// value is removed.\n_UI_EXTERN void uiOpenTypeFeaturesAdd(uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value);\n\n// uiOpenTypeFeaturesRemove() removes the given feature tag\n// and value from otf. If the tag is not present in otf,\n// uiOpenTypeFeaturesRemove() does nothing.\n_UI_EXTERN void uiOpenTypeFeaturesRemove(uiOpenTypeFeatures *otf, char a, char b, char c, char d);\n\n// uiOpenTypeFeaturesGet() determines whether the given feature\n// tag is present in otf. If it is, *value is set to the tag's value and\n// nonzero is returned. Otherwise, zero is returned.\n// \n// Note that if uiOpenTypeFeaturesGet() returns zero, value isn't\n// changed. This is important: if a feature is not present in a\n// uiOpenTypeFeatures, the feature is NOT treated as if its\n// value was zero anyway. Script-specific font shaping rules and\n// font-specific feature settings may use a different default value\n// for a feature. You should likewise not treat a missing feature as\n// having a value of zero either. Instead, a missing feature should\n// be treated as having some unspecified default value.\n_UI_EXTERN int uiOpenTypeFeaturesGet(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t *value);\n\n// uiOpenTypeFeaturesForEach() executes f for every tag-value\n// pair in otf. The enumeration order is unspecified. You cannot\n// modify otf while uiOpenTypeFeaturesForEach() is running.\n_UI_EXTERN void uiOpenTypeFeaturesForEach(const uiOpenTypeFeatures *otf, uiOpenTypeFeaturesForEachFunc f, void *data);\n\n// uiNewFeaturesAttribute() creates a new uiAttribute that changes\n// the font family of the text it is applied to. otf is copied; you may\n// free it after uiNewFeaturesAttribute() returns.\n_UI_EXTERN uiAttribute *uiNewFeaturesAttribute(const uiOpenTypeFeatures *otf);\n\n// uiAttributeFeatures() returns the OpenType features stored in a.\n// The returned uiOpenTypeFeatures object is owned by a. It is an\n// error to call this on a uiAttribute that does not hold OpenType\n// features.\n_UI_EXTERN const uiOpenTypeFeatures *uiAttributeFeatures(const uiAttribute *a);\n\n// uiAttributedString represents a string of UTF-8 text that can\n// optionally be embellished with formatting attributes. libui\n// provides the list of formatting attributes, which cover common\n// formatting traits like boldface and color as well as advanced\n// typographical features provided by OpenType like superscripts\n// and small caps. These attributes can be combined in a variety of\n// ways.\n//\n// Attributes are applied to runs of Unicode codepoints in the string.\n// Zero-length runs are elided. Consecutive runs that have the same\n// attribute type and value are merged. Each attribute is independent\n// of each other attribute; overlapping attributes of different types\n// do not split each other apart, but different values of the same\n// attribute type do.\n//\n// The empty string can also be represented by uiAttributedString,\n// but because of the no-zero-length-attribute rule, it will not have\n// attributes.\n//\n// A uiAttributedString takes ownership of all attributes given to\n// it, as it may need to duplicate or delete uiAttribute objects at\n// any time. By extension, when you free a uiAttributedString,\n// all uiAttributes within will also be freed. Each method will\n// describe its own rules in more details.\n//\n// In addition, uiAttributedString provides facilities for moving\n// between grapheme clusters, which represent a character\n// from the point of view of the end user. The cursor of a text editor\n// is always placed on a grapheme boundary, so you can use these\n// features to move the cursor left or right by one \"character\".\n// TODO does uiAttributedString itself need this\n//\n// uiAttributedString does not provide enough information to be able\n// to draw itself onto a uiDrawContext or respond to user actions.\n// In order to do that, you'll need to use a uiDrawTextLayout, which\n// is built from the combination of a uiAttributedString and a set of\n// layout-specific properties.\ntypedef struct uiAttributedString uiAttributedString;\n\n// uiAttributedStringForEachAttributeFunc is the type of the function\n// invoked by uiAttributedStringForEachAttribute() for every\n// attribute in s. Refer to that function's documentation for more\n// details.\ntypedef uiForEach (*uiAttributedStringForEachAttributeFunc)(const uiAttributedString *s, const uiAttribute *a, size_t start, size_t end, void *data);\n\n// @role uiAttributedString constructor\n// uiNewAttributedString() creates a new uiAttributedString from\n// initialString. The string will be entirely unattributed.\n_UI_EXTERN uiAttributedString *uiNewAttributedString(const char *initialString);\n\n// @role uiAttributedString destructor\n// uiFreeAttributedString() destroys the uiAttributedString s.\n// It will also free all uiAttributes within.\n_UI_EXTERN void uiFreeAttributedString(uiAttributedString *s);\n\n// uiAttributedStringString() returns the textual content of s as a\n// '\\0'-terminated UTF-8 string. The returned pointer is valid until\n// the next change to the textual content of s.\n_UI_EXTERN const char *uiAttributedStringString(const uiAttributedString *s);\n\n// uiAttributedStringLength() returns the number of UTF-8 bytes in\n// the textual content of s, excluding the terminating '\\0'.\n_UI_EXTERN size_t uiAttributedStringLen(const uiAttributedString *s);\n\n// uiAttributedStringAppendUnattributed() adds the '\\0'-terminated\n// UTF-8 string str to the end of s. The new substring will be\n// unattributed.\n_UI_EXTERN void uiAttributedStringAppendUnattributed(uiAttributedString *s, const char *str);\n\n// uiAttributedStringInsertAtUnattributed() adds the '\\0'-terminated\n// UTF-8 string str to s at the byte position specified by at. The new\n// substring will be unattributed; existing attributes will be moved\n// along with their text.\n_UI_EXTERN void uiAttributedStringInsertAtUnattributed(uiAttributedString *s, const char *str, size_t at);\n\n// TODO add the Append and InsertAtExtendingAttributes functions\n// TODO and add functions that take a string + length\n\n// uiAttributedStringDelete() deletes the characters and attributes of\n// s in the byte range [start, end).\n_UI_EXTERN void uiAttributedStringDelete(uiAttributedString *s, size_t start, size_t end);\n\n// 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\n\n// uiAttributedStringSetAttribute() sets a in the byte range [start, end)\n// of s. Any existing attributes in that byte range of the same type are\n// removed. s takes ownership of a; you should not use it after\n// uiAttributedStringSetAttribute() returns.\n_UI_EXTERN void uiAttributedStringSetAttribute(uiAttributedString *s, uiAttribute *a, size_t start, size_t end);\n\n// uiAttributedStringForEachAttribute() enumerates all the\n// uiAttributes in s. It is an error to modify s in f. Within f, s still\n// owns the attribute; you can neither free it nor save it for later\n// use.\n// TODO reword the above for consistency (TODO and find out what I meant by that)\n// 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\n_UI_EXTERN void uiAttributedStringForEachAttribute(const uiAttributedString *s, uiAttributedStringForEachAttributeFunc f, void *data);\n\n// TODO const correct this somehow (the implementation needs to mutate the structure)\n_UI_EXTERN size_t uiAttributedStringNumGraphemes(uiAttributedString *s);\n\n// TODO const correct this somehow (the implementation needs to mutate the structure)\n_UI_EXTERN size_t uiAttributedStringByteIndexToGrapheme(uiAttributedString *s, size_t pos);\n\n// TODO const correct this somehow (the implementation needs to mutate the structure)\n_UI_EXTERN size_t uiAttributedStringGraphemeToByteIndex(uiAttributedString *s, size_t pos);\n\n// uiFontDescriptor provides a complete description of a font where\n// one is needed. Currently, this means as the default font of a\n// uiDrawTextLayout and as the data returned by uiFontButton.\n// All the members operate like the respective uiAttributes.\ntypedef struct uiFontDescriptor uiFontDescriptor;\n\nstruct uiFontDescriptor {\n\t// TODO const-correct this or figure out how to deal with this when getting a value\n\tchar *Family;\n\tdouble Size;\n\tuiTextWeight Weight;\n\tuiTextItalic Italic;\n\tuiTextStretch Stretch;\n};\n\n// uiDrawTextLayout is a concrete representation of a\n// uiAttributedString that can be displayed in a uiDrawContext.\n// It includes information important for the drawing of a block of\n// text, including the bounding box to wrap the text within, the\n// alignment of lines of text within that box, areas to mark as\n// being selected, and other things.\n//\n// Unlike uiAttributedString, the content of a uiDrawTextLayout is\n// immutable once it has been created.\n//\n// TODO talk about OS-specific differences with text drawing that libui can't account for...\ntypedef struct uiDrawTextLayout uiDrawTextLayout;\n\n// uiDrawTextAlign specifies the alignment of lines of text in a\n// uiDrawTextLayout.\n// TODO should this really have Draw in the name?\n_UI_ENUM(uiDrawTextAlign) {\n\tuiDrawTextAlignLeft,\n\tuiDrawTextAlignCenter,\n\tuiDrawTextAlignRight,\n};\n\n// uiDrawTextLayoutParams describes a uiDrawTextLayout.\n// DefaultFont is used to render any text that is not attributed\n// sufficiently in String. Width determines the width of the bounding\n// box of the text; the height is determined automatically.\ntypedef struct uiDrawTextLayoutParams uiDrawTextLayoutParams;\n\n// TODO const-correct this somehow\nstruct uiDrawTextLayoutParams {\n\tuiAttributedString *String;\n\tuiFontDescriptor *DefaultFont;\n\tdouble Width;\n\tuiDrawTextAlign Align;\n};\n\n// @role uiDrawTextLayout constructor\n// uiDrawNewTextLayout() creates a new uiDrawTextLayout from\n// the given parameters.\n//\n// TODO\n// - allow creating a layout out of a substring\n// - allow marking compositon strings\n// - allow marking selections, even after creation\n// - add the following functions:\n// \t- uiDrawTextLayoutHeightForWidth() (returns the height that a layout would need to be to display the entire string at a given width)\n// \t- uiDrawTextLayoutRangeForSize() (returns what substring would fit in a given size)\n// \t- uiDrawTextLayoutNewWithHeight() (limits amount of string used by the height)\n// - some function to fix up a range (for text editing)\n_UI_EXTERN uiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *params);\n\n// @role uiDrawFreeTextLayout destructor\n// uiDrawFreeTextLayout() frees tl. The underlying\n// uiAttributedString is not freed.\n_UI_EXTERN void uiDrawFreeTextLayout(uiDrawTextLayout *tl);\n\n// uiDrawText() draws tl in c with the top-left point of tl at (x, y).\n_UI_EXTERN void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y);\n\n// uiDrawTextLayoutExtents() returns the width and height of tl\n// in width and height. The returned width may be smaller than\n// the width passed into uiDrawNewTextLayout() depending on\n// how the text in tl is wrapped. Therefore, you can use this\n// function to get the actual size of the text layout.\n_UI_EXTERN void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height);\n\n// TODO metrics functions\n\n// TODO number of lines visible for clipping rect, range visible for clipping rect?\n\n// uiFontButton is a button that allows users to choose a font when they click on it.\ntypedef struct uiFontButton uiFontButton;\n#define uiFontButton(this) ((uiFontButton *) (this))\n// uiFontButtonFont() returns the font currently selected in the uiFontButton in desc.\n// uiFontButtonFont() allocates resources in desc; when you are done with the font, call uiFreeFontButtonFont() to release them.\n// uiFontButtonFont() does not allocate desc itself; you must do so.\n// TODO have a function that sets an entire font descriptor to a range in a uiAttributedString at once, for SetFont?\n_UI_EXTERN void uiFontButtonFont(uiFontButton *b, uiFontDescriptor *desc);\n// TOOD SetFont, mechanics\n// uiFontButtonOnChanged() sets the function that is called when the font in the uiFontButton is changed.\n_UI_EXTERN void uiFontButtonOnChanged(uiFontButton *b, void (*f)(uiFontButton *, void *), void *data);\n// uiNewFontButton() creates a new uiFontButton. The default font selected into the uiFontButton is OS-defined.\n_UI_EXTERN uiFontButton *uiNewFontButton(void);\n// uiFreeFontButtonFont() frees resources allocated in desc by uiFontButtonFont().\n// 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).\n// Calling uiFreeFontButtonFont() on a uiFontDescriptor not returned by uiFontButtonFont() results in undefined behavior.\n_UI_EXTERN void uiFreeFontButtonFont(uiFontDescriptor *desc);\n\n_UI_ENUM(uiModifiers) {\n\tuiModifierCtrl = 1 << 0,\n\tuiModifierAlt = 1 << 1,\n\tuiModifierShift = 1 << 2,\n\tuiModifierSuper = 1 << 3,\n};\n\n// TODO document drag captures\nstruct uiAreaMouseEvent {\n\t// TODO document what these mean for scrolling areas\n\tdouble X;\n\tdouble Y;\n\n\t// TODO see draw above\n\tdouble AreaWidth;\n\tdouble AreaHeight;\n\n\tint Down;\n\tint Up;\n\n\tint Count;\n\n\tuiModifiers Modifiers;\n\n\tuint64_t Held1To64;\n};\n\n_UI_ENUM(uiExtKey) {\n\tuiExtKeyEscape = 1,\n\tuiExtKeyInsert,\t\t\t// equivalent to \"Help\" on Apple keyboards\n\tuiExtKeyDelete,\n\tuiExtKeyHome,\n\tuiExtKeyEnd,\n\tuiExtKeyPageUp,\n\tuiExtKeyPageDown,\n\tuiExtKeyUp,\n\tuiExtKeyDown,\n\tuiExtKeyLeft,\n\tuiExtKeyRight,\n\tuiExtKeyF1,\t\t\t// F1..F12 are guaranteed to be consecutive\n\tuiExtKeyF2,\n\tuiExtKeyF3,\n\tuiExtKeyF4,\n\tuiExtKeyF5,\n\tuiExtKeyF6,\n\tuiExtKeyF7,\n\tuiExtKeyF8,\n\tuiExtKeyF9,\n\tuiExtKeyF10,\n\tuiExtKeyF11,\n\tuiExtKeyF12,\n\tuiExtKeyN0,\t\t\t// numpad keys; independent of Num Lock state\n\tuiExtKeyN1,\t\t\t// N0..N9 are guaranteed to be consecutive\n\tuiExtKeyN2,\n\tuiExtKeyN3,\n\tuiExtKeyN4,\n\tuiExtKeyN5,\n\tuiExtKeyN6,\n\tuiExtKeyN7,\n\tuiExtKeyN8,\n\tuiExtKeyN9,\n\tuiExtKeyNDot,\n\tuiExtKeyNEnter,\n\tuiExtKeyNAdd,\n\tuiExtKeyNSubtract,\n\tuiExtKeyNMultiply,\n\tuiExtKeyNDivide,\n};\n\nstruct uiAreaKeyEvent {\n\tchar Key;\n\tuiExtKey ExtKey;\n\tuiModifiers Modifier;\n\n\tuiModifiers Modifiers;\n\n\tint Up;\n};\n\ntypedef struct uiColorButton uiColorButton;\n#define uiColorButton(this) ((uiColorButton *) (this))\n_UI_EXTERN void uiColorButtonColor(uiColorButton *b, double *r, double *g, double *bl, double *a);\n_UI_EXTERN void uiColorButtonSetColor(uiColorButton *b, double r, double g, double bl, double a);\n_UI_EXTERN void uiColorButtonOnChanged(uiColorButton *b, void (*f)(uiColorButton *, void *), void *data);\n_UI_EXTERN uiColorButton *uiNewColorButton(void);\n\ntypedef struct uiForm uiForm;\n#define uiForm(this) ((uiForm *) (this))\n_UI_EXTERN void uiFormAppend(uiForm *f, const char *label, uiControl *c, int stretchy);\n_UI_EXTERN void uiFormDelete(uiForm *f, int index);\n_UI_EXTERN int uiFormPadded(uiForm *f);\n_UI_EXTERN void uiFormSetPadded(uiForm *f, int padded);\n_UI_EXTERN uiForm *uiNewForm(void);\n\n_UI_ENUM(uiAlign) {\n\tuiAlignFill,\n\tuiAlignStart,\n\tuiAlignCenter,\n\tuiAlignEnd,\n};\n\n_UI_ENUM(uiAt) {\n\tuiAtLeading,\n\tuiAtTop,\n\tuiAtTrailing,\n\tuiAtBottom,\n};\n\ntypedef struct uiGrid uiGrid;\n#define uiGrid(this) ((uiGrid *) (this))\n_UI_EXTERN void uiGridAppend(uiGrid *g, uiControl *c, int left, int top, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign);\n_UI_EXTERN void uiGridInsertAt(uiGrid *g, uiControl *c, uiControl *existing, uiAt at, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign);\n_UI_EXTERN int uiGridPadded(uiGrid *g);\n_UI_EXTERN void uiGridSetPadded(uiGrid *g, int padded);\n_UI_EXTERN uiGrid *uiNewGrid(void);\n\n// uiImage stores an image for display on screen.\n// \n// Images are built from one or more representations, each with the\n// same aspect ratio but a different pixel size. libui automatically\n// selects the most appropriate representation for drawing the image\n// when it comes time to draw the image; what this means depends\n// on the pixel density of the target context. Therefore, one can use\n// uiImage to draw higher-detailed images on higher-density\n// displays. The typical use cases are either:\n// \n// \t- have just a single representation, at which point all screens\n// \t  use the same image, and thus uiImage acts like a simple\n// \t  bitmap image, or\n// \t- have two images, one at normal resolution and one at 2x\n// \t  resolution; this matches the current expectations of some\n// \t  desktop systems at the time of writing (mid-2018)\n// \n// uiImage is very simple: it only supports premultiplied 32-bit\n// RGBA images, and libui does not provide any image file loading\n// or image format conversion utilities on top of that.\ntypedef struct uiImage uiImage;\n\n// @role uiImage constructor\n// uiNewImage creates a new uiImage with the given width and\n// height. This width and height should be the size in points of the\n// image in the device-independent case; typically this is the 1x size.\n// TODO for all uiImage functions: use const void * for const correctness\n_UI_EXTERN uiImage *uiNewImage(double width, double height);\n\n// @role uiImage destructor\n// uiFreeImage frees the given image and all associated resources.\n_UI_EXTERN void uiFreeImage(uiImage *i);\n\n// uiImageAppend adds a representation to the uiImage.\n// pixels should point to a byte array of premultiplied pixels\n// stored in [R G B A] order (so ((uint8_t *) pixels)[0] is the R of the\n// first pixel and [3] is the A of the first pixel). pixelWidth and\n// pixelHeight is the size *in pixels* of the image, and pixelStride is\n// the number *of bytes* per row of the pixels array. Therefore,\n// pixels itself must be at least byteStride * pixelHeight bytes long.\n// TODO see if we either need the stride or can provide a way to get the OS-preferred stride (in cairo we do)\n_UI_EXTERN void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int byteStride);\n\n// uiTableValue stores a value to be passed along uiTable and\n// uiTableModel.\n//\n// You do not create uiTableValues directly; instead, you create a\n// uiTableValue of a given type using the specialized constructor\n// functions.\n//\n// uiTableValues are immutable and the uiTableModel and uiTable\n// take ownership of the uiTableValue object once returned, copying\n// its contents as necessary.\ntypedef struct uiTableValue uiTableValue;\n\n// @role uiTableValue destructor\n// uiFreeTableValue() frees a uiTableValue. You generally do not\n// need to call this yourself, as uiTable and uiTableModel do this\n// for you. In fact, it is an error to call this function on a uiTableValue\n// that has been given to a uiTable or uiTableModel. You can call this,\n// however, if you created a uiTableValue that you aren't going to\n// use later, or if you called a uiTableModelHandler method directly\n// and thus never transferred ownership of the uiTableValue.\n_UI_EXTERN void uiFreeTableValue(uiTableValue *v);\n\n// uiTableValueType holds the possible uiTableValue types that may\n// be returned by uiTableValueGetType(). Refer to the documentation\n// for each type's constructor function for details on each type.\n// TODO actually validate these\n_UI_ENUM(uiTableValueType) {\n\tuiTableValueTypeString,\n\tuiTableValueTypeImage,\n\tuiTableValueTypeInt,\n\tuiTableValueTypeColor,\n};\n\n// uiTableValueGetType() returns the type of v.\n// TODO I don't like this name\n_UI_EXTERN uiTableValueType uiTableValueGetType(const uiTableValue *v);\n\n// uiNewTableValueString() returns a new uiTableValue that contains\n// str. str is copied; you do not need to keep it alive after\n// uiNewTableValueString() returns.\n_UI_EXTERN uiTableValue *uiNewTableValueString(const char *str);\n\n// uiTableValueString() returns the string stored in v. The returned\n// string is owned by v. It is an error to call this on a uiTableValue\n// that does not hold a string.\n_UI_EXTERN const char *uiTableValueString(const uiTableValue *v);\n\n// uiNewTableValueImage() returns a new uiTableValue that contains\n// the given uiImage.\n// \n// Unlike other similar constructors, uiNewTableValueImage() does\n// NOT copy the image. This is because images are comparatively\n// larger than the other objects in question. Therefore, you MUST\n// keep the image alive as long as the returned uiTableValue is alive.\n// As a general rule, if libui calls a uiTableModelHandler method, the\n// uiImage is safe to free once any of your code is once again\n// executed.\n_UI_EXTERN uiTableValue *uiNewTableValueImage(uiImage *img);\n\n// uiTableValueImage() returns the uiImage stored in v. As these\n// images are not owned by v, you should not assume anything\n// about the lifetime of the image (unless you created the image,\n// and thus control its lifetime). It is an error to call this on a\n// uiTableValue that does not hold an image.\n_UI_EXTERN uiImage *uiTableValueImage(const uiTableValue *v);\n\n// uiNewTableValueInt() returns a uiTableValue that stores the given\n// int. This can be used both for boolean values (nonzero is true, as\n// in C) or progresses (in which case the valid range is -1..100\n// inclusive).\n_UI_EXTERN uiTableValue *uiNewTableValueInt(int i);\n\n// uiTableValueInt() returns the int stored in v. It is an error to call\n// this on a uiTableValue that does not store an int.\n_UI_EXTERN int uiTableValueInt(const uiTableValue *v);\n\n// uiNewTableValueColor() returns a uiTableValue that stores the\n// given color.\n_UI_EXTERN uiTableValue *uiNewTableValueColor(double r, double g, double b, double a);\n\n// uiTableValueColor() returns the color stored in v. It is an error to\n// call this on a uiTableValue that does not store a color.\n// TODO define whether all this, for both uiTableValue and uiAttribute, is undefined behavior or a caught error\n_UI_EXTERN void uiTableValueColor(const uiTableValue *v, double *r, double *g, double *b, double *a);\n\n// uiTableModel is an object that provides the data for a uiTable.\n// This data is returned via methods you provide in the\n// uiTableModelHandler struct.\n//\n// uiTableModel represents data using a table, but this table does\n// not map directly to uiTable itself. Instead, you can have data\n// columns which provide instructions for how to render a given\n// uiTable's column — for instance, one model column can be used\n// to give certain rows of a uiTable a different background color.\n// Row numbers DO match with uiTable row numbers.\n//\n// Once created, the number and data types of columns of a\n// uiTableModel cannot change.\n//\n// Row and column numbers start at 0. A uiTableModel can be\n// associated with more than one uiTable at a time.\ntypedef struct uiTableModel uiTableModel;\n\n// uiTableModelHandler defines the methods that uiTableModel\n// calls when it needs data. Once a uiTableModel is created, these\n// methods cannot change.\ntypedef struct uiTableModelHandler uiTableModelHandler;\n\n// TODO validate ranges; validate types on each getter/setter call (? table columns only?)\nstruct uiTableModelHandler {\n\t// NumColumns returns the number of model columns in the\n\t// uiTableModel. This value must remain constant through the\n\t// lifetime of the uiTableModel. This method is not guaranteed\n\t// to be called depending on the system.\n\t// TODO strongly check column numbers and types on all platforms so these clauses can go away\n\tint (*NumColumns)(uiTableModelHandler *, uiTableModel *);\n\t// ColumnType returns the value type of the data stored in\n\t// the given model column of the uiTableModel. The returned\n\t// values must remain constant through the lifetime of the\n\t// uiTableModel. This method is not guaranteed to be called\n\t// depending on the system.\n\tuiTableValueType (*ColumnType)(uiTableModelHandler *, uiTableModel *, int);\n\t// NumRows returns the number or rows in the uiTableModel.\n\t// This value must be non-negative.\n\tint (*NumRows)(uiTableModelHandler *, uiTableModel *);\n\t// CellValue returns a uiTableValue corresponding to the model\n\t// cell at (row, column). The type of the returned uiTableValue\n\t// must match column's value type. Under some circumstances,\n\t// NULL may be returned; refer to the various methods that add\n\t// columns to uiTable for details. Once returned, the uiTable\n\t// that calls CellValue will free the uiTableValue returned.\n\tuiTableValue *(*CellValue)(uiTableModelHandler *mh, uiTableModel *m, int row, int column);\n\t// SetCellValue changes the model cell value at (row, column)\n\t// in the uiTableModel. Within this function, either do nothing\n\t// to keep the current cell value or save the new cell value as\n\t// appropriate. After SetCellValue is called, the uiTable will\n\t// itself reload the table cell. Under certain conditions, the\n\t// uiTableValue passed in can be NULL; refer to the various\n\t// methods that add columns to uiTable for details. Once\n\t// returned, the uiTable that called SetCellValue will free the\n\t// uiTableValue passed in.\n\tvoid (*SetCellValue)(uiTableModelHandler *, uiTableModel *, int, int, const uiTableValue *);\n};\n\n// @role uiTableModel constructor\n// uiNewTableModel() creates a new uiTableModel with the given\n// handler methods.\n_UI_EXTERN uiTableModel *uiNewTableModel(uiTableModelHandler *mh);\n\n// @role uiTableModel destructor\n// uiFreeTableModel() frees the given table model. It is an error to\n// free table models currently associated with a uiTable.\n_UI_EXTERN void uiFreeTableModel(uiTableModel *m);\n\n// uiTableModelRowInserted() tells any uiTable associated with m\n// that a new row has been added to m at index index. You call\n// this function when the number of rows in your model has\n// changed; after calling it, NumRows() should returm the new row\n// count.\n_UI_EXTERN void uiTableModelRowInserted(uiTableModel *m, int newIndex);\n\n// uiTableModelRowChanged() tells any uiTable associated with m\n// that the data in the row at index has changed. You do not need to\n// call this in your SetCellValue() handlers, but you do need to call\n// this if your data changes at some other point.\n_UI_EXTERN void uiTableModelRowChanged(uiTableModel *m, int index);\n\n// uiTableModelRowDeleted() tells any uiTable associated with m\n// that the row at index index has been deleted. You call this\n// function when the number of rows in your model has changed;\n// after calling it, NumRows() should returm the new row\n// count.\n// TODO for this and Inserted: make sure the \"after\" part is right; clarify if it's after returning or after calling\n_UI_EXTERN void uiTableModelRowDeleted(uiTableModel *m, int oldIndex);\n// TODO reordering/moving\n\n// uiTableModelColumnNeverEditable and\n// uiTableModelColumnAlwaysEditable are the value of an editable\n// model column parameter to one of the uiTable create column\n// functions; if used, that jparticular uiTable colum is not editable\n// by the user and always editable by the user, respectively.\n#define uiTableModelColumnNeverEditable (-1)\n#define uiTableModelColumnAlwaysEditable (-2)\n\n// uiTableTextColumnOptionalParams are the optional parameters\n// that control the appearance of the text column of a uiTable.\ntypedef struct uiTableTextColumnOptionalParams uiTableTextColumnOptionalParams;\n\n// uiTableParams defines the parameters passed to uiNewTable().\ntypedef struct uiTableParams uiTableParams;\n\nstruct uiTableTextColumnOptionalParams {\n\t// ColorModelColumn is the model column containing the\n\t// text color of this uiTable column's text, or -1 to use the\n\t// default color.\n\t//\n\t// If CellValue() for this column for any cell returns NULL, that\n\t// cell will also use the default text color.\n\tint ColorModelColumn;\n};\n\nstruct uiTableParams {\n\t// Model is the uiTableModel to use for this uiTable.\n\t// This parameter cannot be NULL.\n\tuiTableModel *Model;\n\t// RowBackgroundColorModelColumn is a model column\n\t// number that defines the background color used for the\n\t// entire row in the uiTable, or -1 to use the default color for\n\t// all rows.\n\t//\n\t// If CellValue() for this column for any row returns NULL, that\n\t// row will also use the default background color.\n\tint RowBackgroundColorModelColumn;\n};\n\n// uiTable is a uiControl that shows tabular data, allowing users to\n// manipulate rows of such data at a time.\ntypedef struct uiTable uiTable;\n#define uiTable(this) ((uiTable *) (this))\n\n// uiTableAppendTextColumn() appends a text column to t.\n// name is displayed in the table header.\n// textModelColumn is where the text comes from.\n// If a row is editable according to textEditableModelColumn,\n// SetCellValue() is called with textModelColumn as the column.\n_UI_EXTERN void uiTableAppendTextColumn(uiTable *t,\n\tconst char *name,\n\tint textModelColumn,\n\tint textEditableModelColumn,\n\tuiTableTextColumnOptionalParams *textParams);\n\n// uiTableAppendImageColumn() appends an image column to t.\n// Images are drawn at icon size, appropriate to the pixel density\n// of the screen showing the uiTable.\n_UI_EXTERN void uiTableAppendImageColumn(uiTable *t,\n\tconst char *name,\n\tint imageModelColumn);\n\n// uiTableAppendImageTextColumn() appends a column to t that\n// shows both an image and text.\n_UI_EXTERN void uiTableAppendImageTextColumn(uiTable *t,\n\tconst char *name,\n\tint imageModelColumn,\n\tint textModelColumn,\n\tint textEditableModelColumn,\n\tuiTableTextColumnOptionalParams *textParams);\n\n// uiTableAppendCheckboxColumn appends a column to t that\n// contains a checkbox that the user can interact with (assuming the\n// checkbox is editable). SetCellValue() will be called with\n// checkboxModelColumn as the column in this case.\n_UI_EXTERN void uiTableAppendCheckboxColumn(uiTable *t,\n\tconst char *name,\n\tint checkboxModelColumn,\n\tint checkboxEditableModelColumn);\n\n// uiTableAppendCheckboxTextColumn() appends a column to t\n// that contains both a checkbox and text.\n_UI_EXTERN void uiTableAppendCheckboxTextColumn(uiTable *t,\n\tconst char *name,\n\tint checkboxModelColumn,\n\tint checkboxEditableModelColumn,\n\tint textModelColumn,\n\tint textEditableModelColumn,\n\tuiTableTextColumnOptionalParams *textParams);\n\n// uiTableAppendProgressBarColumn() appends a column to t\n// that displays a progress bar. These columns work like\n// uiProgressBar: a cell value of 0..100 displays that percentage, and\n// a cell value of -1 displays an indeterminate progress bar.\n_UI_EXTERN void uiTableAppendProgressBarColumn(uiTable *t,\n\tconst char *name,\n\tint progressModelColumn);\n\n// uiTableAppendButtonColumn() appends a column to t\n// that shows a button that the user can click on. When the user\n// does click on the button, SetCellValue() is called with a NULL\n// value and buttonModelColumn as the column.\n// CellValue() on buttonModelColumn should return the text to show\n// in the button.\n_UI_EXTERN void uiTableAppendButtonColumn(uiTable *t,\n\tconst char *name,\n\tint buttonModelColumn,\n\tint buttonClickableModelColumn);\n\n// uiNewTable() creates a new uiTable with the specified parameters.\n_UI_EXTERN uiTable *uiNewTable(uiTableParams *params);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "ui_darwin.h",
    "content": "// 7 april 2015\n\n/*\nThis file assumes that you have imported <Cocoa/Cocoa.h> and \"ui.h\" beforehand. It provides API-specific functions for interfacing with foreign controls on Mac OS X.\n*/\n\n#ifndef __LIBUI_UI_DARWIN_H__\n#define __LIBUI_UI_DARWIN_H__\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef struct uiDarwinControl uiDarwinControl;\nstruct uiDarwinControl {\n\tuiControl c;\n\tuiControl *parent;\n\tBOOL enabled;\n\tBOOL visible;\n\tvoid (*SyncEnableState)(uiDarwinControl *, int);\n\tvoid (*SetSuperview)(uiDarwinControl *, NSView *);\n\tBOOL (*HugsTrailingEdge)(uiDarwinControl *);\n\tBOOL (*HugsBottom)(uiDarwinControl *);\n\tvoid (*ChildEdgeHuggingChanged)(uiDarwinControl *);\n\tNSLayoutPriority (*HuggingPriority)(uiDarwinControl *, NSLayoutConstraintOrientation);\n\tvoid (*SetHuggingPriority)(uiDarwinControl *, NSLayoutPriority, NSLayoutConstraintOrientation);\n\tvoid (*ChildVisibilityChanged)(uiDarwinControl *);\n};\n#define uiDarwinControl(this) ((uiDarwinControl *) (this))\n// TODO document\n_UI_EXTERN void uiDarwinControlSyncEnableState(uiDarwinControl *, int);\n_UI_EXTERN void uiDarwinControlSetSuperview(uiDarwinControl *, NSView *);\n_UI_EXTERN BOOL uiDarwinControlHugsTrailingEdge(uiDarwinControl *);\n_UI_EXTERN BOOL uiDarwinControlHugsBottom(uiDarwinControl *);\n_UI_EXTERN void uiDarwinControlChildEdgeHuggingChanged(uiDarwinControl *);\n_UI_EXTERN NSLayoutPriority uiDarwinControlHuggingPriority(uiDarwinControl *, NSLayoutConstraintOrientation);\n_UI_EXTERN void uiDarwinControlSetHuggingPriority(uiDarwinControl *, NSLayoutPriority, NSLayoutConstraintOrientation);\n_UI_EXTERN void uiDarwinControlChildVisibilityChanged(uiDarwinControl *);\n\n#define uiDarwinControlDefaultDestroy(type, handlefield) \\\n\tstatic void type ## Destroy(uiControl *c) \\\n\t{ \\\n\t\t[type(c)->handlefield release]; \\\n\t\tuiFreeControl(c); \\\n\t}\n#define uiDarwinControlDefaultHandle(type, handlefield) \\\n\tstatic uintptr_t type ## Handle(uiControl *c) \\\n\t{ \\\n\t\treturn (uintptr_t) (type(c)->handlefield); \\\n\t}\n#define uiDarwinControlDefaultParent(type, handlefield) \\\n\tstatic uiControl *type ## Parent(uiControl *c) \\\n\t{ \\\n\t\treturn uiDarwinControl(c)->parent; \\\n\t}\n#define uiDarwinControlDefaultSetParent(type, handlefield) \\\n\tstatic void type ## SetParent(uiControl *c, uiControl *parent) \\\n\t{ \\\n\t\tuiControlVerifySetParent(c, parent); \\\n\t\tuiDarwinControl(c)->parent = parent; \\\n\t}\n#define uiDarwinControlDefaultToplevel(type, handlefield) \\\n\tstatic int type ## Toplevel(uiControl *c) \\\n\t{ \\\n\t\treturn 0; \\\n\t}\n#define uiDarwinControlDefaultVisible(type, handlefield) \\\n\tstatic int type ## Visible(uiControl *c) \\\n\t{ \\\n\t\treturn uiDarwinControl(c)->visible; \\\n\t}\n#define uiDarwinControlDefaultShow(type, handlefield) \\\n\tstatic void type ## Show(uiControl *c) \\\n\t{ \\\n\t\tuiDarwinControl(c)->visible = YES; \\\n\t\t[type(c)->handlefield setHidden:NO]; \\\n\t\tuiDarwinNotifyVisibilityChanged(uiDarwinControl(c)); \\\n\t}\n#define uiDarwinControlDefaultHide(type, handlefield) \\\n\tstatic void type ## Hide(uiControl *c) \\\n\t{ \\\n\t\tuiDarwinControl(c)->visible = NO; \\\n\t\t[type(c)->handlefield setHidden:YES]; \\\n\t\tuiDarwinNotifyVisibilityChanged(uiDarwinControl(c)); \\\n\t}\n#define uiDarwinControlDefaultEnabled(type, handlefield) \\\n\tstatic int type ## Enabled(uiControl *c) \\\n\t{ \\\n\t\treturn uiDarwinControl(c)->enabled; \\\n\t}\n#define uiDarwinControlDefaultEnable(type, handlefield) \\\n\tstatic void type ## Enable(uiControl *c) \\\n\t{ \\\n\t\tuiDarwinControl(c)->enabled = YES; \\\n\t\tuiDarwinControlSyncEnableState(uiDarwinControl(c), uiControlEnabledToUser(c)); \\\n\t}\n#define uiDarwinControlDefaultDisable(type, handlefield) \\\n\tstatic void type ## Disable(uiControl *c) \\\n\t{ \\\n\t\tuiDarwinControl(c)->enabled = NO; \\\n\t\tuiDarwinControlSyncEnableState(uiDarwinControl(c), uiControlEnabledToUser(c)); \\\n\t}\n#define uiDarwinControlDefaultSyncEnableState(type, handlefield) \\\n\tstatic void type ## SyncEnableState(uiDarwinControl *c, int enabled) \\\n\t{ \\\n\t\tif (uiDarwinShouldStopSyncEnableState(c, enabled)) \\\n\t\t\treturn; \\\n\t\tif ([type(c)->handlefield respondsToSelector:@selector(setEnabled:)]) \\\n\t\t\t[((id) (type(c)->handlefield)) setEnabled:enabled]; /* id cast to make compiler happy; thanks mikeash in irc.freenode.net/#macdev */ \\\n\t}\n#define uiDarwinControlDefaultSetSuperview(type, handlefield) \\\n\tstatic void type ## SetSuperview(uiDarwinControl *c, NSView *superview) \\\n\t{ \\\n\t\t[type(c)->handlefield setTranslatesAutoresizingMaskIntoConstraints:NO]; \\\n\t\tif (superview == nil) \\\n\t\t\t[type(c)->handlefield removeFromSuperview]; \\\n\t\telse \\\n\t\t\t[superview addSubview:type(c)->handlefield]; \\\n\t}\n#define uiDarwinControlDefaultHugsTrailingEdge(type, handlefield) \\\n\tstatic BOOL type ## HugsTrailingEdge(uiDarwinControl *c) \\\n\t{ \\\n\t\treturn YES; /* always hug by default */ \\\n\t}\n#define uiDarwinControlDefaultHugsBottom(type, handlefield) \\\n\tstatic BOOL type ## HugsBottom(uiDarwinControl *c) \\\n\t{ \\\n\t\treturn YES; /* always hug by default */ \\\n\t}\n#define uiDarwinControlDefaultChildEdgeHuggingChanged(type, handlefield) \\\n\tstatic void type ## ChildEdgeHuggingChanged(uiDarwinControl *c) \\\n\t{ \\\n\t\t/* do nothing */ \\\n\t}\n#define uiDarwinControlDefaultHuggingPriority(type, handlefield) \\\n\tstatic NSLayoutPriority type ## HuggingPriority(uiDarwinControl *c, NSLayoutConstraintOrientation orientation) \\\n\t{ \\\n\t\treturn [type(c)->handlefield contentHuggingPriorityForOrientation:orientation]; \\\n\t}\n#define uiDarwinControlDefaultSetHuggingPriority(type, handlefield) \\\n\tstatic void type ## SetHuggingPriority(uiDarwinControl *c, NSLayoutPriority priority, NSLayoutConstraintOrientation orientation) \\\n\t{ \\\n\t\t[type(c)->handlefield setContentHuggingPriority:priority forOrientation:orientation]; \\\n\t}\n#define uiDarwinControlDefaultChildVisibilityChanged(type, handlefield) \\\n\tstatic void type ## ChildVisibilityChanged(uiDarwinControl *c) \\\n\t{ \\\n\t\t/* do nothing */ \\\n\t}\n\n#define uiDarwinControlAllDefaultsExceptDestroy(type, handlefield) \\\n\tuiDarwinControlDefaultHandle(type, handlefield) \\\n\tuiDarwinControlDefaultParent(type, handlefield) \\\n\tuiDarwinControlDefaultSetParent(type, handlefield) \\\n\tuiDarwinControlDefaultToplevel(type, handlefield) \\\n\tuiDarwinControlDefaultVisible(type, handlefield) \\\n\tuiDarwinControlDefaultShow(type, handlefield) \\\n\tuiDarwinControlDefaultHide(type, handlefield) \\\n\tuiDarwinControlDefaultEnabled(type, handlefield) \\\n\tuiDarwinControlDefaultEnable(type, handlefield) \\\n\tuiDarwinControlDefaultDisable(type, handlefield) \\\n\tuiDarwinControlDefaultSyncEnableState(type, handlefield) \\\n\tuiDarwinControlDefaultSetSuperview(type, handlefield) \\\n\tuiDarwinControlDefaultHugsTrailingEdge(type, handlefield) \\\n\tuiDarwinControlDefaultHugsBottom(type, handlefield) \\\n\tuiDarwinControlDefaultChildEdgeHuggingChanged(type, handlefield) \\\n\tuiDarwinControlDefaultHuggingPriority(type, handlefield) \\\n\tuiDarwinControlDefaultSetHuggingPriority(type, handlefield) \\\n\tuiDarwinControlDefaultChildVisibilityChanged(type, handlefield)\n\n#define uiDarwinControlAllDefaults(type, handlefield) \\\n\tuiDarwinControlDefaultDestroy(type, handlefield) \\\n\tuiDarwinControlAllDefaultsExceptDestroy(type, handlefield)\n\n// TODO document\n#define uiDarwinNewControl(type, var) \\\n\tvar = type(uiDarwinAllocControl(sizeof (type), type ## Signature, #type)); \\\n\tuiControl(var)->Destroy = type ## Destroy; \\\n\tuiControl(var)->Handle = type ## Handle; \\\n\tuiControl(var)->Parent = type ## Parent; \\\n\tuiControl(var)->SetParent = type ## SetParent; \\\n\tuiControl(var)->Toplevel = type ## Toplevel; \\\n\tuiControl(var)->Visible = type ## Visible; \\\n\tuiControl(var)->Show = type ## Show; \\\n\tuiControl(var)->Hide = type ## Hide; \\\n\tuiControl(var)->Enabled = type ## Enabled; \\\n\tuiControl(var)->Enable = type ## Enable; \\\n\tuiControl(var)->Disable = type ## Disable; \\\n\tuiDarwinControl(var)->SyncEnableState = type ## SyncEnableState; \\\n\tuiDarwinControl(var)->SetSuperview = type ## SetSuperview; \\\n\tuiDarwinControl(var)->HugsTrailingEdge = type ## HugsTrailingEdge; \\\n\tuiDarwinControl(var)->HugsBottom = type ## HugsBottom; \\\n\tuiDarwinControl(var)->ChildEdgeHuggingChanged = type ## ChildEdgeHuggingChanged; \\\n\tuiDarwinControl(var)->HuggingPriority = type ## HuggingPriority; \\\n\tuiDarwinControl(var)->SetHuggingPriority = type ## SetHuggingPriority; \\\n\tuiDarwinControl(var)->ChildVisibilityChanged = type ## ChildVisibilityChanged; \\\n\tuiDarwinControl(var)->visible = YES; \\\n\tuiDarwinControl(var)->enabled = YES;\n// TODO document\n_UI_EXTERN uiDarwinControl *uiDarwinAllocControl(size_t n, uint32_t typesig, const char *typenamestr);\n\n// Use this function as a shorthand for setting control fonts.\n_UI_EXTERN void uiDarwinSetControlFont(NSControl *c, NSControlSize size);\n\n// You can use this function from within your control implementations to return text strings that can be freed with uiFreeText().\n_UI_EXTERN char *uiDarwinNSStringToText(NSString *);\n\n// TODO document\n_UI_EXTERN BOOL uiDarwinShouldStopSyncEnableState(uiDarwinControl *, BOOL);\n\n// TODO document\n_UI_EXTERN void uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl *);\n_UI_EXTERN void uiDarwinNotifyVisibilityChanged(uiDarwinControl *c);\n\n// TODO document\n// TODO document that values should not be cached\n_UI_EXTERN CGFloat uiDarwinMarginAmount(void *reserved);\n_UI_EXTERN CGFloat uiDarwinPaddingAmount(void *reserved);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "ui_unix.h",
    "content": "// 7 april 2015\n\n/*\nThis file assumes that you have included <gtk/gtk.h> and \"ui.h\" beforehand. It provides API-specific functions for interfacing with foreign controls on Unix systems that use GTK+ to provide their UI (currently all except Mac OS X).\n*/\n\n#ifndef __LIBUI_UI_UNIX_H__\n#define __LIBUI_UI_UNIX_H__\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef struct uiUnixControl uiUnixControl;\nstruct uiUnixControl {\n\tuiControl c;\n\tuiControl *parent;\n\tgboolean addedBefore;\n\tgboolean explicitlyHidden;\n\tvoid (*SetContainer)(uiUnixControl *, GtkContainer *, gboolean);\n};\n#define uiUnixControl(this) ((uiUnixControl *) (this))\n// TODO document\n_UI_EXTERN void uiUnixControlSetContainer(uiUnixControl *, GtkContainer *, gboolean);\n\n#define uiUnixControlDefaultDestroy(type) \\\n\tstatic void type ## Destroy(uiControl *c) \\\n\t{ \\\n\t\t/* TODO is this safe on floating refs? */ \\\n\t\tg_object_unref(type(c)->widget); \\\n\t\tuiFreeControl(c); \\\n\t}\n#define uiUnixControlDefaultHandle(type) \\\n\tstatic uintptr_t type ## Handle(uiControl *c) \\\n\t{ \\\n\t\treturn (uintptr_t) (type(c)->widget); \\\n\t}\n#define uiUnixControlDefaultParent(type) \\\n\tstatic uiControl *type ## Parent(uiControl *c) \\\n\t{ \\\n\t\treturn uiUnixControl(c)->parent; \\\n\t}\n#define uiUnixControlDefaultSetParent(type) \\\n\tstatic void type ## SetParent(uiControl *c, uiControl *parent) \\\n\t{ \\\n\t\tuiControlVerifySetParent(c, parent); \\\n\t\tuiUnixControl(c)->parent = parent; \\\n\t}\n#define uiUnixControlDefaultToplevel(type) \\\n\tstatic int type ## Toplevel(uiControl *c) \\\n\t{ \\\n\t\treturn 0; \\\n\t}\n#define uiUnixControlDefaultVisible(type) \\\n\tstatic int type ## Visible(uiControl *c) \\\n\t{ \\\n\t\treturn gtk_widget_get_visible(type(c)->widget); \\\n\t}\n#define uiUnixControlDefaultShow(type) \\\n\tstatic void type ## Show(uiControl *c) \\\n\t{ \\\n\t\t/*TODO part of massive hack about hidden before*/uiUnixControl(c)->explicitlyHidden=FALSE; \\\n\t\tgtk_widget_show(type(c)->widget); \\\n\t}\n#define uiUnixControlDefaultHide(type) \\\n\tstatic void type ## Hide(uiControl *c) \\\n\t{ \\\n\t\t/*TODO part of massive hack about hidden before*/uiUnixControl(c)->explicitlyHidden=TRUE; \\\n\t\tgtk_widget_hide(type(c)->widget); \\\n\t}\n#define uiUnixControlDefaultEnabled(type) \\\n\tstatic int type ## Enabled(uiControl *c) \\\n\t{ \\\n\t\treturn gtk_widget_get_sensitive(type(c)->widget); \\\n\t}\n#define uiUnixControlDefaultEnable(type) \\\n\tstatic void type ## Enable(uiControl *c) \\\n\t{ \\\n\t\tgtk_widget_set_sensitive(type(c)->widget, TRUE); \\\n\t}\n#define uiUnixControlDefaultDisable(type) \\\n\tstatic void type ## Disable(uiControl *c) \\\n\t{ \\\n\t\tgtk_widget_set_sensitive(type(c)->widget, FALSE); \\\n\t}\n// TODO this whole addedBefore stuff is a MASSIVE HACK.\n#define uiUnixControlDefaultSetContainer(type) \\\n\tstatic void type ## SetContainer(uiUnixControl *c, GtkContainer *container, gboolean remove) \\\n\t{ \\\n\t\tif (!uiUnixControl(c)->addedBefore) { \\\n\t\t\tg_object_ref_sink(type(c)->widget); /* our own reference, which we release in Destroy() */ \\\n\t\t\t/* 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? */ \\\n\t\t\t/*TODO*/if(!uiUnixControl(c)->explicitlyHidden) gtk_widget_show(type(c)->widget); \\\n\t\t\tuiUnixControl(c)->addedBefore = TRUE; \\\n\t\t} \\\n\t\tif (remove) \\\n\t\t\tgtk_container_remove(container, type(c)->widget); \\\n\t\telse \\\n\t\t\tgtk_container_add(container, type(c)->widget); \\\n\t}\n\n#define uiUnixControlAllDefaultsExceptDestroy(type) \\\n\tuiUnixControlDefaultHandle(type) \\\n\tuiUnixControlDefaultParent(type) \\\n\tuiUnixControlDefaultSetParent(type) \\\n\tuiUnixControlDefaultToplevel(type) \\\n\tuiUnixControlDefaultVisible(type) \\\n\tuiUnixControlDefaultShow(type) \\\n\tuiUnixControlDefaultHide(type) \\\n\tuiUnixControlDefaultEnabled(type) \\\n\tuiUnixControlDefaultEnable(type) \\\n\tuiUnixControlDefaultDisable(type) \\\n\tuiUnixControlDefaultSetContainer(type)\n\n#define uiUnixControlAllDefaults(type) \\\n\tuiUnixControlDefaultDestroy(type) \\\n\tuiUnixControlAllDefaultsExceptDestroy(type)\n\n// TODO document\n#define uiUnixNewControl(type, var) \\\n\tvar = type(uiUnixAllocControl(sizeof (type), type ## Signature, #type)); \\\n\tuiControl(var)->Destroy = type ## Destroy; \\\n\tuiControl(var)->Handle = type ## Handle; \\\n\tuiControl(var)->Parent = type ## Parent; \\\n\tuiControl(var)->SetParent = type ## SetParent; \\\n\tuiControl(var)->Toplevel = type ## Toplevel; \\\n\tuiControl(var)->Visible = type ## Visible; \\\n\tuiControl(var)->Show = type ## Show; \\\n\tuiControl(var)->Hide = type ## Hide; \\\n\tuiControl(var)->Enabled = type ## Enabled; \\\n\tuiControl(var)->Enable = type ## Enable; \\\n\tuiControl(var)->Disable = type ## Disable; \\\n\tuiUnixControl(var)->SetContainer = type ## SetContainer;\n// TODO document\n_UI_EXTERN uiUnixControl *uiUnixAllocControl(size_t n, uint32_t typesig, const char *typenamestr);\n\n// uiUnixStrdupText() takes the given string and produces a copy of it suitable for being freed by uiFreeText().\n_UI_EXTERN char *uiUnixStrdupText(const char *);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "ui_windows.h",
    "content": "// 21 april 2016\n\n/*\nThis file assumes that you have included <windows.h> and \"ui.h\" beforehand. It provides API-specific functions for interfacing with foreign controls in Windows.\n*/\n\n#ifndef __LIBUI_UI_WINDOWS_H__\n#define __LIBUI_UI_WINDOWS_H__\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef struct uiWindowsSizing uiWindowsSizing;\n\ntypedef struct uiWindowsControl uiWindowsControl;\nstruct uiWindowsControl {\n\tuiControl c;\n\tuiControl *parent;\n\t// TODO this should be int on both os x and windows\n\tBOOL enabled;\n\tBOOL visible;\n\tvoid (*SyncEnableState)(uiWindowsControl *, int);\n\tvoid (*SetParentHWND)(uiWindowsControl *, HWND);\n\tvoid (*MinimumSize)(uiWindowsControl *, int *, int *);\n\tvoid (*MinimumSizeChanged)(uiWindowsControl *);\n\tvoid (*LayoutRect)(uiWindowsControl *c, RECT *r);\n\tvoid (*AssignControlIDZOrder)(uiWindowsControl *, LONG_PTR *, HWND *);\n\tvoid (*ChildVisibilityChanged)(uiWindowsControl *);\n};\n#define uiWindowsControl(this) ((uiWindowsControl *) (this))\n// TODO document\n_UI_EXTERN void uiWindowsControlSyncEnableState(uiWindowsControl *, int);\n_UI_EXTERN void uiWindowsControlSetParentHWND(uiWindowsControl *, HWND);\n_UI_EXTERN void uiWindowsControlMinimumSize(uiWindowsControl *, int *, int *);\n_UI_EXTERN void uiWindowsControlMinimumSizeChanged(uiWindowsControl *);\n_UI_EXTERN void uiWindowsControlLayoutRect(uiWindowsControl *, RECT *);\n_UI_EXTERN void uiWindowsControlAssignControlIDZOrder(uiWindowsControl *, LONG_PTR *, HWND *);\n_UI_EXTERN void uiWindowsControlChildVisibilityChanged(uiWindowsControl *);\n\n// TODO document\n#define uiWindowsControlDefaultDestroy(type) \\\n\tstatic void type ## Destroy(uiControl *c) \\\n\t{ \\\n\t\tuiWindowsEnsureDestroyWindow(type(c)->hwnd); \\\n\t\tuiFreeControl(c); \\\n\t}\n#define uiWindowsControlDefaultHandle(type) \\\n\tstatic uintptr_t type ## Handle(uiControl *c) \\\n\t{ \\\n\t\treturn (uintptr_t) (type(c)->hwnd); \\\n\t}\n#define uiWindowsControlDefaultParent(type) \\\n\tstatic uiControl *type ## Parent(uiControl *c) \\\n\t{ \\\n\t\treturn uiWindowsControl(c)->parent; \\\n\t}\n#define uiWindowsControlDefaultSetParent(type) \\\n\tstatic void type ## SetParent(uiControl *c, uiControl *parent) \\\n\t{ \\\n\t\tuiControlVerifySetParent(c, parent); \\\n\t\tuiWindowsControl(c)->parent = parent; \\\n\t}\n#define uiWindowsControlDefaultToplevel(type) \\\n\tstatic int type ## Toplevel(uiControl *c) \\\n\t{ \\\n\t\treturn 0; \\\n\t}\n#define uiWindowsControlDefaultVisible(type) \\\n\tstatic int type ## Visible(uiControl *c) \\\n\t{ \\\n\t\treturn uiWindowsControl(c)->visible; \\\n\t}\n#define uiWindowsControlDefaultShow(type) \\\n\tstatic void type ## Show(uiControl *c) \\\n\t{ \\\n\t\tuiWindowsControl(c)->visible = 1; \\\n\t\tShowWindow(type(c)->hwnd, SW_SHOW); \\\n\t\tuiWindowsControlNotifyVisibilityChanged(uiWindowsControl(c)); \\\n\t}\n#define uiWindowsControlDefaultHide(type) \\\n\tstatic void type ## Hide(uiControl *c) \\\n\t{ \\\n\t\tuiWindowsControl(c)->visible = 0; \\\n\t\tShowWindow(type(c)->hwnd, SW_HIDE); \\\n\t\tuiWindowsControlNotifyVisibilityChanged(uiWindowsControl(c)); \\\n\t}\n#define uiWindowsControlDefaultEnabled(type) \\\n\tstatic int type ## Enabled(uiControl *c) \\\n\t{ \\\n\t\treturn uiWindowsControl(c)->enabled; \\\n\t}\n#define uiWindowsControlDefaultEnable(type) \\\n\tstatic void type ## Enable(uiControl *c) \\\n\t{ \\\n\t\tuiWindowsControl(c)->enabled = 1; \\\n\t\tuiWindowsControlSyncEnableState(uiWindowsControl(c), uiControlEnabledToUser(c)); \\\n\t}\n#define uiWindowsControlDefaultDisable(type) \\\n\tstatic void type ## Disable(uiControl *c) \\\n\t{ \\\n\t\tuiWindowsControl(c)->enabled = 0; \\\n\t\tuiWindowsControlSyncEnableState(uiWindowsControl(c), uiControlEnabledToUser(c)); \\\n\t}\n#define uiWindowsControlDefaultSyncEnableState(type) \\\n\tstatic void type ## SyncEnableState(uiWindowsControl *c, int enabled) \\\n\t{ \\\n\t\tif (uiWindowsShouldStopSyncEnableState(c, enabled)) \\\n\t\t\treturn; \\\n\t\tEnableWindow(type(c)->hwnd, enabled); \\\n\t}\n#define uiWindowsControlDefaultSetParentHWND(type) \\\n\tstatic void type ## SetParentHWND(uiWindowsControl *c, HWND parent) \\\n\t{ \\\n\t\tuiWindowsEnsureSetParentHWND(type(c)->hwnd, parent); \\\n\t}\n// note that there is no uiWindowsControlDefaultMinimumSize(); you MUST define this yourself!\n#define uiWindowsControlDefaultMinimumSizeChanged(type) \\\n\tstatic void type ## MinimumSizeChanged(uiWindowsControl *c) \\\n\t{ \\\n\t\tif (uiWindowsControlTooSmall(c)) { \\\n\t\t\tuiWindowsControlContinueMinimumSizeChanged(c); \\\n\t\t\treturn; \\\n\t\t} \\\n\t\t/* otherwise do nothing; we have no children */ \\\n\t}\n#define uiWindowsControlDefaultLayoutRect(type) \\\n\tstatic void type ## LayoutRect(uiWindowsControl *c, RECT *r) \\\n\t{ \\\n\t\t/* use the window rect as we include the non-client area in the sizes */ \\\n\t\tuiWindowsEnsureGetWindowRect(type(c)->hwnd, r); \\\n\t}\n#define uiWindowsControlDefaultAssignControlIDZOrder(type) \\\n\tstatic void type ## AssignControlIDZOrder(uiWindowsControl *c, LONG_PTR *controlID, HWND *insertAfter) \\\n\t{ \\\n\t\tuiWindowsEnsureAssignControlIDZOrder(type(c)->hwnd, controlID, insertAfter); \\\n\t}\n#define uiWindowsControlDefaultChildVisibilityChanged(type) \\\n\tstatic void type ## ChildVisibilityChanged(uiWindowsControl *c) \\\n\t{ \\\n\t\t/* do nothing */ \\\n\t}\n\n#define uiWindowsControlAllDefaultsExceptDestroy(type) \\\n\tuiWindowsControlDefaultHandle(type) \\\n\tuiWindowsControlDefaultParent(type) \\\n\tuiWindowsControlDefaultSetParent(type) \\\n\tuiWindowsControlDefaultToplevel(type) \\\n\tuiWindowsControlDefaultVisible(type) \\\n\tuiWindowsControlDefaultShow(type) \\\n\tuiWindowsControlDefaultHide(type) \\\n\tuiWindowsControlDefaultEnabled(type) \\\n\tuiWindowsControlDefaultEnable(type) \\\n\tuiWindowsControlDefaultDisable(type) \\\n\tuiWindowsControlDefaultSyncEnableState(type) \\\n\tuiWindowsControlDefaultSetParentHWND(type) \\\n\tuiWindowsControlDefaultMinimumSizeChanged(type) \\\n\tuiWindowsControlDefaultLayoutRect(type) \\\n\tuiWindowsControlDefaultAssignControlIDZOrder(type) \\\n\tuiWindowsControlDefaultChildVisibilityChanged(type)\n\n#define uiWindowsControlAllDefaults(type) \\\n\tuiWindowsControlDefaultDestroy(type) \\\n\tuiWindowsControlAllDefaultsExceptDestroy(type)\n\n// TODO document\n#define uiWindowsNewControl(type, var) \\\n\tvar = type(uiWindowsAllocControl(sizeof (type), type ## Signature, #type)); \\\n\tuiControl(var)->Destroy = type ## Destroy; \\\n\tuiControl(var)->Handle = type ## Handle; \\\n\tuiControl(var)->Parent = type ## Parent; \\\n\tuiControl(var)->SetParent = type ## SetParent; \\\n\tuiControl(var)->Toplevel = type ## Toplevel; \\\n\tuiControl(var)->Visible = type ## Visible; \\\n\tuiControl(var)->Show = type ## Show; \\\n\tuiControl(var)->Hide = type ## Hide; \\\n\tuiControl(var)->Enabled = type ## Enabled; \\\n\tuiControl(var)->Enable = type ## Enable; \\\n\tuiControl(var)->Disable = type ## Disable; \\\n\tuiWindowsControl(var)->SyncEnableState = type ## SyncEnableState; \\\n\tuiWindowsControl(var)->SetParentHWND = type ## SetParentHWND; \\\n\tuiWindowsControl(var)->MinimumSize = type ## MinimumSize; \\\n\tuiWindowsControl(var)->MinimumSizeChanged = type ## MinimumSizeChanged; \\\n\tuiWindowsControl(var)->LayoutRect = type ## LayoutRect; \\\n\tuiWindowsControl(var)->AssignControlIDZOrder = type ## AssignControlIDZOrder; \\\n\tuiWindowsControl(var)->ChildVisibilityChanged = type ## ChildVisibilityChanged; \\\n\tuiWindowsControl(var)->visible = 1; \\\n\tuiWindowsControl(var)->enabled = 1;\n// TODO document\n_UI_EXTERN uiWindowsControl *uiWindowsAllocControl(size_t n, uint32_t typesig, const char *typenamestr);\n\n// TODO document\n_UI_EXTERN HWND uiWindowsEnsureCreateControlHWND(DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle, HINSTANCE hInstance, LPVOID lpParam, BOOL useStandardControlFont);\n\n// TODO document\n_UI_EXTERN void uiWindowsEnsureDestroyWindow(HWND hwnd);\n\n// TODO document\n// TODO document that this should only be used in SetParentHWND() implementations\n_UI_EXTERN void uiWindowsEnsureSetParentHWND(HWND hwnd, HWND parent);\n\n// TODO document\n_UI_EXTERN void uiWindowsEnsureAssignControlIDZOrder(HWND hwnd, LONG_PTR *controlID, HWND *insertAfter);\n\n// TODO document\n_UI_EXTERN void uiWindowsEnsureGetClientRect(HWND hwnd, RECT *r);\n_UI_EXTERN void uiWindowsEnsureGetWindowRect(HWND hwnd, RECT *r);\n\n// TODO document\n_UI_EXTERN char *uiWindowsWindowText(HWND hwnd);\n_UI_EXTERN void uiWindowsSetWindowText(HWND hwnd, const char *text);\n\n// TODO document\n_UI_EXTERN int uiWindowsWindowTextWidth(HWND hwnd);\n\n// TODO document\n// TODO point out this should only be used in a resize cycle\n_UI_EXTERN void uiWindowsEnsureMoveWindowDuringResize(HWND hwnd, int x, int y, int width, int height);\n\n// TODO document\n_UI_EXTERN void uiWindowsRegisterWM_COMMANDHandler(HWND hwnd, BOOL (*handler)(uiControl *, HWND, WORD, LRESULT *), uiControl *c);\n_UI_EXTERN void uiWindowsUnregisterWM_COMMANDHandler(HWND hwnd);\n\n// TODO document\n_UI_EXTERN void uiWindowsRegisterWM_NOTIFYHandler(HWND hwnd, BOOL (*handler)(uiControl *, HWND, NMHDR *, LRESULT *), uiControl *c);\n_UI_EXTERN void uiWindowsUnregisterWM_NOTIFYHandler(HWND hwnd);\n\n// TODO document\n_UI_EXTERN void uiWindowsRegisterWM_HSCROLLHandler(HWND hwnd, BOOL (*handler)(uiControl *, HWND, WORD, LRESULT *), uiControl *c);\n_UI_EXTERN void uiWindowsUnregisterWM_HSCROLLHandler(HWND hwnd);\n\n// TODO document\n_UI_EXTERN void uiWindowsRegisterReceiveWM_WININICHANGE(HWND hwnd);\n_UI_EXTERN void uiWindowsUnregisterReceiveWM_WININICHANGE(HWND hwnd);\n\n// TODO document\ntypedef struct uiWindowsSizing uiWindowsSizing;\nstruct uiWindowsSizing {\n\tint BaseX;\n\tint BaseY;\n\tLONG InternalLeading;\n};\n_UI_EXTERN void uiWindowsGetSizing(HWND hwnd, uiWindowsSizing *sizing);\n_UI_EXTERN void uiWindowsSizingDlgUnitsToPixels(uiWindowsSizing *sizing, int *x, int *y);\n_UI_EXTERN void uiWindowsSizingStandardPadding(uiWindowsSizing *sizing, int *x, int *y);\n\n// TODO document\n_UI_EXTERN HWND uiWindowsMakeContainer(uiWindowsControl *c, void (*onResize)(uiWindowsControl *));\n\n// TODO document\n_UI_EXTERN BOOL uiWindowsControlTooSmall(uiWindowsControl *c);\n_UI_EXTERN void uiWindowsControlContinueMinimumSizeChanged(uiWindowsControl *c);\n\n// TODO document\n_UI_EXTERN void uiWindowsControlAssignSoleControlIDZOrder(uiWindowsControl *);\n\n// TODO document\n_UI_EXTERN BOOL uiWindowsShouldStopSyncEnableState(uiWindowsControl *c, int enabled);\n\n// TODO document\n_UI_EXTERN void uiWindowsControlNotifyVisibilityChanged(uiWindowsControl *c);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "unix/OLD_table.c",
    "content": "// 26 june 2016\n#include \"uipriv_unix.h\"\n\nvoid *uiTableModelStrdup(const char *str)\n{\n\treturn g_strdup(str);\n}\n\nvoid *uiTableModelGiveColor(double r, double g, double b, double a)\n{\n\tGdkRGBA rgba;\n\n\trgba.red = r;\n\trgba.green = g;\n\trgba.blue = b;\n\trgba.alpha = a;\n\treturn gdk_rgba_copy(&rgba);\n}\n\nuiTableModel *uiNewTableModel(uiTableModelHandler *mh)\n{\n\tuiTableModel *m;\n\n\tm = uiTableModel(g_object_new(uiTableModelType, NULL));\n\tm->mh = mh;\n\treturn m;\n}\n\nvoid uiFreeTableModel(uiTableModel *m)\n{\n\tg_object_unref(m);\n}\n\nvoid uiTableModelRowInserted(uiTableModel *m, int newIndex)\n{\n\tGtkTreePath *path;\n\tGtkTreeIter iter;\n\n\tpath = gtk_tree_path_new_from_indices(newIndex, -1);\n\titer.stamp = STAMP_GOOD;\n\titer.user_data = GINT_TO_POINTER(newIndex);\n\tgtk_tree_model_row_inserted(GTK_TREE_MODEL(m), path, &iter);\n\tgtk_tree_path_free(path);\n}\n\nvoid uiTableModelRowChanged(uiTableModel *m, int index)\n{\n\tGtkTreePath *path;\n\tGtkTreeIter iter;\n\n\tpath = gtk_tree_path_new_from_indices(index, -1);\n\titer.stamp = STAMP_GOOD;\n\titer.user_data = GINT_TO_POINTER(index);\n\tgtk_tree_model_row_changed(GTK_TREE_MODEL(m), path, &iter);\n\tgtk_tree_path_free(path);\n}\n\nvoid uiTableModelRowDeleted(uiTableModel *m, int oldIndex)\n{\n\tGtkTreePath *path;\n\n\tpath = gtk_tree_path_new_from_indices(oldIndex, -1);\n\tgtk_tree_model_row_deleted(GTK_TREE_MODEL(m), path);\n\tgtk_tree_path_free(path);\n}\n\nenum {\n\tpartText,\n\tpartImage,\n\tpartButton,\n\tpartCheckbox,\n\tpartProgressBar,\n};\n\nstruct tablePart {\n\tint type;\n\tint textColumn;\n\tint imageColumn;\n\tint valueColumn;\n\tint colorColumn;\n\tGtkCellRenderer *r;\n\tuiTable *tv;\t\t\t// for pixbufs and background color\n};\n\nstruct uiTableColumn {\n\tGtkTreeViewColumn *c;\n\tuiTable *tv;\t\t\t// for pixbufs and background color\n\tGPtrArray *parts;\n};\n\nstruct uiTable {\n\tuiUnixControl c;\n\tGtkWidget *widget;\n\tGtkContainer *scontainer;\n\tGtkScrolledWindow *sw;\n\tGtkWidget *treeWidget;\n\tGtkTreeView *tv;\n\tGPtrArray *columns;\n\tuiTableModel *model;\n\tint backgroundColumn;\n};\n\n// use the same size as GtkFileChooserWidget's treeview\n// TODO refresh when icon theme changes\n// TODO doesn't work when scaled\n// TODO is this even necessary?\nstatic void setImageSize(GtkCellRenderer *r)\n{\n\tgint size;\n\tgint width, height;\n\tgint xpad, ypad;\n\n\tsize = 16;\t\t// fallback used by GtkFileChooserWidget\n\tif (gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height) != FALSE)\n\t\tsize = MAX(width, height);\n\tgtk_cell_renderer_get_padding(r, &xpad, &ypad);\n\tgtk_cell_renderer_set_fixed_size(r,\n\t\t2 * xpad + size,\n\t\t2 * ypad + size);\n}\n\nstatic void applyColor(GtkTreeModel *mm, GtkTreeIter *iter, int modelColumn, GtkCellRenderer *r, const char *prop, const char *propSet)\n{\n\tGValue value = G_VALUE_INIT;\n\tGdkRGBA *rgba;\n\n\tgtk_tree_model_get_value(mm, iter, modelColumn, &value);\n\trgba = (GdkRGBA *) g_value_get_boxed(&value);\n\tif (rgba != NULL)\n\t\tg_object_set(r, prop, rgba, NULL);\n\telse\n\t\tg_object_set(r, propSet, FALSE, NULL);\n\tg_value_unset(&value);\n}\n\nstatic void dataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *mm, GtkTreeIter *iter, gpointer data)\n{\n\tstruct tablePart *part = (struct tablePart *) data;\n\tGValue value = G_VALUE_INIT;\n\tconst gchar *str;\n\tuiImage *img;\n\tint pval;\n\n\tswitch (part->type) {\n\tcase partText:\n\t\tgtk_tree_model_get_value(mm, iter, part->textColumn, &value);\n\t\tstr = g_value_get_string(&value);\n\t\tg_object_set(r, \"text\", str, NULL);\n\t\tif (part->colorColumn != -1)\n\t\t\tapplyColor(mm, iter,\n\t\t\t\tpart->colorColumn,\n\t\t\t\tr, \"foreground-rgba\", \"foreground-set\");\n\t\tbreak;\n\tcase partImage:\n//TODO\t\tsetImageSize(r);\n\t\tgtk_tree_model_get_value(mm, iter, part->imageColumn, &value);\n\t\timg = (uiImage *) g_value_get_pointer(&value);\n\t\tg_object_set(r, \"surface\",\n\t\t\tuiprivImageAppropriateSurface(img, part->tv->treeWidget),\n\t\t\tNULL);\n\t\tbreak;\n\tcase partButton:\n\t\tgtk_tree_model_get_value(mm, iter, part->textColumn, &value);\n\t\tstr = g_value_get_string(&value);\n\t\tg_object_set(r, \"text\", str, NULL);\n\t\tbreak;\n\tcase partCheckbox:\n\t\tgtk_tree_model_get_value(mm, iter, part->valueColumn, &value);\n\t\tg_object_set(r, \"active\", g_value_get_int(&value) != 0, NULL);\n\t\tbreak;\n\tcase partProgressBar:\n\t\tgtk_tree_model_get_value(mm, iter, part->valueColumn, &value);\n\t\tpval = g_value_get_int(&value);\n\t\tif (pval == -1) {\n\t\t\t// TODO\n\t\t} else\n\t\t\tg_object_set(r,\n\t\t\t\t\"pulse\", -1,\n\t\t\t\t\"value\", pval,\n\t\t\t\tNULL);\n\t\tbreak;\n\t}\n\tg_value_unset(&value);\n\n\tif (part->tv->backgroundColumn != -1)\n\t\tapplyColor(mm, iter,\n\t\t\tpart->tv->backgroundColumn,\n\t\t\tr, \"cell-background-rgba\", \"cell-background-set\");\n}\n\nstatic void onEdited(struct tablePart *part, int column, const char *pathstr, const void *data)\n{\n\tGtkTreePath *path;\n\tint row;\n\tuiTableModel *m;\n\n\tpath = gtk_tree_path_new_from_string(pathstr);\n\trow = gtk_tree_path_get_indices(path)[0];\n\tgtk_tree_path_free(path);\n\tm = part->tv->model;\n\t(*(m->mh->SetCellValue))(m->mh, m, row, column, data);\n\t// and update\n\tuiTableModelRowChanged(m, row);\n}\n\nstatic void appendPart(uiTableColumn *c, struct tablePart *part, GtkCellRenderer *r, int expand)\n{\n\tpart->r = r;\n\tgtk_tree_view_column_pack_start(c->c, part->r, expand != 0);\n\tgtk_tree_view_column_set_cell_data_func(c->c, part->r, dataFunc, part, NULL);\n\tg_ptr_array_add(c->parts, part);\n}\n\nstatic void textEdited(GtkCellRendererText *renderer, gchar *path, gchar *newText, gpointer data)\n{\n\tstruct tablePart *part = (struct tablePart *) data;\n\n\tonEdited(part, part->textColumn, path, newText);\n}\n\nvoid uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand)\n{\n\tstruct tablePart *part;\n\tGtkCellRenderer *r;\n\n\tpart = uiprivNew(struct tablePart);\n\tpart->type = partText;\n\tpart->textColumn = modelColumn;\n\tpart->tv = c->tv;\n\tpart->colorColumn = -1;\n\n\tr = gtk_cell_renderer_text_new();\n\tg_object_set(r, \"editable\", FALSE, NULL);\n\tg_signal_connect(r, \"edited\", G_CALLBACK(textEdited), part);\n\n\tappendPart(c, part, r, expand);\n}\n\nvoid uiTableColumnAppendImagePart(uiTableColumn *c, int modelColumn, int expand)\n{\n\tstruct tablePart *part;\n\n\tpart = uiprivNew(struct tablePart);\n\tpart->type = partImage;\n\tpart->imageColumn = modelColumn;\n\tpart->tv = c->tv;\n\tappendPart(c, part,\n\t\tgtk_cell_renderer_pixbuf_new(),\n\t\texpand);\n}\n\n// TODO wrong type here\nstatic void buttonClicked(GtkCellRenderer *r, gchar *pathstr, gpointer data)\n{\n\tstruct tablePart *part = (struct tablePart *) data;\n\n\tonEdited(part, part->textColumn, pathstr, NULL);\n}\n\nvoid uiTableColumnAppendButtonPart(uiTableColumn *c, int modelColumn, int expand)\n{\n\tstruct tablePart *part;\n\tGtkCellRenderer *r;\n\n\tpart = uiprivNew(struct tablePart);\n\tpart->type = partButton;\n\tpart->textColumn = modelColumn;\n\tpart->tv = c->tv;\n\n\tr = uiprivNewCellRendererButton();\n\tg_object_set(r, \"sensitive\", TRUE, NULL);\t\t// editable by default\n\tg_signal_connect(r, \"clicked\", G_CALLBACK(buttonClicked), part);\n\n\tappendPart(c, part, r, expand);\n}\n\n// yes, we need to do all this twice :|\nstatic void checkboxToggled(GtkCellRendererToggle *r, gchar *pathstr, gpointer data)\n{\n\tstruct tablePart *part = (struct tablePart *) data;\n\tGtkTreePath *path;\n\tint row;\n\tuiTableModel *m;\n\tvoid *value;\n\tint intval;\n\n\tpath = gtk_tree_path_new_from_string(pathstr);\n\trow = gtk_tree_path_get_indices(path)[0];\n\tgtk_tree_path_free(path);\n\tm = part->tv->model;\n\tvalue = (*(m->mh->CellValue))(m->mh, m, row, part->valueColumn);\n\tintval = !uiTableModelTakeInt(value);\n\tonEdited(part, part->valueColumn, pathstr, uiTableModelGiveInt(intval));\n}\n\nvoid uiTableColumnAppendCheckboxPart(uiTableColumn *c, int modelColumn, int expand)\n{\n\tstruct tablePart *part;\n\tGtkCellRenderer *r;\n\n\tpart = uiprivNew(struct tablePart);\n\tpart->type = partCheckbox;\n\tpart->valueColumn = modelColumn;\n\tpart->tv = c->tv;\n\n\tr = gtk_cell_renderer_toggle_new();\n\tg_object_set(r, \"sensitive\", TRUE, NULL);\t\t// editable by default\n\tg_signal_connect(r, \"toggled\", G_CALLBACK(checkboxToggled), part);\n\n\tappendPart(c, part, r, expand);\n}\n\nvoid uiTableColumnAppendProgressBarPart(uiTableColumn *c, int modelColumn, int expand)\n{\n\tstruct tablePart *part;\n\n\tpart = uiprivNew(struct tablePart);\n\tpart->type = partProgressBar;\n\tpart->valueColumn = modelColumn;\n\tpart->tv = c->tv;\n\tappendPart(c, part,\n\t\tgtk_cell_renderer_progress_new(),\n\t\texpand);\n}\n\nvoid uiTableColumnPartSetEditable(uiTableColumn *c, int part, int editable)\n{\n\tstruct tablePart *p;\n\n\tp = (struct tablePart *) g_ptr_array_index(c->parts, part);\n\tswitch (p->type) {\n\tcase partImage:\n\tcase partProgressBar:\n\t\treturn;\n\tcase partButton:\n\tcase partCheckbox:\n\t\tg_object_set(p->r, \"sensitive\", editable != 0, NULL);\n\t\treturn;\n\t}\n\tg_object_set(p->r, \"editable\", editable != 0, NULL);\n}\n\nvoid uiTableColumnPartSetTextColor(uiTableColumn *c, int part, int modelColumn)\n{\n\tstruct tablePart *p;\n\n\tp = (struct tablePart *) g_ptr_array_index(c->parts, part);\n\tp->colorColumn = modelColumn;\n\t// TODO refresh table\n}\n\nuiUnixControlAllDefaultsExceptDestroy(uiTable)\n\nstatic void uiTableDestroy(uiControl *c)\n{\n\tuiTable *t = uiTable(c);\n\n\t// TODO\n\tg_object_unref(t->widget);\n\tuiFreeControl(uiControl(t));\n}\n\nuiTableColumn *uiTableAppendColumn(uiTable *t, const char *name)\n{\n\tuiTableColumn *c;\n\n\tc = uiprivNew(uiTableColumn);\n\tc->c = gtk_tree_view_column_new();\n\tgtk_tree_view_column_set_resizable(c->c, TRUE);\n\tgtk_tree_view_column_set_title(c->c, name);\n\tgtk_tree_view_append_column(t->tv, c->c);\n\tc->tv = t;\t\t// TODO rename field to t, cascade\n\tc->parts = g_ptr_array_new();\n\treturn c;\n}\n\nvoid uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn)\n{\n\tt->backgroundColumn = modelColumn;\n\t// TODO refresh table\n}\n\nuiTable *uiNewTable(uiTableModel *model)\n{\n\tuiTable *t;\n\n\tuiUnixNewControl(uiTable, t);\n\n\tt->model = model;\n\tt->backgroundColumn = -1;\n\n\tt->widget = gtk_scrolled_window_new(NULL, NULL);\n\tt->scontainer = GTK_CONTAINER(t->widget);\n\tt->sw = GTK_SCROLLED_WINDOW(t->widget);\n\tgtk_scrolled_window_set_shadow_type(t->sw, GTK_SHADOW_IN);\n\n\tt->treeWidget = gtk_tree_view_new_with_model(GTK_TREE_MODEL(t->model));\n\tt->tv = GTK_TREE_VIEW(t->treeWidget);\n\t// TODO set up t->tv\n\n\tgtk_container_add(t->scontainer, t->treeWidget);\n\t// and make the tree view visible; only the scrolled window's visibility is controlled by libui\n\tgtk_widget_show(t->treeWidget);\n\n\treturn t;\n}\n"
  },
  {
    "path": "unix/alloc.c",
    "content": "// 7 april 2015\n#include <string.h>\n#include \"uipriv_unix.h\"\n\nstatic GPtrArray *allocations;\n\n#define UINT8(p) ((uint8_t *) (p))\n#define PVOID(p) ((void *) (p))\n#define EXTRA (sizeof (size_t) + sizeof (const char **))\n#define DATA(p) PVOID(UINT8(p) + EXTRA)\n#define BASE(p) PVOID(UINT8(p) - EXTRA)\n#define SIZE(p) ((size_t *) (p))\n#define CCHAR(p) ((const char **) (p))\n#define TYPE(p) CCHAR(UINT8(p) + sizeof (size_t))\n\nvoid uiprivInitAlloc(void)\n{\n\tallocations = g_ptr_array_new();\n}\n\nstatic void uninitComplain(gpointer ptr, gpointer data)\n{\n\tchar **str = (char **) data;\n\tchar *str2;\n\n\tif (*str == NULL)\n\t\t*str = g_strdup_printf(\"\");\n\tstr2 = g_strdup_printf(\"%s%p %s\\n\", *str, ptr, *TYPE(ptr));\n\tg_free(*str);\n\t*str = str2;\n}\n\nvoid uiprivUninitAlloc(void)\n{\n\tchar *str = NULL;\n\n\tif (allocations->len == 0) {\n\t\tg_ptr_array_free(allocations, TRUE);\n\t\treturn;\n\t}\n\tg_ptr_array_foreach(allocations, uninitComplain, &str);\n\tuiprivUserBug(\"Some data was leaked; either you left a uiControl lying around or there's a bug in libui itself. Leaked data:\\n%s\", str);\n\tg_free(str);\n}\n\nvoid *uiprivAlloc(size_t size, const char *type)\n{\n\tvoid *out;\n\n\tout = g_malloc0(EXTRA + size);\n\t*SIZE(out) = size;\n\t*TYPE(out) = type;\n\tg_ptr_array_add(allocations, out);\n\treturn DATA(out);\n}\n\nvoid *uiprivRealloc(void *p, size_t new, const char *type)\n{\n\tvoid *out;\n\tsize_t *s;\n\n\tif (p == NULL)\n\t\treturn uiprivAlloc(new, type);\n\tp = BASE(p);\n\tout = g_realloc(p, EXTRA + new);\n\ts = SIZE(out);\n\tif (new > *s)\n\t\tmemset(((uint8_t *) DATA(out)) + *s, 0, new - *s);\n\t*s = new;\n\tif (g_ptr_array_remove(allocations, p) == FALSE)\n\t\tuiprivImplBug(\"%p not found in allocations array in uiprivRealloc()\", p);\n\tg_ptr_array_add(allocations, out);\n\treturn DATA(out);\n}\n\nvoid uiprivFree(void *p)\n{\n\tif (p == NULL)\n\t\tuiprivImplBug(\"attempt to uiprivFree(NULL)\");\n\tp = BASE(p);\n\tg_free(p);\n\tif (g_ptr_array_remove(allocations, p) == FALSE)\n\t\tuiprivImplBug(\"%p not found in allocations array in uiprivFree()\", p);\n}\n"
  },
  {
    "path": "unix/area.c",
    "content": "// 4 september 2015\n#include \"uipriv_unix.h\"\n\n// notes:\n// - G_DECLARE_DERIVABLE/FINAL_INTERFACE() requires glib 2.44 and that's starting with debian stretch (testing) (GTK+ 3.18) and ubuntu 15.04 (GTK+ 3.14) - debian jessie has 2.42 (GTK+ 3.14)\n#define areaWidgetType (areaWidget_get_type())\n#define areaWidget(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), areaWidgetType, areaWidget))\n#define isAreaWidget(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), areaWidgetType))\n#define areaWidgetClass(class) (G_TYPE_CHECK_CLASS_CAST((class), areaWidgetType, areaWidgetClass))\n#define isAreaWidgetClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), areaWidget))\n#define getAreaWidgetClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), areaWidgetType, areaWidgetClass))\n\ntypedef struct areaWidget areaWidget;\ntypedef struct areaWidgetClass areaWidgetClass;\n\nstruct areaWidget {\n\tGtkDrawingArea parent_instance;\n\tuiArea *a;\n\t// construct-only parameters aare not set until after the init() function has returned\n\t// we need this particular object available during init(), so put it here instead of in uiArea\n\t// keep a pointer in uiArea for convenience, though\n\tuiprivClickCounter cc;\n};\n\nstruct areaWidgetClass {\n\tGtkDrawingAreaClass parent_class;\n};\n\nstruct uiArea {\n\tuiUnixControl c;\n\tGtkWidget *widget;\t\t// either swidget or areaWidget depending on whether it is scrolling\n\n\tGtkWidget *swidget;\n\tGtkContainer *scontainer;\n\tGtkScrolledWindow *sw;\n\n\tGtkWidget *areaWidget;\n\tGtkDrawingArea *drawingArea;\n\tareaWidget *area;\n\n\tuiAreaHandler *ah;\n\n\tgboolean scrolling;\n\tint scrollWidth;\n\tint scrollHeight;\n\n\t// note that this is a pointer; see above\n\tuiprivClickCounter *cc;\n\n\t// for user window drags\n\tGdkEventButton *dragevent;\n};\n\nG_DEFINE_TYPE(areaWidget, areaWidget, GTK_TYPE_DRAWING_AREA)\n\nstatic void areaWidget_init(areaWidget *aw)\n{\n\t// for events\n\tgtk_widget_add_events(GTK_WIDGET(aw),\n\t\tGDK_POINTER_MOTION_MASK |\n\t\tGDK_BUTTON_MOTION_MASK |\n\t\tGDK_BUTTON_PRESS_MASK |\n\t\tGDK_BUTTON_RELEASE_MASK |\n\t\tGDK_KEY_PRESS_MASK |\n\t\tGDK_KEY_RELEASE_MASK |\n\t\tGDK_ENTER_NOTIFY_MASK |\n\t\tGDK_LEAVE_NOTIFY_MASK);\n\n\tgtk_widget_set_can_focus(GTK_WIDGET(aw), TRUE);\n\n\tuiprivClickCounterReset(&(aw->cc));\n}\n\nstatic void areaWidget_dispose(GObject *obj)\n{\n\tG_OBJECT_CLASS(areaWidget_parent_class)->dispose(obj);\n}\n\nstatic void areaWidget_finalize(GObject *obj)\n{\n\tG_OBJECT_CLASS(areaWidget_parent_class)->finalize(obj);\n}\n\nstatic void areaWidget_size_allocate(GtkWidget *w, GtkAllocation *allocation)\n{\n\tareaWidget *aw = areaWidget(w);\n\tuiArea *a = aw->a;\n\n\t// GtkDrawingArea has a size_allocate() implementation; we need to call it\n\t// this will call gtk_widget_set_allocation() for us\n\tGTK_WIDGET_CLASS(areaWidget_parent_class)->size_allocate(w, allocation);\n\n\tif (!a->scrolling)\n\t\t// we must redraw everything on resize because Windows requires it\n\t\t// TODO https://developer.gnome.org/gtk3/3.10/GtkWidget.html#gtk-widget-set-redraw-on-allocate ?\n\t\t// TODO drop this rule; it was stupid and documenting this was stupid — let platforms where it matters do it on their own\n\t\t// TODO or do we not, for parity of performance?\n\t\tgtk_widget_queue_resize(w);\n}\n\nstatic void loadAreaSize(uiArea *a, double *width, double *height)\n{\n\tGtkAllocation allocation;\n\n\t*width = 0;\n\t*height = 0;\n\t// don't provide size information for scrolling areas\n\tif (!a->scrolling) {\n\t\tgtk_widget_get_allocation(a->areaWidget, &allocation);\n\t\t// these are already in drawing space coordinates\n\t\t// for drawing, the size of drawing space has the same value as the widget allocation\n\t\t// thanks to tristan in irc.gimp.net/#gtk+\n\t\t*width = allocation.width;\n\t\t*height = allocation.height;\n\t}\n}\n\nstatic gboolean areaWidget_draw(GtkWidget *w, cairo_t *cr)\n{\n\tareaWidget *aw = areaWidget(w);\n\tuiArea *a = aw->a;\n\tuiAreaDrawParams dp;\n\tdouble clipX0, clipY0, clipX1, clipY1;\n\n\tdp.Context = uiprivNewContext(cr,\n\t\tgtk_widget_get_style_context(a->widget));\n\n\tloadAreaSize(a, &(dp.AreaWidth), &(dp.AreaHeight));\n\n\tcairo_clip_extents(cr, &clipX0, &clipY0, &clipX1, &clipY1);\n\tdp.ClipX = clipX0;\n\tdp.ClipY = clipY0;\n\tdp.ClipWidth = clipX1 - clipX0;\n\tdp.ClipHeight = clipY1 - clipY0;\n\n\t// no need to save or restore the graphics state to reset transformations; GTK+ does that for us\n\t(*(a->ah->Draw))(a->ah, a, &dp);\n\n\tuiprivFreeContext(dp.Context);\n\treturn FALSE;\n}\n\n// to do this properly for scrolling areas, we need to\n// - return the same value for min and nat\n// - call gtk_widget_queue_resize() when the size changes\n// thanks to Company in irc.gimp.net/#gtk+\nstatic void areaWidget_get_preferred_height(GtkWidget *w, gint *min, gint *nat)\n{\n\tareaWidget *aw = areaWidget(w);\n\tuiArea *a = aw->a;\n\n\t// always chain up just in case\n\tGTK_WIDGET_CLASS(areaWidget_parent_class)->get_preferred_height(w, min, nat);\n\tif (a->scrolling) {\n\t\t*min = a->scrollHeight;\n\t\t*nat = a->scrollHeight;\n\t}\n}\n\nstatic void areaWidget_get_preferred_width(GtkWidget *w, gint *min, gint *nat)\n{\n\tareaWidget *aw = areaWidget(w);\n\tuiArea *a = aw->a;\n\n\t// always chain up just in case\n\tGTK_WIDGET_CLASS(areaWidget_parent_class)->get_preferred_width(w, min, nat);\n\tif (a->scrolling) {\n\t\t*min = a->scrollWidth;\n\t\t*nat = a->scrollWidth;\n\t}\n}\n\nstatic guint translateModifiers(guint state, GdkWindow *window)\n{\n\tGdkModifierType statetype;\n\n\t// GDK doesn't initialize the modifier flags fully; we have to explicitly tell it to (thanks to Daniel_S and daniels (two different people) in irc.gimp.net/#gtk+)\n\tstatetype = state;\n\tgdk_keymap_add_virtual_modifiers(\n\t\tgdk_keymap_get_for_display(gdk_window_get_display(window)),\n\t\t&statetype);\n\treturn statetype;\n}\n\nstatic uiModifiers toModifiers(guint state)\n{\n\tuiModifiers m;\n\n\tm = 0;\n\tif ((state & GDK_CONTROL_MASK) != 0)\n\t\tm |= uiModifierCtrl;\n\tif ((state & GDK_META_MASK) != 0)\n\t\tm |= uiModifierAlt;\n\tif ((state & GDK_MOD1_MASK) != 0)\t\t// GTK+ itself requires this to be Alt (just read through gtkaccelgroup.c)\n\t\tm |= uiModifierAlt;\n\tif ((state & GDK_SHIFT_MASK) != 0)\n\t\tm |= uiModifierShift;\n\tif ((state & GDK_SUPER_MASK) != 0)\n\t\tm |= uiModifierSuper;\n\treturn m;\n}\n\n// capture on drag is done automatically on GTK+\nstatic void finishMouseEvent(uiArea *a, uiAreaMouseEvent *me, guint mb, gdouble x, gdouble y, guint state, GdkWindow *window)\n{\n\t// on GTK+, mouse buttons 4-7 are for scrolling; if we got here, that's a mistake\n\tif (mb >= 4 && mb <= 7)\n\t\treturn;\n\t// if the button ID >= 8, continue counting from 4, as in the MouseEvent spec\n\tif (me->Down >= 8)\n\t\tme->Down -= 4;\n\tif (me->Up >= 8)\n\t\tme->Up -= 4;\n\n\tstate = translateModifiers(state, window);\n\tme->Modifiers = toModifiers(state);\n\n\t// the mb != # checks exclude the Up/Down button from Held\n\tme->Held1To64 = 0;\n\tif (mb != 1 && (state & GDK_BUTTON1_MASK) != 0)\n\t\tme->Held1To64 |= 1 << 0;\n\tif (mb != 2 && (state & GDK_BUTTON2_MASK) != 0)\n\t\tme->Held1To64 |= 1 << 1;\n\tif (mb != 3 && (state & GDK_BUTTON3_MASK) != 0)\n\t\tme->Held1To64 |= 1 << 2;\n\t// don't check GDK_BUTTON4_MASK or GDK_BUTTON5_MASK because those are for the scrolling buttons mentioned above\n\t// GDK expressly does not support any more buttons in the GdkModifierType; see https://git.gnome.org/browse/gtk+/tree/gdk/x11/gdkdevice-xi2.c#n763 (thanks mclasen in irc.gimp.net/#gtk+)\n\n\t// these are already in drawing space coordinates\n\t// the size of drawing space has the same value as the widget allocation\n\t// thanks to tristan in irc.gimp.net/#gtk+\n\tme->X = x;\n\tme->Y = y;\n\n\tloadAreaSize(a, &(me->AreaWidth), &(me->AreaHeight));\n\n\t(*(a->ah->MouseEvent))(a->ah, a, me);\n}\n\nstatic gboolean areaWidget_button_press_event(GtkWidget *w, GdkEventButton *e)\n{\n\tareaWidget *aw = areaWidget(w);\n\tuiArea *a = aw->a;\n\tgint maxTime, maxDistance;\n\tGtkSettings *settings;\n\tuiAreaMouseEvent me;\n\n\t// clicking doesn't automatically transfer keyboard focus; we must do so manually (thanks tristan in irc.gimp.net/#gtk+)\n\tgtk_widget_grab_focus(w);\n\n\tme.Down = e->button;\n\tme.Up = 0;\n\n\t// we handle multiple clicks ourselves here, in the same way as we do on Windows\n\tif (e->type != GDK_BUTTON_PRESS)\n\t\t// ignore GDK's generated double-clicks and beyond\n\t\treturn GDK_EVENT_PROPAGATE;\n\tsettings = gtk_widget_get_settings(w);\n\tg_object_get(settings,\n\t\t\"gtk-double-click-time\", &maxTime,\n\t\t\"gtk-double-click-distance\", &maxDistance,\n\t\tNULL);\n\t// don't unref settings; it's transfer-none (thanks gregier in irc.gimp.net/#gtk+)\n\t// e->time is guint32\n\t// e->x and e->y are floating-point; just make them 32-bit integers\n\t// maxTime and maxDistance... are gint, which *should* fit, hopefully...\n\tme.Count = uiprivClickCounterClick(a->cc, me.Down,\n\t\te->x, e->y,\n\t\te->time, maxTime,\n\t\tmaxDistance, maxDistance);\n\n\t// and set things up for window drags\n\ta->dragevent = e;\n\tfinishMouseEvent(a, &me, e->button, e->x, e->y, e->state, e->window);\n\ta->dragevent = NULL;\n\treturn GDK_EVENT_PROPAGATE;\n}\n\nstatic gboolean areaWidget_button_release_event(GtkWidget *w, GdkEventButton *e)\n{\n\tareaWidget *aw = areaWidget(w);\n\tuiArea *a = aw->a;\n\tuiAreaMouseEvent me;\n\n\tme.Down = 0;\n\tme.Up = e->button;\n\tme.Count = 0;\n\tfinishMouseEvent(a, &me, e->button, e->x, e->y, e->state, e->window);\n\treturn GDK_EVENT_PROPAGATE;\n}\n\nstatic gboolean areaWidget_motion_notify_event(GtkWidget *w, GdkEventMotion *e)\n{\n\tareaWidget *aw = areaWidget(w);\n\tuiArea *a = aw->a;\n\tuiAreaMouseEvent me;\n\n\tme.Down = 0;\n\tme.Up = 0;\n\tme.Count = 0;\n\tfinishMouseEvent(a, &me, 0, e->x, e->y, e->state, e->window);\n\treturn GDK_EVENT_PROPAGATE;\n}\n\n// we want switching away from the control to reset the double-click counter, like with WM_ACTIVATE on Windows\n// according to tristan in irc.gimp.net/#gtk+, doing this on both enter-notify-event and leave-notify-event is correct (and it seems to be true in my own tests; plus the events DO get sent when switching programs with the keyboard (just pointing that out))\nstatic gboolean onCrossing(areaWidget *aw, int left)\n{\n\tuiArea *a = aw->a;\n\n\t(*(a->ah->MouseCrossed))(a->ah, a, left);\n\tuiprivClickCounterReset(a->cc);\n\treturn GDK_EVENT_PROPAGATE;\n}\n\nstatic gboolean areaWidget_enter_notify_event(GtkWidget *w, GdkEventCrossing *e)\n{\n\treturn onCrossing(areaWidget(w), 0);\n}\n\nstatic gboolean areaWidget_leave_notify_event(GtkWidget *w, GdkEventCrossing *e)\n{\n\treturn onCrossing(areaWidget(w), 1);\n}\n\n// note: there is no equivalent to WM_CAPTURECHANGED on GTK+; there literally is no way to break a grab like that (at least not on X11 and Wayland)\n// even if I invoke the task switcher and switch processes, the mouse grab will still be held until I let go of all buttons\n// therefore, no DragBroken()\n\n// we use GDK_KEY_Print as a sentinel because libui will never support the print screen key; that key belongs to the user\n\nstatic const struct {\n\tguint keyval;\n\tuiExtKey extkey;\n} extKeys[] = {\n\t{ GDK_KEY_Escape, uiExtKeyEscape },\n\t{ GDK_KEY_Insert, uiExtKeyInsert },\n\t{ GDK_KEY_Delete, uiExtKeyDelete },\n\t{ GDK_KEY_Home, uiExtKeyHome },\n\t{ GDK_KEY_End, uiExtKeyEnd },\n\t{ GDK_KEY_Page_Up, uiExtKeyPageUp },\n\t{ GDK_KEY_Page_Down, uiExtKeyPageDown },\n\t{ GDK_KEY_Up, uiExtKeyUp },\n\t{ GDK_KEY_Down, uiExtKeyDown },\n\t{ GDK_KEY_Left, uiExtKeyLeft },\n\t{ GDK_KEY_Right, uiExtKeyRight },\n\t{ GDK_KEY_F1, uiExtKeyF1 },\n\t{ GDK_KEY_F2, uiExtKeyF2 },\n\t{ GDK_KEY_F3, uiExtKeyF3 },\n\t{ GDK_KEY_F4, uiExtKeyF4 },\n\t{ GDK_KEY_F5, uiExtKeyF5 },\n\t{ GDK_KEY_F6, uiExtKeyF6 },\n\t{ GDK_KEY_F7, uiExtKeyF7 },\n\t{ GDK_KEY_F8, uiExtKeyF8 },\n\t{ GDK_KEY_F9, uiExtKeyF9 },\n\t{ GDK_KEY_F10, uiExtKeyF10 },\n\t{ GDK_KEY_F11, uiExtKeyF11 },\n\t{ GDK_KEY_F12, uiExtKeyF12 },\n\t// numpad numeric keys and . are handled in events.c\n\t{ GDK_KEY_KP_Enter, uiExtKeyNEnter },\n\t{ GDK_KEY_KP_Add, uiExtKeyNAdd },\n\t{ GDK_KEY_KP_Subtract, uiExtKeyNSubtract },\n\t{ GDK_KEY_KP_Multiply, uiExtKeyNMultiply },\n\t{ GDK_KEY_KP_Divide, uiExtKeyNDivide },\n\t{ GDK_KEY_Print, 0 },\n};\n\nstatic const struct {\n\tguint keyval;\n\tuiModifiers mod;\n} modKeys[] = {\n\t{ GDK_KEY_Control_L, uiModifierCtrl },\n\t{ GDK_KEY_Control_R, uiModifierCtrl },\n\t{ GDK_KEY_Alt_L, uiModifierAlt },\n\t{ GDK_KEY_Alt_R, uiModifierAlt },\n\t{ GDK_KEY_Meta_L, uiModifierAlt },\n\t{ GDK_KEY_Meta_R, uiModifierAlt },\n\t{ GDK_KEY_Shift_L, uiModifierShift },\n\t{ GDK_KEY_Shift_R, uiModifierShift },\n\t{ GDK_KEY_Super_L, uiModifierSuper },\n\t{ GDK_KEY_Super_R, uiModifierSuper },\n\t{ GDK_KEY_Print, 0 },\n};\n\nstatic int areaKeyEvent(uiArea *a, int up, GdkEventKey *e)\n{\n\tuiAreaKeyEvent ke;\n\tguint state;\n\tint i;\n\n\tke.Key = 0;\n\tke.ExtKey = 0;\n\tke.Modifier = 0;\n\n\tstate = translateModifiers(e->state, e->window);\n\tke.Modifiers = toModifiers(state);\n\n\tke.Up = up;\n\n\tfor (i = 0; extKeys[i].keyval != GDK_KEY_Print; i++)\n\t\tif (extKeys[i].keyval == e->keyval) {\n\t\t\tke.ExtKey = extKeys[i].extkey;\n\t\t\tgoto keyFound;\n\t\t}\n\n\tfor (i = 0; modKeys[i].keyval != GDK_KEY_Print; i++)\n\t\tif (modKeys[i].keyval == e->keyval) {\n\t\t\tke.Modifier = modKeys[i].mod;\n\t\t\t// don't include the modifier in ke.Modifiers\n\t\t\tke.Modifiers &= ~ke.Modifier;\n\t\t\tgoto keyFound;\n\t\t}\n\n\tif (uiprivFromScancode(e->hardware_keycode - 8, &ke))\n\t\tgoto keyFound;\n\n\t// no supported key found; treat as unhandled\n\treturn 0;\n\nkeyFound:\n\treturn (*(a->ah->KeyEvent))(a->ah, a, &ke);\n}\n\nstatic gboolean areaWidget_key_press_event(GtkWidget *w, GdkEventKey *e)\n{\n\tareaWidget *aw = areaWidget(w);\n\tuiArea *a = aw->a;\n\n\tif (areaKeyEvent(a, 0, e))\n\t\treturn GDK_EVENT_STOP;\n\treturn GDK_EVENT_PROPAGATE;\n}\n\nstatic gboolean areaWidget_key_release_event(GtkWidget *w, GdkEventKey *e)\n{\n\tareaWidget *aw = areaWidget(w);\n\tuiArea *a = aw->a;\n\n\tif (areaKeyEvent(a, 1, e))\n\t\treturn GDK_EVENT_STOP;\n\treturn GDK_EVENT_PROPAGATE;\n}\n\nenum {\n\tpArea = 1,\n\tnProps,\n};\n\nstatic GParamSpec *pspecArea;\n\nstatic void areaWidget_set_property(GObject *obj, guint prop, const GValue *value, GParamSpec *pspec)\n{\n\tareaWidget *aw = areaWidget(obj);\n\n\tswitch (prop) {\n\tcase pArea:\n\t\taw->a = (uiArea *) g_value_get_pointer(value);\n\t\taw->a->cc = &(aw->cc);\n\t\treturn;\n\t}\n\tG_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop, pspec);\n}\n\nstatic void areaWidget_get_property(GObject *obj, guint prop, GValue *value, GParamSpec *pspec)\n{\n\tG_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop, pspec);\n}\n\nstatic void areaWidget_class_init(areaWidgetClass *class)\n{\n\tG_OBJECT_CLASS(class)->dispose = areaWidget_dispose;\n\tG_OBJECT_CLASS(class)->finalize = areaWidget_finalize;\n\tG_OBJECT_CLASS(class)->set_property = areaWidget_set_property;\n\tG_OBJECT_CLASS(class)->get_property = areaWidget_get_property;\n\n\tGTK_WIDGET_CLASS(class)->size_allocate = areaWidget_size_allocate;\n\tGTK_WIDGET_CLASS(class)->draw = areaWidget_draw;\n\tGTK_WIDGET_CLASS(class)->get_preferred_height = areaWidget_get_preferred_height;\n\tGTK_WIDGET_CLASS(class)->get_preferred_width = areaWidget_get_preferred_width;\n\tGTK_WIDGET_CLASS(class)->button_press_event = areaWidget_button_press_event;\n\tGTK_WIDGET_CLASS(class)->button_release_event = areaWidget_button_release_event;\n\tGTK_WIDGET_CLASS(class)->motion_notify_event = areaWidget_motion_notify_event;\n\tGTK_WIDGET_CLASS(class)->enter_notify_event = areaWidget_enter_notify_event;\n\tGTK_WIDGET_CLASS(class)->leave_notify_event = areaWidget_leave_notify_event;\n\tGTK_WIDGET_CLASS(class)->key_press_event = areaWidget_key_press_event;\n\tGTK_WIDGET_CLASS(class)->key_release_event = areaWidget_key_release_event;\n\n\tpspecArea = g_param_spec_pointer(\"libui-area\",\n\t\t\"libui-area\",\n\t\t\"uiArea.\",\n\t\tG_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);\n\tg_object_class_install_property(G_OBJECT_CLASS(class), pArea, pspecArea);\n}\n\n// control implementation\n\nuiUnixControlAllDefaults(uiArea)\n\nvoid uiAreaSetSize(uiArea *a, int width, int height)\n{\n\tif (!a->scrolling)\n\t\tuiprivUserBug(\"You cannot call uiAreaSetSize() on a non-scrolling uiArea. (area: %p)\", a);\n\ta->scrollWidth = width;\n\ta->scrollHeight = height;\n\tgtk_widget_queue_resize(a->areaWidget);\n}\n\nvoid uiAreaQueueRedrawAll(uiArea *a)\n{\n\tgtk_widget_queue_draw(a->areaWidget);\n}\n\nvoid uiAreaScrollTo(uiArea *a, double x, double y, double width, double height)\n{\n\t// TODO\n\t// TODO adjust adjustments and find source for that\n}\n\nvoid uiAreaBeginUserWindowMove(uiArea *a)\n{\n\tGtkWidget *toplevel;\n\n\tif (a->dragevent == NULL)\n\t\tuiprivUserBug(\"cannot call uiAreaBeginUserWindowMove() outside of a Mouse() with Down != 0\");\n\t// TODO don't we have a libui function for this? did I scrap it?\n\t// TODO widget or areaWidget?\n\ttoplevel = gtk_widget_get_toplevel(a->widget);\n\tif (toplevel == NULL) {\n\t\t// TODO\n\t\treturn;\n\t}\n\t// the docs say to do this\n\tif (!gtk_widget_is_toplevel(toplevel)) {\n\t\t// TODO\n\t\treturn;\n\t}\n\tif (!GTK_IS_WINDOW(toplevel)) {\n\t\t// TODO\n\t\treturn;\n\t}\n\tgtk_window_begin_move_drag(GTK_WINDOW(toplevel),\n\t\ta->dragevent->button,\n\t\ta->dragevent->x_root,\t\t// TODO are these correct?\n\t\ta->dragevent->y_root,\n\t\ta->dragevent->time);\n}\n\nstatic const GdkWindowEdge edges[] = {\n\t[uiWindowResizeEdgeLeft] = GDK_WINDOW_EDGE_WEST,\n\t[uiWindowResizeEdgeTop] = GDK_WINDOW_EDGE_NORTH,\n\t[uiWindowResizeEdgeRight] = GDK_WINDOW_EDGE_EAST,\n\t[uiWindowResizeEdgeBottom] = GDK_WINDOW_EDGE_SOUTH,\n\t[uiWindowResizeEdgeTopLeft] = GDK_WINDOW_EDGE_NORTH_WEST,\n\t[uiWindowResizeEdgeTopRight] = GDK_WINDOW_EDGE_NORTH_EAST,\n\t[uiWindowResizeEdgeBottomLeft] = GDK_WINDOW_EDGE_SOUTH_WEST,\n\t[uiWindowResizeEdgeBottomRight] = GDK_WINDOW_EDGE_SOUTH_EAST,\n};\n\nvoid uiAreaBeginUserWindowResize(uiArea *a, uiWindowResizeEdge edge)\n{\n\tGtkWidget *toplevel;\n\n\tif (a->dragevent == NULL)\n\t\tuiprivUserBug(\"cannot call uiAreaBeginUserWindowResize() outside of a Mouse() with Down != 0\");\n\t// TODO don't we have a libui function for this? did I scrap it?\n\t// TODO widget or areaWidget?\n\ttoplevel = gtk_widget_get_toplevel(a->widget);\n\tif (toplevel == NULL) {\n\t\t// TODO\n\t\treturn;\n\t}\n\t// the docs say to do this\n\tif (!gtk_widget_is_toplevel(toplevel)) {\n\t\t// TODO\n\t\treturn;\n\t}\n\tif (!GTK_IS_WINDOW(toplevel)) {\n\t\t// TODO\n\t\treturn;\n\t}\n\tgtk_window_begin_resize_drag(GTK_WINDOW(toplevel),\n\t\tedges[edge],\n\t\ta->dragevent->button,\n\t\ta->dragevent->x_root,\t\t// TODO are these correct?\n\t\ta->dragevent->y_root,\n\t\ta->dragevent->time);\n}\n\nuiArea *uiNewArea(uiAreaHandler *ah)\n{\n\tuiArea *a;\n\n\tuiUnixNewControl(uiArea, a);\n\n\ta->ah = ah;\n\ta->scrolling = FALSE;\n\n\ta->areaWidget = GTK_WIDGET(g_object_new(areaWidgetType,\n\t\t\"libui-area\", a,\n\t\tNULL));\n\ta->drawingArea = GTK_DRAWING_AREA(a->areaWidget);\n\ta->area = areaWidget(a->areaWidget);\n\n\ta->widget = a->areaWidget;\n\n\treturn a;\n}\n\nuiArea *uiNewScrollingArea(uiAreaHandler *ah, int width, int height)\n{\n\tuiArea *a;\n\n\tuiUnixNewControl(uiArea, a);\n\n\ta->ah = ah;\n\ta->scrolling = TRUE;\n\ta->scrollWidth = width;\n\ta->scrollHeight = height;\n\n\ta->swidget = gtk_scrolled_window_new(NULL, NULL);\n\ta->scontainer = GTK_CONTAINER(a->swidget);\n\ta->sw = GTK_SCROLLED_WINDOW(a->swidget);\n\n\ta->areaWidget = GTK_WIDGET(g_object_new(areaWidgetType,\n\t\t\"libui-area\", a,\n\t\tNULL));\n\ta->drawingArea = GTK_DRAWING_AREA(a->areaWidget);\n\ta->area = areaWidget(a->areaWidget);\n\n\ta->widget = a->swidget;\n\n\tgtk_container_add(a->scontainer, a->areaWidget);\n\t// and make the area visible; only the scrolled window's visibility is controlled by libui\n\tgtk_widget_show(a->areaWidget);\n\n\treturn a;\n}\n"
  },
  {
    "path": "unix/attrstr.c",
    "content": "// 12 february 2017\n#include \"uipriv_unix.h\"\n#include \"attrstr.h\"\n\n// TODO pango alpha attributes turn 0 into 65535 :|\n\n// TODO make this name less generic?\nstruct foreachParams {\n\tPangoAttrList *attrs;\n};\n\nstatic void addattr(struct foreachParams *p, size_t start, size_t end, PangoAttribute *attr)\n{\n\tif (attr == NULL)\t\t// in case of a future attribute\n\t\treturn;\n\tattr->start_index = start;\n\tattr->end_index = end;\n\tpango_attr_list_insert(p->attrs, attr);\n}\n\nstatic uiForEach processAttribute(const uiAttributedString *s, const uiAttribute *attr, size_t start, size_t end, void *data)\n{\n\tstruct foreachParams *p = (struct foreachParams *) data;\n\tdouble r, g, b, a;\n\tPangoUnderline underline;\n\tuiUnderlineColor colorType;\n\tconst uiOpenTypeFeatures *features;\n\tGString *featurestr;\n\n\tswitch (uiAttributeGetType(attr)) {\n\tcase uiAttributeTypeFamily:\n\t\taddattr(p, start, end,\n\t\t\tpango_attr_family_new(uiAttributeFamily(attr)));\n\t\tbreak;\n\tcase uiAttributeTypeSize:\n\t\taddattr(p, start, end,\n\t\t\tpango_attr_size_new(cairoToPango(uiAttributeSize(attr))));\n\t\tbreak;\n\tcase uiAttributeTypeWeight:\n\t\t// TODO reverse the misalignment from drawtext.c if it is corrected \n\t\taddattr(p, start, end,\n\t\t\tpango_attr_weight_new(uiprivWeightToPangoWeight(uiAttributeWeight(attr))));\n\t\tbreak;\n\tcase uiAttributeTypeItalic:\n\t\taddattr(p, start, end,\n\t\t\tpango_attr_style_new(uiprivItalicToPangoStyle(uiAttributeItalic(attr))));\n\t\tbreak;\n\tcase uiAttributeTypeStretch:\n\t\taddattr(p, start, end,\n\t\t\tpango_attr_stretch_new(uiprivStretchToPangoStretch(uiAttributeStretch(attr))));\n\t\tbreak;\n\tcase uiAttributeTypeColor:\n\t\tuiAttributeColor(attr, &r, &g, &b, &a);\n\t\taddattr(p, start, end,\n\t\t\tpango_attr_foreground_new(\n\t\t\t\t(guint16) (r * 65535.0),\n\t\t\t\t(guint16) (g * 65535.0),\n\t\t\t\t(guint16) (b * 65535.0)));\n\t\taddattr(p, start, end,\n\t\t\tuiprivFUTURE_pango_attr_foreground_alpha_new(\n\t\t\t\t(guint16) (a * 65535.0)));\n\t\tbreak;\n\tcase uiAttributeTypeBackground:\n\t\t// TODO make sure this works properly with line paragraph spacings (after figuring out what that means, of course)\n\t\tuiAttributeColor(attr, &r, &g, &b, &a);\n\t\taddattr(p, start, end,\n\t\t\tpango_attr_background_new(\n\t\t\t\t(guint16) (r * 65535.0),\n\t\t\t\t(guint16) (g * 65535.0),\n\t\t\t\t(guint16) (b * 65535.0)));\n\t\taddattr(p, start, end,\n\t\t\tuiprivFUTURE_pango_attr_background_alpha_new(\n\t\t\t\t(guint16) (a * 65535.0)));\n\t\tbreak;\n\tcase uiAttributeTypeUnderline:\n\t\tswitch (uiAttributeUnderline(attr)) {\n\t\tcase uiUnderlineNone:\n\t\t\tunderline = PANGO_UNDERLINE_NONE;\n\t\t\tbreak;\n\t\tcase uiUnderlineSingle:\n\t\t\tunderline = PANGO_UNDERLINE_SINGLE;\n\t\t\tbreak;\n\t\tcase uiUnderlineDouble:\n\t\t\tunderline = PANGO_UNDERLINE_DOUBLE;\n\t\t\tbreak;\n\t\tcase uiUnderlineSuggestion:\n\t\t\tunderline = PANGO_UNDERLINE_ERROR;\n\t\t\tbreak;\n\t\t}\n\t\taddattr(p, start, end,\n\t\t\tpango_attr_underline_new(underline));\n\t\tbreak;\n\tcase uiAttributeTypeUnderlineColor:\n\t\tuiAttributeUnderlineColor(attr, &colorType, &r, &g, &b, &a);\n\t\tswitch (colorType) {\n\t\tcase uiUnderlineColorCustom:\n\t\t\taddattr(p, start, end,\n\t\t\t\tpango_attr_underline_color_new(\n\t\t\t\t\t(guint16) (r * 65535.0),\n\t\t\t\t\t(guint16) (g * 65535.0),\n\t\t\t\t\t(guint16) (b * 65535.0)));\n\t\t\tbreak;\n\t\tcase uiUnderlineColorSpelling:\n\t\t\t// TODO GtkTextView style property error-underline-color\n\t\t\taddattr(p, start, end,\n\t\t\t\tpango_attr_underline_color_new(65535, 0, 0));\n\t\t\tbreak;\n\t\tcase uiUnderlineColorGrammar:\n\t\t\t// TODO find a more appropriate color\n\t\t\taddattr(p, start, end,\n\t\t\t\tpango_attr_underline_color_new(0, 65535, 0));\n\t\t\tbreak;\n\t\tcase uiUnderlineColorAuxiliary:\n\t\t\t// TODO find a more appropriate color\n\t\t\taddattr(p, start, end,\n\t\t\t\tpango_attr_underline_color_new(0, 0, 65535));\n\t\t\tbreak;\n\t\t}\n\t\tbreak;\n\tcase uiAttributeTypeFeatures:\n\t\t// only generate an attribute if the features object is not NULL\n\t\t// TODO state that this is allowed\n\t\tfeatures = uiAttributeFeatures(attr);\n\t\tif (features == NULL)\n\t\t\tbreak;\n\t\tfeaturestr = uiprivOpenTypeFeaturesToPangoCSSFeaturesString(features);\n\t\taddattr(p, start, end,\n\t\t\tuiprivFUTURE_pango_attr_font_features_new(featurestr->str));\n\t\tg_string_free(featurestr, TRUE);\n\t\tbreak;\n\tdefault:\n\t\t// TODO complain\n\t\t;\n\t}\n\treturn uiForEachContinue;\n}\n\nPangoAttrList *uiprivAttributedStringToPangoAttrList(uiDrawTextLayoutParams *p)\n{\n\tstruct foreachParams fep;\n\n\tfep.attrs = pango_attr_list_new();\n\tuiAttributedStringForEachAttribute(p->String, processAttribute, &fep);\n\treturn fep.attrs;\n}\n"
  },
  {
    "path": "unix/attrstr.h",
    "content": "// 11 march 2018\n#include \"../common/attrstr.h\"\n\n// See https://developer.gnome.org/pango/1.30/pango-Cairo-Rendering.html#pango-Cairo-Rendering.description\n// 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\n#define pangoToCairo(pango) (pango_units_to_double(pango))\n#define cairoToPango(cairo) (pango_units_from_double(cairo))\n\n// opentype.c\nextern GString *uiprivOpenTypeFeaturesToPangoCSSFeaturesString(const uiOpenTypeFeatures *otf);\n\n// fontmatch.c\nextern PangoWeight uiprivWeightToPangoWeight(uiTextWeight w);\nextern PangoStyle uiprivItalicToPangoStyle(uiTextItalic i);\nextern PangoStretch uiprivStretchToPangoStretch(uiTextStretch s);\nextern PangoFontDescription *uiprivFontDescriptorToPangoFontDescription(const uiFontDescriptor *uidesc);\nextern void uiprivFontDescriptorFromPangoFontDescription(PangoFontDescription *pdesc, uiFontDescriptor *uidesc);\n\n// attrstr.c\nextern PangoAttrList *uiprivAttributedStringToPangoAttrList(uiDrawTextLayoutParams *p);\n"
  },
  {
    "path": "unix/box.c",
    "content": "// 7 april 2015\n#include \"uipriv_unix.h\"\n\nstruct boxChild {\n\tuiControl *c;\n\tint stretchy;\n\tgboolean oldhexpand;\n\tGtkAlign oldhalign;\n\tgboolean oldvexpand;\n\tGtkAlign oldvalign;\n};\n\nstruct uiBox {\n\tuiUnixControl c;\n\tGtkWidget *widget;\n\tGtkContainer *container;\n\tGtkBox *box;\n\tGArray *controls;\n\tint vertical;\n\tint padded;\n\tGtkSizeGroup *stretchygroup;\t\t// ensures all stretchy controls have the same size\n};\n\nuiUnixControlAllDefaultsExceptDestroy(uiBox)\n\n#define ctrl(b, i) &g_array_index(b->controls, struct boxChild, i)\n\nstatic void uiBoxDestroy(uiControl *c)\n{\n\tuiBox *b = uiBox(c);\n\tstruct boxChild *bc;\n\tguint i;\n\n\t// kill the size group\n\tg_object_unref(b->stretchygroup);\n\t// free all controls\n\tfor (i = 0; i < b->controls->len; i++) {\n\t\tbc = ctrl(b, i);\n\t\tuiControlSetParent(bc->c, NULL);\n\t\t// and make sure the widget itself stays alive\n\t\tuiUnixControlSetContainer(uiUnixControl(bc->c), b->container, TRUE);\n\t\tuiControlDestroy(bc->c);\n\t}\n\tg_array_free(b->controls, TRUE);\n\t// and then ourselves\n\tg_object_unref(b->widget);\n\tuiFreeControl(uiControl(b));\n}\n\nvoid uiBoxAppend(uiBox *b, uiControl *c, int stretchy)\n{\n\tstruct boxChild bc;\n\tGtkWidget *widget;\n\n\tbc.c = c;\n\tbc.stretchy = stretchy;\n\twidget = GTK_WIDGET(uiControlHandle(bc.c));\n\tbc.oldhexpand = gtk_widget_get_hexpand(widget);\n\tbc.oldhalign = gtk_widget_get_halign(widget);\n\tbc.oldvexpand = gtk_widget_get_vexpand(widget);\n\tbc.oldvalign = gtk_widget_get_valign(widget);\n\n\tif (bc.stretchy) {\n\t\tif (b->vertical) {\n\t\t\tgtk_widget_set_vexpand(widget, TRUE);\n\t\t\tgtk_widget_set_valign(widget, GTK_ALIGN_FILL);\n\t\t} else {\n\t\t\tgtk_widget_set_hexpand(widget, TRUE);\n\t\t\tgtk_widget_set_halign(widget, GTK_ALIGN_FILL);\n\t\t}\n\t\tgtk_size_group_add_widget(b->stretchygroup, widget);\n\t} else\n\t\tif (b->vertical)\n\t\t\tgtk_widget_set_vexpand(widget, FALSE);\n\t\telse\n\t\t\tgtk_widget_set_hexpand(widget, FALSE);\n\t// and make them fill the opposite direction\n\tif (b->vertical) {\n\t\tgtk_widget_set_hexpand(widget, TRUE);\n\t\tgtk_widget_set_halign(widget, GTK_ALIGN_FILL);\n\t} else {\n\t\tgtk_widget_set_vexpand(widget, TRUE);\n\t\tgtk_widget_set_valign(widget, GTK_ALIGN_FILL);\n\t}\n\n\tuiControlSetParent(bc.c, uiControl(b));\n\tuiUnixControlSetContainer(uiUnixControl(bc.c), b->container, FALSE);\n\tg_array_append_val(b->controls, bc);\n}\n\nvoid uiBoxDelete(uiBox *b, int index)\n{\n\tstruct boxChild *bc;\n\tGtkWidget *widget;\n\n\tbc = ctrl(b, index);\n\twidget = GTK_WIDGET(uiControlHandle(bc->c));\n\n\tuiControlSetParent(bc->c, NULL);\n\tuiUnixControlSetContainer(uiUnixControl(bc->c), b->container, TRUE);\n\n\tif (bc->stretchy)\n\t\tgtk_size_group_remove_widget(b->stretchygroup, widget);\n\tgtk_widget_set_hexpand(widget, bc->oldhexpand);\n\tgtk_widget_set_halign(widget, bc->oldhalign);\n\tgtk_widget_set_vexpand(widget, bc->oldvexpand);\n\tgtk_widget_set_valign(widget, bc->oldvalign);\n\n\tg_array_remove_index(b->controls, index);\n}\n\nint uiBoxPadded(uiBox *b)\n{\n\treturn b->padded;\n}\n\nvoid uiBoxSetPadded(uiBox *b, int padded)\n{\n\tb->padded = padded;\n\tif (b->padded)\n\t\tif (b->vertical)\n\t\t\tgtk_box_set_spacing(b->box, uiprivGTKYPadding);\n\t\telse\n\t\t\tgtk_box_set_spacing(b->box, uiprivGTKXPadding);\n\telse\n\t\tgtk_box_set_spacing(b->box, 0);\n}\n\nstatic uiBox *finishNewBox(GtkOrientation orientation)\n{\n\tuiBox *b;\n\n\tuiUnixNewControl(uiBox, b);\n\n\tb->widget = gtk_box_new(orientation, 0);\n\tb->container = GTK_CONTAINER(b->widget);\n\tb->box = GTK_BOX(b->widget);\n\n\tb->vertical = orientation == GTK_ORIENTATION_VERTICAL;\n\n\tif (b->vertical)\n\t\tb->stretchygroup = gtk_size_group_new(GTK_SIZE_GROUP_VERTICAL);\n\telse\n\t\tb->stretchygroup = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);\n\n\tb->controls = g_array_new(FALSE, TRUE, sizeof (struct boxChild));\n\n\treturn b;\n}\n\nuiBox *uiNewHorizontalBox(void)\n{\n\treturn finishNewBox(GTK_ORIENTATION_HORIZONTAL);\n}\n\nuiBox *uiNewVerticalBox(void)\n{\n\treturn finishNewBox(GTK_ORIENTATION_VERTICAL);\n}\n"
  },
  {
    "path": "unix/button.c",
    "content": "// 10 june 2015\n#include \"uipriv_unix.h\"\n\nstruct uiButton {\n\tuiUnixControl c;\n\tGtkWidget *widget;\n\tGtkButton *button;\n\tvoid (*onClicked)(uiButton *, void *);\n\tvoid *onClickedData;\n};\n\nuiUnixControlAllDefaults(uiButton)\n\nstatic void onClicked(GtkButton *button, gpointer data)\n{\n\tuiButton *b = uiButton(data);\n\n\t(*(b->onClicked))(b, b->onClickedData);\n}\n\nstatic void defaultOnClicked(uiButton *b, void *data)\n{\n\t// do nothing\n}\n\nchar *uiButtonText(uiButton *b)\n{\n\treturn uiUnixStrdupText(gtk_button_get_label(b->button));\n}\n\nvoid uiButtonSetText(uiButton *b, const char *text)\n{\n\tgtk_button_set_label(b->button, text);\n}\n\nvoid uiButtonOnClicked(uiButton *b, void (*f)(uiButton *, void *), void *data)\n{\n\tb->onClicked = f;\n\tb->onClickedData = data;\n}\n\nuiButton *uiNewButton(const char *text)\n{\n\tuiButton *b;\n\n\tuiUnixNewControl(uiButton, b);\n\n\tb->widget = gtk_button_new_with_label(text);\n\tb->button = GTK_BUTTON(b->widget);\n\n\tg_signal_connect(b->widget, \"clicked\", G_CALLBACK(onClicked), b);\n\tuiButtonOnClicked(b, defaultOnClicked, NULL);\n\n\treturn b;\n}\n"
  },
  {
    "path": "unix/cellrendererbutton.c",
    "content": "// 28 june 2016\n#include \"uipriv_unix.h\"\n\n// TODOs\n// - it's a rather tight fit\n// - selected row text color is white (TODO not on 3.22)\n// - accessibility\n// - right side too big? (TODO reverify)\n\n#define cellRendererButtonType (cellRendererButton_get_type())\n#define cellRendererButton(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), cellRendererButtonType, cellRendererButton))\n#define isCellRendererButton(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), cellRendererButtonType))\n#define cellRendererButtonClass(class) (G_TYPE_CHECK_CLASS_CAST((class), cellRendererButtonType, cellRendererButtonClass))\n#define isCellRendererButtonClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), cellRendererButton))\n#define getCellRendererButtonClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), cellRendererButtonType, cellRendererButtonClass))\n\ntypedef struct cellRendererButton cellRendererButton;\ntypedef struct cellRendererButtonClass cellRendererButtonClass;\n\nstruct cellRendererButton {\n\tGtkCellRenderer parent_instance;\n\tchar *text;\n};\n\nstruct cellRendererButtonClass {\n\tGtkCellRendererClass parent_class;\n};\n\nG_DEFINE_TYPE(cellRendererButton, cellRendererButton, GTK_TYPE_CELL_RENDERER)\n\nstatic void cellRendererButton_init(cellRendererButton *c)\n{\n\tg_object_set(c, \"mode\", GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL);\n\t// the standard cell renderers all do this\n\tgtk_cell_renderer_set_padding(GTK_CELL_RENDERER(c), 2, 2);\n}\n\nstatic void cellRendererButton_dispose(GObject *obj)\n{\n\tG_OBJECT_CLASS(cellRendererButton_parent_class)->dispose(obj);\n}\n\nstatic void cellRendererButton_finalize(GObject *obj)\n{\n\tcellRendererButton *c = cellRendererButton(obj);\n\n\tif (c->text != NULL) {\n\t\tg_free(c->text);\n\t\tc->text = NULL;\n\t}\n\tG_OBJECT_CLASS(cellRendererButton_parent_class)->finalize(obj);\n}\n\nstatic GtkSizeRequestMode cellRendererButton_get_request_mode(GtkCellRenderer *r)\n{\n\treturn GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;\n}\n\n// 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\n// TODO how does this seem to work with highlight on 3.22, and does that work with 3.10 too\nstatic GtkStyleContext *setButtonStyle(GtkWidget *widget)\n{\n\tGtkStyleContext *base, *context;\n\tGtkWidgetPath *path;\n\n\tbase = gtk_widget_get_style_context(widget);\n\tcontext = gtk_style_context_new();\n\n\tpath = gtk_widget_path_copy(gtk_style_context_get_path(base));\n\tgtk_widget_path_append_type(path, G_TYPE_NONE);\n\tif (!uiprivFUTURE_gtk_widget_path_iter_set_object_name(path, -1, \"button\"))\n\t\t// not on 3.20; try the type\n\t\tgtk_widget_path_iter_set_object_type(path, -1, GTK_TYPE_BUTTON);\n\n\tgtk_style_context_set_path(context, path);\n\tgtk_style_context_set_parent(context, base);\n\t// 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\n\t// TODO make a future for that too\n\tgtk_style_context_set_state(context, gtk_style_context_get_state(base));\n\tgtk_widget_path_unref(path);\n\n\t// and if the above widget path screwery stil doesn't work, this will\n\tgtk_style_context_add_class(context, GTK_STYLE_CLASS_BUTTON);\n\n\treturn context;\n}\n\nvoid unsetButtonStyle(GtkStyleContext *context)\n{\n\tg_object_unref(context);\n}\n\n// this is based on what GtkCellRendererText in GTK+ 3.22.30 does\n// TODO compare to 3.10.9 (https://gitlab.gnome.org/GNOME/gtk/blob/3.10.9/gtk/gtkcellrenderertext.c)\nstatic PangoLayout *cellRendererButtonPangoLayout(cellRendererButton *c, GtkWidget *widget)\n{\n\tPangoLayout *layout;\n\n\tlayout = gtk_widget_create_pango_layout(widget, c->text);\n\tpango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE);\n\tpango_layout_set_width(layout, -1);\n\tpango_layout_set_wrap(layout, PANGO_WRAP_CHAR);\n\tpango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);\n\treturn layout;\n}\n\n// this is based on what GtkCellRendererText in GTK+ 3.22.30 does\n// TODO compare to 3.10.9 (https://gitlab.gnome.org/GNOME/gtk/blob/3.10.9/gtk/gtkcellrenderertext.c)\nstatic void cellRendererButtonSize(cellRendererButton *c, GtkWidget *widget, PangoLayout *layout, const GdkRectangle *cell_area, gint *xoff, gint *yoff, gint *width, gint *height)\n{\n\tPangoRectangle rect;\n\tgint xpad, ypad;\n\tgfloat xalign, yalign;\n\n\tgtk_cell_renderer_get_padding(GTK_CELL_RENDERER(c), &xpad, &ypad);\n\tpango_layout_get_pixel_extents(layout, NULL, &rect);\n\tif (rect.width > cell_area->width - (2 * xpad))\n\t\trect.width = cell_area->width - (2 * xpad);\n\tif (rect.height > cell_area->height - (2 * ypad))\n\t\trect.height = cell_area->height - (2 * ypad);\n\n\tgtk_cell_renderer_get_alignment(GTK_CELL_RENDERER(c), &xalign, &yalign);\n\tif (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL)\n\t\txalign = 1.0 - xalign;\n\tif (xoff != NULL) {\n\t\t*xoff = cell_area->width - (rect.width + (2 * xpad));\n\t\t*xoff = (gint) ((gfloat) (*xoff) * xalign);\n\t}\n\tif (yoff != NULL) {\n\t\t*yoff = cell_area->height - (rect.height + (2 * ypad));\n\t\t*yoff = (gint) ((gfloat) (*yoff) * yalign);\n\t\tif (*yoff < 0)\n\t\t\t*yoff = 0;\n\t}\n\tif (width != NULL)\n\t\t*width = rect.width - (2 * xpad);\n\tif (height != NULL)\n\t\t*height = rect.height - (2 * ypad);\n}\n\n// this is based on what GtkCellRendererText in GTK+ 3.22.30 does\n// TODO compare to 3.10.9 (https://gitlab.gnome.org/GNOME/gtk/blob/3.10.9/gtk/gtkcellrenderertext.c)\nstatic void cellRendererButton_get_preferred_width(GtkCellRenderer *r, GtkWidget *widget, gint *minimum, gint *natural)\n{\n\tcellRendererButton *c = cellRendererButton(r);\n\tgint xpad;\n\tPangoLayout *layout;\n\tPangoRectangle rect;\n\tgint out;\n\n\tgtk_cell_renderer_get_padding(GTK_CELL_RENDERER(c), &xpad, NULL);\n\n\tlayout = cellRendererButtonPangoLayout(c, widget);\n\tpango_layout_get_extents(layout, NULL, &rect);\n\tg_object_unref(layout);\n\n\tout = PANGO_PIXELS_CEIL(rect.width) + (2 * xpad);\n\tif (rect.x > 0)\n\t\tout += rect.x;\n\tif (minimum != NULL)\n\t\t*minimum = out;\n\tif (natural != NULL)\n\t\t*natural = out;\n}\n\n// this is based on what GtkCellRendererText in GTK+ 3.22.30 does\n// TODO compare to 3.10.9 (https://gitlab.gnome.org/GNOME/gtk/blob/3.10.9/gtk/gtkcellrenderertext.c)\nstatic void cellRendererButton_get_preferred_height_for_width(GtkCellRenderer *r, GtkWidget *widget, gint width, gint *minimum, gint *natural)\n{\n\tcellRendererButton *c = cellRendererButton(r);\n\tgint xpad, ypad;\n\tPangoLayout *layout;\n\tgint height;\n\tgint out;\n\n\tgtk_cell_renderer_get_padding(GTK_CELL_RENDERER(c), &xpad, &ypad);\n\n\tlayout = cellRendererButtonPangoLayout(c, widget);\n\tpango_layout_set_width(layout, (width + (xpad * 2)) * PANGO_SCALE);\n\tpango_layout_get_pixel_size(layout, NULL, &height);\n\tg_object_unref(layout);\n\n\tout = height + (ypad * 2);\n\tif (minimum != NULL)\n\t\t*minimum = out;\n\tif (natural != NULL)\n\t\t*natural = out;\n}\n\n// this is based on what GtkCellRendererText in GTK+ 3.22.30 does\n// TODO compare to 3.10.9 (https://gitlab.gnome.org/GNOME/gtk/blob/3.10.9/gtk/gtkcellrenderertext.c)\nstatic void cellRendererButton_get_preferred_height(GtkCellRenderer *r, GtkWidget *widget, gint *minimum, gint *natural)\n{\n\tgint width;\n\n\tgtk_cell_renderer_get_preferred_width(r, widget, &width, NULL);\n\tgtk_cell_renderer_get_preferred_height_for_width(r, widget, width, minimum, natural);\n}\n\n// this is based on what GtkCellRendererText in GTK+ 3.22.30 does\n// TODO compare to 3.10.9 (https://gitlab.gnome.org/GNOME/gtk/blob/3.10.9/gtk/gtkcellrenderertext.c)\nstatic void cellRendererButton_get_aligned_area(GtkCellRenderer *r, GtkWidget *widget, GtkCellRendererState flags, const GdkRectangle *cell_area, GdkRectangle *aligned_area)\n{\n\tcellRendererButton *c = cellRendererButton(r);\n\tPangoLayout *layout;\n\tgint xoff, yoff;\n\tgint width, height;\n\n\tlayout = cellRendererButtonPangoLayout(c, widget);\n\tcellRendererButtonSize(c, widget, layout, cell_area,\n\t\t&xoff, &yoff, &width, &height);\n\n\taligned_area->x = cell_area->x + xoff;\n\taligned_area->y = cell_area->y + yoff;\n\taligned_area->width = width;\n\taligned_area->height = height;\n\n\tg_object_unref(layout);\n}\n\n// 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)\nstatic void cellRendererButton_render(GtkCellRenderer *r, cairo_t *cr, GtkWidget *widget, const GdkRectangle *background_area, const GdkRectangle *cell_area, GtkCellRendererState flags)\n{\n\tcellRendererButton *c = cellRendererButton(r);\n\tgint xpad, ypad;\n\tGdkRectangle alignedArea;\n\tgint xoff, yoff;\n\tGtkStyleContext *context;\n\tPangoLayout *layout;\n\tPangoRectangle rect;\n\n\tgtk_cell_renderer_get_padding(GTK_CELL_RENDERER(c), &xpad, &ypad);\n\tlayout = cellRendererButtonPangoLayout(c, widget);\n\tcellRendererButtonSize(c, widget, layout, cell_area,\n\t\t&xoff, &yoff, NULL, NULL);\n\n\tcontext = setButtonStyle(widget);\n\n\tgtk_render_background(context, cr,\n\t\tbackground_area->x + xpad,\n\t\tbackground_area->y + ypad,\n\t\tbackground_area->width - (xpad * 2),\n\t\tbackground_area->height - (ypad * 2));\n\tgtk_render_frame(context, cr,\n\t\tbackground_area->x + xpad,\n\t\tbackground_area->y + ypad,\n\t\tbackground_area->width - (xpad * 2),\n\t\tbackground_area->height - (ypad * 2));\n\n\tpango_layout_get_pixel_extents(layout, NULL, &rect);\n\txoff -= rect.x;\n\tgtk_render_layout(context, cr,\n\t\tcell_area->x + xoff + xpad,\n\t\tcell_area->y + yoff + ypad,\n\t\tlayout);\n\n\tunsetButtonStyle(context);\n\tg_object_unref(layout);\n}\n\nstatic guint clickedSignal;\n\nstatic gboolean cellRendererButton_activate(GtkCellRenderer *r, GdkEvent *e, GtkWidget *widget, const gchar *path, const GdkRectangle *background_area, const GdkRectangle *cell_area, GtkCellRendererState flags)\n{\n\tg_signal_emit(r, clickedSignal, 0, path);\n\treturn TRUE;\n}\n\nstatic GParamSpec *props[2] = { NULL, NULL };\n\nstatic void cellRendererButton_set_property(GObject *object, guint prop, const GValue *value, GParamSpec *pspec)\n{\n\tcellRendererButton *c = cellRendererButton(object);\n\n\tif (prop != 1) {\n\t\tG_OBJECT_WARN_INVALID_PROPERTY_ID(c, prop, pspec);\n\t\treturn;\n\t}\n\tif (c->text != NULL)\n\t\tg_free(c->text);\n\tc->text = g_value_dup_string(value);\n\t// GtkCellRendererText doesn't queue a redraw; we won't either\n}\n\nstatic void cellRendererButton_get_property(GObject *object, guint prop, GValue *value, GParamSpec *pspec)\n{\n\tcellRendererButton *c = cellRendererButton(object);\n\n\tif (prop != 1) {\n\t\tG_OBJECT_WARN_INVALID_PROPERTY_ID(c, prop, pspec);\n\t\treturn;\n\t}\n\tg_value_set_string(value, c->text);\n}\n\nstatic void cellRendererButton_class_init(cellRendererButtonClass *class)\n{\n\tG_OBJECT_CLASS(class)->dispose = cellRendererButton_dispose;\n\tG_OBJECT_CLASS(class)->finalize = cellRendererButton_finalize;\n\tG_OBJECT_CLASS(class)->set_property = cellRendererButton_set_property;\n\tG_OBJECT_CLASS(class)->get_property = cellRendererButton_get_property;\n\tGTK_CELL_RENDERER_CLASS(class)->get_request_mode = cellRendererButton_get_request_mode;\n\tGTK_CELL_RENDERER_CLASS(class)->get_preferred_width = cellRendererButton_get_preferred_width;\n\tGTK_CELL_RENDERER_CLASS(class)->get_preferred_height_for_width = cellRendererButton_get_preferred_height_for_width;\n\tGTK_CELL_RENDERER_CLASS(class)->get_preferred_height = cellRendererButton_get_preferred_height;\n\t// don't provide a get_preferred_width_for_height()\n\tGTK_CELL_RENDERER_CLASS(class)->get_aligned_area = cellRendererButton_get_aligned_area;\n\t// don't provide a get_size()\n\tGTK_CELL_RENDERER_CLASS(class)->render = cellRendererButton_render;\n\tGTK_CELL_RENDERER_CLASS(class)->activate = cellRendererButton_activate;\n\t// don't provide a start_editing()\n\n\tprops[1] = g_param_spec_string(\"text\",\n\t\t\"Text\",\n\t\t\"Button text\",\n\t\t\"\",\n\t\tG_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);\n\tg_object_class_install_properties(G_OBJECT_CLASS(class), 2, props);\n\n\tclickedSignal = g_signal_new(\"clicked\",\n\t\tG_TYPE_FROM_CLASS(class),\n\t\tG_SIGNAL_RUN_LAST,\n\t\t0,\n\t\tNULL, NULL, NULL,\n\t\tG_TYPE_NONE,\n\t\t1, G_TYPE_STRING);\n}\n\nGtkCellRenderer *uiprivNewCellRendererButton(void)\n{\n\treturn GTK_CELL_RENDERER(g_object_new(cellRendererButtonType, NULL));\n}\n"
  },
  {
    "path": "unix/checkbox.c",
    "content": "// 10 june 2015\n#include \"uipriv_unix.h\"\n\nstruct uiCheckbox {\n\tuiUnixControl c;\n\tGtkWidget *widget;\n\tGtkButton *button;\n\tGtkToggleButton *toggleButton;\n\tGtkCheckButton *checkButton;\n\tvoid (*onToggled)(uiCheckbox *, void *);\n\tvoid *onToggledData;\n\tgulong onToggledSignal;\n};\n\nuiUnixControlAllDefaults(uiCheckbox)\n\nstatic void onToggled(GtkToggleButton *b, gpointer data)\n{\n\tuiCheckbox *c = uiCheckbox(data);\n\n\t(*(c->onToggled))(c, c->onToggledData);\n}\n\nstatic void defaultOnToggled(uiCheckbox *c, void *data)\n{\n\t// do nothing\n}\n\nchar *uiCheckboxText(uiCheckbox *c)\n{\n\treturn uiUnixStrdupText(gtk_button_get_label(c->button));\n}\n\nvoid uiCheckboxSetText(uiCheckbox *c, const char *text)\n{\n\tgtk_button_set_label(GTK_BUTTON(c->button), text);\n}\n\nvoid uiCheckboxOnToggled(uiCheckbox *c, void (*f)(uiCheckbox *, void *), void *data)\n{\n\tc->onToggled = f;\n\tc->onToggledData = data;\n}\n\nint uiCheckboxChecked(uiCheckbox *c)\n{\n\treturn gtk_toggle_button_get_active(c->toggleButton) != FALSE;\n}\n\nvoid uiCheckboxSetChecked(uiCheckbox *c, int checked)\n{\n\tgboolean active;\n\n\tactive = FALSE;\n\tif (checked)\n\t\tactive = TRUE;\n\t// we need to inhibit sending of ::toggled because this WILL send a ::toggled otherwise\n\tg_signal_handler_block(c->toggleButton, c->onToggledSignal);\n\tgtk_toggle_button_set_active(c->toggleButton, active);\n\tg_signal_handler_unblock(c->toggleButton, c->onToggledSignal);\n}\n\nuiCheckbox *uiNewCheckbox(const char *text)\n{\n\tuiCheckbox *c;\n\n\tuiUnixNewControl(uiCheckbox, c);\n\n\tc->widget = gtk_check_button_new_with_label(text);\n\tc->button = GTK_BUTTON(c->widget);\n\tc->toggleButton = GTK_TOGGLE_BUTTON(c->widget);\n\tc->checkButton = GTK_CHECK_BUTTON(c->widget);\n\n\tc->onToggledSignal = g_signal_connect(c->widget, \"toggled\", G_CALLBACK(onToggled), c);\n\tuiCheckboxOnToggled(c, defaultOnToggled, NULL);\n\n\treturn c;\n}\n"
  },
  {
    "path": "unix/child.c",
    "content": "// 28 august 2015\n#include \"uipriv_unix.h\"\n\n// This file contains helpers for managing child controls.\n\nstruct uiprivChild {\n\tuiControl *c;\n\tGtkWidget *widget;\n\n\tgboolean oldhexpand;\n\tGtkAlign oldhalign;\n\tgboolean oldvexpand;\n\tGtkAlign oldvalign;\n\n\t// Some children can be boxed; that is, they can have an optionally-margined box around them.\n\t// uiGroup, uiTab, and uiWindow all do this.\n\tGtkWidget *box;\n\n\t// If the child is not boxed, this is its parent.\n\t// If the child is boxed, this is the box.\n\tGtkContainer *parent;\n\n\t// This flag is for users of these functions.\n\t// For uiBox, this is \"spaced\".\n\t// For uiTab, this is \"margined\". (uiGroup and uiWindow have to maintain their margined state themselves, since the margined state is independent of whether there is a child for those two.)\n\tint flag;\n};\n\nuiprivChild *uiprivNewChild(uiControl *child, uiControl *parent, GtkContainer *parentContainer)\n{\n\tuiprivChild *c;\n\n\tif (child == NULL)\n\t\treturn NULL;\n\n\tc = uiprivNew(uiprivChild);\n\tc->c = child;\n\tc->widget = GTK_WIDGET(uiControlHandle(c->c));\n\n\tc->oldhexpand = gtk_widget_get_hexpand(c->widget);\n\tc->oldhalign = gtk_widget_get_halign(c->widget);\n\tc->oldvexpand = gtk_widget_get_vexpand(c->widget);\n\tc->oldvalign = gtk_widget_get_valign(c->widget);\n\n\tuiControlSetParent(c->c, parent);\n\tuiUnixControlSetContainer(uiUnixControl(c->c), parentContainer, FALSE);\n\tc->parent = parentContainer;\n\n\treturn c;\n}\n\nuiprivChild *uiprivNewChildWithBox(uiControl *child, uiControl *parent, GtkContainer *parentContainer, int margined)\n{\n\tuiprivChild *c;\n\tGtkWidget *box;\n\n\tif (child == NULL)\n\t\treturn NULL;\n\tbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);\n\tgtk_widget_show(box);\n\tc = uiprivNewChild(child, parent, GTK_CONTAINER(box));\n\tgtk_widget_set_hexpand(c->widget, TRUE);\n\tgtk_widget_set_halign(c->widget, GTK_ALIGN_FILL);\n\tgtk_widget_set_vexpand(c->widget, TRUE);\n\tgtk_widget_set_valign(c->widget, GTK_ALIGN_FILL);\n\tc->box = box;\n\tgtk_container_add(parentContainer, c->box);\n\tuiprivChildSetMargined(c, margined);\n\treturn c;\n}\n\nvoid uiprivChildRemove(uiprivChild *c)\n{\n\tuiControlSetParent(c->c, NULL);\n\tuiUnixControlSetContainer(uiUnixControl(c->c), c->parent, TRUE);\n\n\tgtk_widget_set_hexpand(c->widget, c->oldhexpand);\n\tgtk_widget_set_halign(c->widget, c->oldhalign);\n\tgtk_widget_set_vexpand(c->widget, c->oldvexpand);\n\tgtk_widget_set_valign(c->widget, c->oldvalign);\n\n\tif (c->box != NULL)\n\t\tgtk_widget_destroy(c->box);\n\n\tuiprivFree(c);\n}\n\nvoid uiprivChildDestroy(uiprivChild *c)\n{\n\tuiControl *child;\n\n\tchild = c->c;\n\tuiprivChildRemove(c);\n\tuiControlDestroy(child);\n}\n\nGtkWidget *uiprivChildWidget(uiprivChild *c)\n{\n\treturn c->widget;\n}\n\nint uiprivChildFlag(uiprivChild *c)\n{\n\treturn c->flag;\n}\n\nvoid uiprivChildSetFlag(uiprivChild *c, int flag)\n{\n\tc->flag = flag;\n}\n\nGtkWidget *uiprivChildBox(uiprivChild *c)\n{\n\treturn c->box;\n}\n\nvoid uiprivChildSetMargined(uiprivChild *c, int margined)\n{\n\tuiprivSetMargined(GTK_CONTAINER(c->box), margined);\n}\n"
  },
  {
    "path": "unix/colorbutton.c",
    "content": "// 15 may 2016\n#include \"uipriv_unix.h\"\n\nstruct uiColorButton {\n\tuiUnixControl c;\n\tGtkWidget *widget;\n\tGtkButton *button;\n\tGtkColorButton *cb;\n\tGtkColorChooser *cc;\n\tvoid (*onChanged)(uiColorButton *, void *);\n\tvoid *onChangedData;\n};\n\nuiUnixControlAllDefaults(uiColorButton)\n\nstatic void onColorSet(GtkColorButton *button, gpointer data)\n{\n\tuiColorButton *b = uiColorButton(data);\n\n\t(*(b->onChanged))(b, b->onChangedData);\n}\n\nstatic void defaultOnChanged(uiColorButton *b, void *data)\n{\n\t// do nothing\n}\n\nvoid uiColorButtonColor(uiColorButton *b, double *r, double *g, double *bl, double *a)\n{\n\tGdkRGBA rgba;\n\n\tgtk_color_chooser_get_rgba(b->cc, &rgba);\n\t*r = rgba.red;\n\t*g = rgba.green;\n\t*bl = rgba.blue;\n\t*a = rgba.alpha;\n}\n\nvoid uiColorButtonSetColor(uiColorButton *b, double r, double g, double bl, double a)\n{\n\tGdkRGBA rgba;\n\n\trgba.red = r;\n\trgba.green = g;\n\trgba.blue = bl;\n\trgba.alpha = a;\n\t// no need to inhibit the signal; color-set is documented as only being sent when the user changes the color\n\tgtk_color_chooser_set_rgba(b->cc, &rgba);\n}\n\nvoid uiColorButtonOnChanged(uiColorButton *b, void (*f)(uiColorButton *, void *), void *data)\n{\n\tb->onChanged = f;\n\tb->onChangedData = data;\n}\n\nuiColorButton *uiNewColorButton(void)\n{\n\tuiColorButton *b;\n\tGdkRGBA black;\n\n\tuiUnixNewControl(uiColorButton, b);\n\n\t// I'm not sure what the initial color is; set up a real one\n\tblack.red = 0.0;\n\tblack.green = 0.0;\n\tblack.blue = 0.0;\n\tblack.alpha = 1.0;\n\tb->widget = gtk_color_button_new_with_rgba(&black);\n\tb->button = GTK_BUTTON(b->widget);\n\tb->cb = GTK_COLOR_BUTTON(b->widget);\n\tb->cc = GTK_COLOR_CHOOSER(b->widget);\n\n\tgtk_color_chooser_set_use_alpha(b->cc, TRUE);\n\n\tg_signal_connect(b->widget, \"color-set\", G_CALLBACK(onColorSet), b);\n\tuiColorButtonOnChanged(b, defaultOnChanged, NULL);\n\n\treturn b;\n}\n"
  },
  {
    "path": "unix/combobox.c",
    "content": "// 11 june 2015\n#include \"uipriv_unix.h\"\n\nstruct uiCombobox {\n\tuiUnixControl c;\n\tGtkWidget *widget;\n\tGtkComboBox *combobox;\n\tGtkComboBoxText *comboboxText;\n\tvoid (*onSelected)(uiCombobox *, void *);\n\tvoid *onSelectedData;\n\tgulong onSelectedSignal;\n};\n\nuiUnixControlAllDefaults(uiCombobox)\n\nstatic void onChanged(GtkComboBox *cbox, gpointer data)\n{\n\tuiCombobox *c = uiCombobox(data);\n\n\t(*(c->onSelected))(c, c->onSelectedData);\n}\n\nstatic void defaultOnSelected(uiCombobox *c, void *data)\n{\n\t// do nothing\n}\n\nvoid uiComboboxAppend(uiCombobox *c, const char *text)\n{\n\tgtk_combo_box_text_append(c->comboboxText, NULL, text);\n}\n\nint uiComboboxSelected(uiCombobox *c)\n{\n\treturn gtk_combo_box_get_active(c->combobox);\n}\n\nvoid uiComboboxSetSelected(uiCombobox *c, int n)\n{\n\t// we need to inhibit sending of ::changed because this WILL send a ::changed otherwise\n\tg_signal_handler_block(c->combobox, c->onSelectedSignal);\n\tgtk_combo_box_set_active(c->combobox, n);\n\tg_signal_handler_unblock(c->combobox, c->onSelectedSignal);\n}\n\nvoid uiComboboxOnSelected(uiCombobox *c, void (*f)(uiCombobox *c, void *data), void *data)\n{\n\tc->onSelected = f;\n\tc->onSelectedData = data;\n}\n\nuiCombobox *uiNewCombobox(void)\n{\n\tuiCombobox *c;\n\n\tuiUnixNewControl(uiCombobox, c);\n\n\tc->widget = gtk_combo_box_text_new();\n\tc->combobox = GTK_COMBO_BOX(c->widget);\n\tc->comboboxText = GTK_COMBO_BOX_TEXT(c->widget);\n\n\tc->onSelectedSignal = g_signal_connect(c->widget, \"changed\", G_CALLBACK(onChanged), c);\n\tuiComboboxOnSelected(c, defaultOnSelected, NULL);\n\n\treturn c;\n}\n"
  },
  {
    "path": "unix/control.c",
    "content": "// 16 august 2015\n#include \"uipriv_unix.h\"\n\nvoid uiUnixControlSetContainer(uiUnixControl *c, GtkContainer *container, gboolean remove)\n{\n\t(*(c->SetContainer))(c, container, remove);\n}\n\n#define uiUnixControlSignature 0x556E6978\n\nuiUnixControl *uiUnixAllocControl(size_t n, uint32_t typesig, const char *typenamestr)\n{\n\treturn uiUnixControl(uiAllocControl(n, uiUnixControlSignature, typesig, typenamestr));\n}\n"
  },
  {
    "path": "unix/datetimepicker.c",
    "content": "// 4 september 2015\n#include \"uipriv_unix.h\"\n\n// LONGTERM imitate gnome-calendar's day/month/year entries above the calendar\n// LONGTERM allow entering a 24-hour hour in the hour spinbutton and adjust accordingly\n\n#define uiprivDateTimePickerWidgetType (uiprivDateTimePickerWidget_get_type())\n#define uiprivDateTimePickerWidget(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), uiprivDateTimePickerWidgetType, uiprivDateTimePickerWidget))\n#define isDateTimePickerWidget(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), uiprivDateTimePickerWidgetType))\n#define uiprivDateTimePickerWidgetClass(class) (G_TYPE_CHECK_CLASS_CAST((class), uiprivDateTimePickerWidgetType, uiprivDateTimePickerWidgetClass))\n#define isDateTimePickerWidgetClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), uiprivDateTimePickerWidget))\n#define getDateTimePickerWidgetClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), uiprivDateTimePickerWidgetType, uiprivDateTimePickerWidgetClass))\n\ntypedef struct uiprivDateTimePickerWidget uiprivDateTimePickerWidget;\ntypedef struct uiprivDateTimePickerWidgetClass uiprivDateTimePickerWidgetClass;\n\nstruct uiprivDateTimePickerWidget {\n\tGtkToggleButton parent_instance;\n\n\tgulong toggledSignal;\n\n\tgboolean hasTime;\n\tgboolean hasDate;\n\n\tGtkWidget *window;\n\tGtkWidget *box;\n\tGtkWidget *calendar;\n\tGtkWidget *timebox;\n\tGtkWidget *hours;\n\tGtkWidget *minutes;\n\tGtkWidget *seconds;\n\tGtkWidget *ampm;\n\n\tgulong calendarBlock;\n\tgulong hoursBlock;\n\tgulong minutesBlock;\n\tgulong secondsBlock;\n\tgulong ampmBlock;\n\n\tGdkDevice *keyboard;\n\tGdkDevice *mouse;\n};\n\nstruct uiprivDateTimePickerWidgetClass {\n\tGtkToggleButtonClass parent_class;\n};\n\nG_DEFINE_TYPE(uiprivDateTimePickerWidget, uiprivDateTimePickerWidget, GTK_TYPE_TOGGLE_BUTTON)\n\nstatic int realSpinValue(GtkSpinButton *spinButton)\n{\n\tGtkAdjustment *adj;\n\n\tadj = gtk_spin_button_get_adjustment(spinButton);\n\treturn (int) gtk_adjustment_get_value(adj);\n}\n\nstatic void setRealSpinValue(GtkSpinButton *spinButton, int value, gulong block)\n{\n\tGtkAdjustment *adj;\n\n\tg_signal_handler_block(spinButton, block);\n\tadj = gtk_spin_button_get_adjustment(spinButton);\n\tgtk_adjustment_set_value(adj, value);\n\tg_signal_handler_unblock(spinButton, block);\n}\n\nstatic GDateTime *selected(uiprivDateTimePickerWidget *d)\n{\n\t// choose a day for which all times are likely to be valid for the default date in case we're only dealing with time\n\tguint year = 1970, month = 1, day = 1;\n\tguint hour = 0, minute = 0, second = 0;\n\n\tif (d->hasDate) {\n\t\tgtk_calendar_get_date(GTK_CALENDAR(d->calendar), &year, &month, &day);\n\t\tmonth++;\t\t// GtkCalendar/GDateTime differences\n\t}\n\tif (d->hasTime) {\n\t\thour = realSpinValue(GTK_SPIN_BUTTON(d->hours));\n\t\tif (realSpinValue(GTK_SPIN_BUTTON(d->ampm)) != 0)\n\t\t\thour += 12;\n\t\tminute = realSpinValue(GTK_SPIN_BUTTON(d->minutes));\n\t\tsecond = realSpinValue(GTK_SPIN_BUTTON(d->seconds));\n\t}\n\treturn g_date_time_new_local(year, month, day, hour, minute, second);\n}\n\nstatic void setLabel(uiprivDateTimePickerWidget *d)\n{\n\tGDateTime *dt;\n\tchar *fmt;\n\tchar *msg;\n\tgboolean free;\n\n\tdt = selected(d);\n\tfree = FALSE;\n\tif (d->hasDate && d->hasTime) {\n\t\t// don't use D_T_FMT; that's too verbose\n\t\tfmt = g_strdup_printf(\"%s %s\", nl_langinfo(D_FMT), nl_langinfo(T_FMT));\n\t\tfree = TRUE;\n\t} else if (d->hasDate)\n\t\tfmt = nl_langinfo(D_FMT);\n\telse\n\t\tfmt = nl_langinfo(T_FMT);\n\tmsg = g_date_time_format(dt, fmt);\n\tgtk_button_set_label(GTK_BUTTON(d), msg);\n\tg_free(msg);\n\tif (free)\n\t\tg_free(fmt);\n\tg_date_time_unref(dt);\n}\n\nstatic int changedSignal;\n\nstatic void dateTimeChanged(uiprivDateTimePickerWidget *d)\n{\n\tg_signal_emit(d, changedSignal, 0);\n\tsetLabel(d);\n\t// TODO fire event here instead?\n}\n\n// we don't want ::toggled to be sent again\nstatic void setActive(uiprivDateTimePickerWidget *d, gboolean active)\n{\n\tg_signal_handler_block(d, d->toggledSignal);\n\tgtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d), active);\n\tg_signal_handler_unblock(d, d->toggledSignal);\n}\n\n// like startGrab() below, a lot of this is in the order that GtkComboBox does it\nstatic void endGrab(uiprivDateTimePickerWidget *d)\n{\n\tif (d->keyboard != NULL)\n\t\tgdk_device_ungrab(d->keyboard, GDK_CURRENT_TIME);\n\tgdk_device_ungrab(d->mouse, GDK_CURRENT_TIME);\n\tgtk_device_grab_remove(d->window, d->mouse);\n\td->keyboard = NULL;\n\td->mouse = NULL;\n}\n\nstatic void hidePopup(uiprivDateTimePickerWidget *d)\n{\n\tendGrab(d);\n\tgtk_widget_hide(d->window);\n\tsetActive(d, FALSE);\n}\n\n// this consolidates a good chunk of what GtkComboBox does\nstatic gboolean startGrab(uiprivDateTimePickerWidget *d)\n{\n\tGdkDevice *dev;\n\tguint32 time;\n\tGdkWindow *window;\n\tGdkDevice *keyboard, *mouse;\n\n\tdev = gtk_get_current_event_device();\n\tif (dev == NULL) {\n\t\t// this is what GtkComboBox does\n\t\t// since no device was set, just use the first available \"master device\"\n\t\tGdkDisplay *disp;\n\t\tGdkDeviceManager *dm;\n\t\tGList *list;\n\n\t\tdisp = gtk_widget_get_display(GTK_WIDGET(d));\n\t\tdm = gdk_display_get_device_manager(disp);\n\t\tlist = gdk_device_manager_list_devices(dm, GDK_DEVICE_TYPE_MASTER);\n\t\tdev = (GdkDevice *) (list->data);\n\t\tg_list_free(list);\n\t}\n\n\ttime = gtk_get_current_event_time();\n\tkeyboard = dev;\n\tmouse = gdk_device_get_associated_device(dev);\n\tif (gdk_device_get_source(dev) != GDK_SOURCE_KEYBOARD) {\n\t\tdev = mouse;\n\t\tmouse = keyboard;\n\t\tkeyboard = dev;\n\t}\n\n\twindow = gtk_widget_get_window(d->window);\n\tif (keyboard != NULL)\n\t\tif (gdk_device_grab(keyboard, window,\n\t\t\tGDK_OWNERSHIP_WINDOW, TRUE,\n\t\t\tGDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK,\n\t\t\tNULL, time) != GDK_GRAB_SUCCESS)\n\t\t\treturn FALSE;\n\tif (mouse != NULL)\n\t\tif (gdk_device_grab(mouse, window,\n\t\t\tGDK_OWNERSHIP_WINDOW, TRUE,\n\t\t\tGDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK,\n\t\t\tNULL, time) != GDK_GRAB_SUCCESS) {\n\t\t\tif (keyboard != NULL)\n\t\t\t\tgdk_device_ungrab(keyboard, time);\n\t\t\treturn FALSE;\n\t\t}\n\n\tgtk_device_grab_add(d->window, mouse, TRUE);\n\td->keyboard = keyboard;\n\td->mouse = mouse;\n\treturn TRUE;\n}\n\n// based on gtk_combo_box_list_position() in the GTK+ source code\nstatic void allocationToScreen(uiprivDateTimePickerWidget *d, gint *x, gint *y)\n{\n\tGdkWindow *window;\n\tGtkAllocation a;\n\tGtkRequisition aWin;\n\tGdkScreen *screen;\n\tGdkRectangle workarea;\n\tint otherY;\n\n\tgtk_widget_get_allocation(GTK_WIDGET(d), &a);\n\tgtk_widget_get_preferred_size(d->window, &aWin, NULL);\n\t*x = 0;\n\t*y = 0;\n\tif (!gtk_widget_get_has_window(GTK_WIDGET(d))) {\n\t\t*x = a.x;\n\t\t*y = a.y;\n\t}\n\twindow = gtk_widget_get_window(GTK_WIDGET(d));\n\tgdk_window_get_root_coords(window, *x, *y, x, y);\n\tif (gtk_widget_get_direction(GTK_WIDGET(d)) == GTK_TEXT_DIR_RTL)\n\t\t*x += a.width - aWin.width;\n\n\t// now adjust to prevent the box from going offscreen\n\tscreen = gtk_widget_get_screen(GTK_WIDGET(d));\n\tgdk_screen_get_monitor_workarea(screen,\n\t\tgdk_screen_get_monitor_at_window(screen, window),\n\t\t&workarea);\n\tif (*x < workarea.x)\t\t\t\t\t// too far to the left?\n\t\t*x = workarea.x;\n\telse if (*x + aWin.width > (workarea.x + workarea.width))\t// too far to the right?\n\t\t*x = (workarea.x + workarea.width) - aWin.width;\n\t// this isn't the same algorithm used by GtkComboBox\n\t// first, get our two choices; *y for down and otherY for up\n\totherY = *y - aWin.height;\n\t*y += a.height;\n\t// and use otherY if we're too low\n\tif (*y + aWin.height >= workarea.y + workarea.height)\n\t\t*y = otherY;\n}\n\nstatic void showPopup(uiprivDateTimePickerWidget *d)\n{\n\tGtkWidget *toplevel;\n\tgint x, y;\n\n\t// GtkComboBox does it\n\ttoplevel = gtk_widget_get_toplevel(GTK_WIDGET(d));\n\tif (GTK_IS_WINDOW(toplevel))\n\t\tgtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(toplevel)), GTK_WINDOW(d->window));\n\n\tallocationToScreen(d, &x, &y);\n\tgtk_window_move(GTK_WINDOW(d->window), x, y);\n\n\tgtk_widget_show(d->window);\n\tsetActive(d, TRUE);\n\n\tif (!startGrab(d))\n\t\thidePopup(d);\n}\n\nstatic void onToggled(GtkToggleButton *b, gpointer data)\n{\n\tuiprivDateTimePickerWidget *d = uiprivDateTimePickerWidget(b);\n\n\tif (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d)))\n\t\tshowPopup(d);\n\telse\n\t\thidePopup(d);\n}\n\nstatic gboolean grabBroken(GtkWidget *w, GdkEventGrabBroken *e, gpointer data)\n{\n\tuiprivDateTimePickerWidget *d = uiprivDateTimePickerWidget(data);\n\n\thidePopup(d);\n\treturn TRUE;\t\t// this is what GtkComboBox does\n}\n\nstatic gboolean buttonReleased(GtkWidget *w, GdkEventButton *e, gpointer data)\n{\n\tuiprivDateTimePickerWidget *d = uiprivDateTimePickerWidget(data);\n\tint winx, winy;\n\tGtkAllocation wina;\n\tgboolean in;\n\n\tgtk_widget_get_allocation(d->window, &wina);\n\twinx = 0;\n\twiny = 0;\n\tif (!gtk_widget_get_has_window(d->window)) {\n\t\twinx = wina.x;\n\t\twiny = wina.y;\n\t}\n\tgdk_window_get_root_coords(gtk_widget_get_window(d->window), winx, winy, &winx, &winy);\n\tin = TRUE;\n\tif (e->x_root < winx)\n\t\tin = FALSE;\n\tif (e->x_root >= (winx + wina.width))\n\t\tin = FALSE;\n\tif (e->y_root < winy)\n\t\tin = FALSE;\n\tif (e->y_root >= (winy + wina.height))\n\t\tin = FALSE;\n\tif (!in)\n\t\thidePopup(d);\n\treturn TRUE;\t\t// this is what GtkComboBox does\n}\n\nstatic gint hoursSpinboxInput(GtkSpinButton *sb, gpointer ptr, gpointer data)\n{\n\tdouble *out = (double *) ptr;\n\tconst gchar *text;\n\tint value;\n\n\ttext = gtk_entry_get_text(GTK_ENTRY(sb));\n\tvalue = (int) g_strtod(text, NULL);\n\tif (value < 0 || value > 12)\n\t\treturn GTK_INPUT_ERROR;\n\tif (value == 12)\t\t// 12 to the user is 0 internally\n\t\tvalue = 0;\n\t*out = (double) value;\n\treturn TRUE;\n}\n\nstatic gboolean hoursSpinboxOutput(GtkSpinButton *sb, gpointer data)\n{\n\tgchar *text;\n\tint value;\n\n\tvalue = realSpinValue(sb);\n\tif (value == 0)\t\t// 0 internally is 12 to the user\n\t\tvalue = 12;\n\ttext = g_strdup_printf(\"%d\", value);\n\tgtk_entry_set_text(GTK_ENTRY(sb), text);\n\tg_free(text);\n\treturn TRUE;\n}\n\nstatic gboolean zeroPadSpinbox(GtkSpinButton *sb, gpointer data)\n{\n\tgchar *text;\n\tint value;\n\n\tvalue = realSpinValue(sb);\n\ttext = g_strdup_printf(\"%02d\", value);\n\tgtk_entry_set_text(GTK_ENTRY(sb), text);\n\tg_free(text);\n\treturn TRUE;\n}\n\n// this is really hacky but we can't use GtkCombobox here :(\nstatic gint ampmSpinboxInput(GtkSpinButton *sb, gpointer ptr, gpointer data)\n{\n\tdouble *out = (double *) ptr;\n\tconst gchar *text;\n\tchar firstAM, firstPM;\n\n\ttext = gtk_entry_get_text(GTK_ENTRY(sb));\n\t// LONGTERM don't use ASCII here for case insensitivity\n\tfirstAM = g_ascii_tolower(nl_langinfo(AM_STR)[0]);\n\tfirstPM = g_ascii_tolower(nl_langinfo(PM_STR)[0]);\n\tfor (; *text != '\\0'; text++)\n\t\tif (g_ascii_tolower(*text) == firstAM) {\n\t\t\t*out = 0;\n\t\t\treturn TRUE;\n\t\t} else if (g_ascii_tolower(*text) == firstPM) {\n\t\t\t*out = 1;\n\t\t\treturn TRUE;\n\t\t}\n\treturn GTK_INPUT_ERROR;\n}\n\nstatic gboolean ampmSpinboxOutput(GtkSpinButton *sb, gpointer data)\n{\n\tint value;\n\n\tvalue = gtk_spin_button_get_value_as_int(sb);\n\tif (value == 0)\n\t\tgtk_entry_set_text(GTK_ENTRY(sb), nl_langinfo(AM_STR));\n\telse\n\t\tgtk_entry_set_text(GTK_ENTRY(sb), nl_langinfo(PM_STR));\n\treturn TRUE;\n}\n\nstatic void spinboxChanged(GtkSpinButton *sb, gpointer data)\n{\n\tuiprivDateTimePickerWidget *d = uiprivDateTimePickerWidget(data);\n\n\tdateTimeChanged(d);\n}\n\nstatic GtkWidget *newSpinbox(uiprivDateTimePickerWidget *d, int min, int max, gint (*input)(GtkSpinButton *, gpointer, gpointer), gboolean (*output)(GtkSpinButton *, gpointer), gulong *block)\n{\n\tGtkWidget *sb;\n\n\tsb = gtk_spin_button_new_with_range(min, max, 1);\n\tgtk_spin_button_set_digits(GTK_SPIN_BUTTON(sb), 0);\n\tgtk_spin_button_set_wrap(GTK_SPIN_BUTTON(sb), TRUE);\n\tgtk_orientable_set_orientation(GTK_ORIENTABLE(sb), GTK_ORIENTATION_VERTICAL);\n\t*block = g_signal_connect(sb, \"value-changed\", G_CALLBACK(spinboxChanged), d);\n\tif (input != NULL)\n\t\tg_signal_connect(sb, \"input\", G_CALLBACK(input), NULL);\n\tif (output != NULL)\n\t\tg_signal_connect(sb, \"output\", G_CALLBACK(output), NULL);\n\treturn sb;\n}\n\nstatic void dateChanged(GtkCalendar *c, gpointer data)\n{\n\tuiprivDateTimePickerWidget *d = uiprivDateTimePickerWidget(data);\n\n\tdateTimeChanged(d);\n}\n\nstatic void setDateOnly(uiprivDateTimePickerWidget *d)\n{\n\td->hasTime = FALSE;\n\tgtk_container_remove(GTK_CONTAINER(d->box), d->timebox);\n}\n\nstatic void setTimeOnly(uiprivDateTimePickerWidget *d)\n{\n\td->hasDate = FALSE;\n\tgtk_container_remove(GTK_CONTAINER(d->box), d->calendar);\n}\n\nstatic void uiprivDateTimePickerWidget_setTime(uiprivDateTimePickerWidget *d, GDateTime *dt)\n{\n\tgint year, month, day;\n\tgint hour;\n\n\t// notice how we block signals from firing\n\tif (d->hasDate) {\n\t\tg_date_time_get_ymd(dt, &year, &month, &day);\n\t\tmonth--;\t\t\t// GDateTime/GtkCalendar differences\n\t\tg_signal_handler_block(d->calendar, d->calendarBlock);\n\t\tgtk_calendar_select_month(GTK_CALENDAR(d->calendar), month, year);\n\t\tgtk_calendar_select_day(GTK_CALENDAR(d->calendar), day);\n\t\tg_signal_handler_unblock(d->calendar, d->calendarBlock);\n\t}\n\tif (d->hasTime) {\n\t\thour = g_date_time_get_hour(dt);\n\t\tif (hour >= 12) {\n\t\t\thour -= 12;\n\t\t\tsetRealSpinValue(GTK_SPIN_BUTTON(d->ampm), 1, d->ampmBlock);\n\t\t}\n\t\tsetRealSpinValue(GTK_SPIN_BUTTON(d->hours), hour, d->hoursBlock);\n\t\tsetRealSpinValue(GTK_SPIN_BUTTON(d->minutes), g_date_time_get_minute(dt), d->minutesBlock);\n\t\tsetRealSpinValue(GTK_SPIN_BUTTON(d->seconds), g_date_time_get_seconds(dt), d->secondsBlock);\n\t}\n\tg_date_time_unref(dt);\n}\n\nstatic void uiprivDateTimePickerWidget_init(uiprivDateTimePickerWidget *d)\n{\n\td->window = gtk_window_new(GTK_WINDOW_POPUP);\n\tgtk_window_set_resizable(GTK_WINDOW(d->window), FALSE);\n\tgtk_window_set_attached_to(GTK_WINDOW(d->window), GTK_WIDGET(d));\n\tgtk_window_set_decorated(GTK_WINDOW(d->window), FALSE);\n\tgtk_window_set_deletable(GTK_WINDOW(d->window), FALSE);\n\tgtk_window_set_type_hint(GTK_WINDOW(d->window), GDK_WINDOW_TYPE_HINT_COMBO);\n\tgtk_window_set_skip_taskbar_hint(GTK_WINDOW(d->window), TRUE);\n\tgtk_window_set_skip_pager_hint(GTK_WINDOW(d->window), TRUE);\n\tgtk_window_set_has_resize_grip(GTK_WINDOW(d->window), FALSE);\n\tgtk_container_set_border_width(GTK_CONTAINER(d->window), 12);\n\t// and make it stand out a bit\n\tgtk_style_context_add_class(gtk_widget_get_style_context(d->window), \"frame\");\n\n\td->box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);\n\tgtk_container_add(GTK_CONTAINER(d->window), d->box);\n\n\td->calendar = gtk_calendar_new();\n\td->calendarBlock = g_signal_connect(d->calendar, \"day-selected\", G_CALLBACK(dateChanged), d);\n\tgtk_container_add(GTK_CONTAINER(d->box), d->calendar);\n\n\td->timebox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);\n\tgtk_widget_set_valign(d->timebox, GTK_ALIGN_CENTER);\n\tgtk_container_add(GTK_CONTAINER(d->box), d->timebox);\n\n\td->hours = newSpinbox(d, 0, 11, hoursSpinboxInput, hoursSpinboxOutput, &(d->hoursBlock));\n\tgtk_container_add(GTK_CONTAINER(d->timebox), d->hours);\n\n\tgtk_container_add(GTK_CONTAINER(d->timebox),\n\t\tgtk_label_new(\":\"));\n\n\td->minutes = newSpinbox(d, 0, 59, NULL, zeroPadSpinbox, &(d->minutesBlock));\n\tgtk_container_add(GTK_CONTAINER(d->timebox), d->minutes);\n\n\tgtk_container_add(GTK_CONTAINER(d->timebox),\n\t\tgtk_label_new(\":\"));\n\n\td->seconds = newSpinbox(d, 0, 59, NULL, zeroPadSpinbox, &(d->secondsBlock));\n\tgtk_container_add(GTK_CONTAINER(d->timebox), d->seconds);\n\n\t// LONGTERM this should be the case, but that interferes with grabs\n\t// switch to it when we can drop GTK+ 3.10 and use popovers\n#if 0\n\td->ampm = gtk_combo_box_text_new();\n\tgtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(d->ampm), NULL, \"AM\");\n\tgtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(d->ampm), NULL, \"PM\");\n#endif\n\td->ampm = newSpinbox(d, 0, 1, ampmSpinboxInput, ampmSpinboxOutput, &(d->ampmBlock));\n\tgtk_spin_button_set_numeric(GTK_SPIN_BUTTON(d->ampm), FALSE);\n\tgtk_widget_set_valign(d->ampm, GTK_ALIGN_CENTER);\n\tgtk_container_add(GTK_CONTAINER(d->timebox), d->ampm);\n\n\tgtk_widget_show_all(d->box);\n\n\tg_signal_connect(d->window, \"grab-broken-event\", G_CALLBACK(grabBroken), d);\n\tg_signal_connect(d->window, \"button-release-event\", G_CALLBACK(buttonReleased), d);\n\n\td->toggledSignal = g_signal_connect(d, \"toggled\", G_CALLBACK(onToggled), NULL);\n\td->keyboard = NULL;\n\td->mouse = NULL;\n\n\td->hasTime = TRUE;\n\td->hasDate = TRUE;\n\n\t// set the current date/time\n\tuiprivDateTimePickerWidget_setTime(d, g_date_time_new_now_local());\n}\n\nstatic void uiprivDateTimePickerWidget_dispose(GObject *obj)\n{\n\tuiprivDateTimePickerWidget *d = uiprivDateTimePickerWidget(obj);\n\n\tif (d->window != NULL) {\n\t\tgtk_widget_destroy(d->window);\n\t\td->window = NULL;\n\t}\n\tG_OBJECT_CLASS(uiprivDateTimePickerWidget_parent_class)->dispose(obj);\n}\n\nstatic void uiprivDateTimePickerWidget_finalize(GObject *obj)\n{\n\tG_OBJECT_CLASS(uiprivDateTimePickerWidget_parent_class)->finalize(obj);\n}\n\nstatic void uiprivDateTimePickerWidget_class_init(uiprivDateTimePickerWidgetClass *class)\n{\n\tG_OBJECT_CLASS(class)->dispose = uiprivDateTimePickerWidget_dispose;\n\tG_OBJECT_CLASS(class)->finalize = uiprivDateTimePickerWidget_finalize;\n\n\tchangedSignal = g_signal_new(\"changed\",\n\t\tG_TYPE_FROM_CLASS(class),\n\t\tG_SIGNAL_RUN_LAST,\n\t\t0,\n\t\tNULL, NULL, NULL,\n\t\tG_TYPE_NONE,\n\t\t0);\n}\n\nstruct uiDateTimePicker {\n\tuiUnixControl c;\n\tGtkWidget *widget;\n\tuiprivDateTimePickerWidget *d;\n\tvoid (*onChanged)(uiDateTimePicker *, void *);\n\tvoid *onChangedData;\n\tgulong setBlock;\n};\n\nuiUnixControlAllDefaults(uiDateTimePicker)\n\nstatic void defaultOnChanged(uiDateTimePicker *d, void *data)\n{\n\t// do nothing\n}\n\nvoid uiDateTimePickerTime(uiDateTimePicker *d, struct tm *time)\n{\n\ttime_t t;\n\tstruct tm tmbuf;\n\tGDateTime *dt;\n\n\tdt = selected(d->d);\n\tt = g_date_time_to_unix(dt);\n\tg_date_time_unref(dt);\n\n\t// Copy time to minimize a race condition\n\t// time.h functions use global non-thread-safe data\n\ttmbuf = *localtime(&t);\n\tmemcpy(time, &tmbuf, sizeof (struct tm));\n}\n\nvoid uiDateTimePickerSetTime(uiDateTimePicker *d, const struct tm *time)\n{\n\ttime_t t;\n\tstruct tm tmbuf;\n\n\t// TODO find a better way to avoid this; possibly by removing the signal entirely, or the call to dateTimeChanged() (most likely both)\n\tg_signal_handler_block(d->d, d->setBlock);\n\n\t// Copy time because mktime() modifies its argument\n\tmemcpy(&tmbuf, time, sizeof (struct tm));\n\tt = mktime(&tmbuf);\n\n\tuiprivDateTimePickerWidget_setTime(d->d, g_date_time_new_from_unix_local(t));\n\tdateTimeChanged(d->d);\n\n\tg_signal_handler_unblock(d->d, d->setBlock);\n}\n\nvoid uiDateTimePickerOnChanged(uiDateTimePicker *d, void (*f)(uiDateTimePicker *, void *), void *data)\n{\n\td->onChanged = f;\n\td->onChangedData = data;\n}\n\nstatic void onChanged(uiprivDateTimePickerWidget *d, gpointer data)\n{\n\tuiDateTimePicker *c;\n\n\tc = uiDateTimePicker(data);\n\t(*(c->onChanged))(c, c->onChangedData);\n}\n\nstatic GtkWidget *newDTP(void)\n{\n\tGtkWidget *w;\n\n\tw = GTK_WIDGET(g_object_new(uiprivDateTimePickerWidgetType, \"label\", \"\", NULL));\n\tsetLabel(uiprivDateTimePickerWidget(w));\n\treturn w;\n}\n\nstatic GtkWidget *newDP(void)\n{\n\tGtkWidget *w;\n\n\tw = GTK_WIDGET(g_object_new(uiprivDateTimePickerWidgetType, \"label\", \"\", NULL));\n\tsetDateOnly(uiprivDateTimePickerWidget(w));\n\tsetLabel(uiprivDateTimePickerWidget(w));\n\treturn w;\n}\n\nstatic GtkWidget *newTP(void)\n{\n\tGtkWidget *w;\n\n\tw = GTK_WIDGET(g_object_new(uiprivDateTimePickerWidgetType, \"label\", \"\", NULL));\n\tsetTimeOnly(uiprivDateTimePickerWidget(w));\n\tsetLabel(uiprivDateTimePickerWidget(w));\n\treturn w;\n}\n\nuiDateTimePicker *finishNewDateTimePicker(GtkWidget *(*fn)(void))\n{\n\tuiDateTimePicker *d;\n\n\tuiUnixNewControl(uiDateTimePicker, d);\n\n\td->widget = (*fn)();\n\td->d = uiprivDateTimePickerWidget(d->widget);\n\td->setBlock = g_signal_connect(d->widget, \"changed\", G_CALLBACK(onChanged), d);\n\tuiDateTimePickerOnChanged(d, defaultOnChanged, NULL);\n\n\treturn d;\n}\n\nuiDateTimePicker *uiNewDateTimePicker(void)\n{\n\treturn finishNewDateTimePicker(newDTP);\n}\n\nuiDateTimePicker *uiNewDatePicker(void)\n{\n\treturn finishNewDateTimePicker(newDP);\n}\n\nuiDateTimePicker *uiNewTimePicker(void)\n{\n\treturn finishNewDateTimePicker(newTP);\n}\n"
  },
  {
    "path": "unix/debug.c",
    "content": "// 13 may 2016\n#include \"uipriv_unix.h\"\n\n// LONGTERM don't halt on release builds\n\nvoid uiprivRealBug(const char *file, const char *line, const char *func, const char *prefix, const char *format, va_list ap)\n{\n\tchar *a, *b;\n\n\ta = g_strdup_printf(\"[libui] %s:%s:%s() %s\", file, line, func, prefix);\n\tb = g_strdup_vprintf(format, ap);\n\tg_critical(\"%s%s\", a, b);\n\tG_BREAKPOINT();\n}\n"
  },
  {
    "path": "unix/draw.c",
    "content": "// 6 september 2015\n#include \"uipriv_unix.h\"\n#include \"draw.h\"\n\nuiDrawContext *uiprivNewContext(cairo_t *cr, GtkStyleContext *style)\n{\n\tuiDrawContext *c;\n\n\tc = uiprivNew(uiDrawContext);\n\tc->cr = cr;\n\tc->style = style;\n\treturn c;\n}\n\nvoid uiprivFreeContext(uiDrawContext *c)\n{\n\t// free neither cr nor style; we own neither\n\tuiprivFree(c);\n}\n\nstatic cairo_pattern_t *mkbrush(uiDrawBrush *b)\n{\n\tcairo_pattern_t *pat;\n\tsize_t i;\n\n\tswitch (b->Type) {\n\tcase uiDrawBrushTypeSolid:\n\t\tpat = cairo_pattern_create_rgba(b->R, b->G, b->B, b->A);\n\t\tbreak;\n\tcase uiDrawBrushTypeLinearGradient:\n\t\tpat = cairo_pattern_create_linear(b->X0, b->Y0, b->X1, b->Y1);\n\t\tbreak;\n\tcase uiDrawBrushTypeRadialGradient:\n\t\t// make the start circle radius 0 to make it a point\n\t\tpat = cairo_pattern_create_radial(\n\t\t\tb->X0, b->Y0, 0,\n\t\t\tb->X1, b->Y1, b->OuterRadius);\n\t\tbreak;\n//\tcase uiDrawBrushTypeImage:\n\t}\n\tif (cairo_pattern_status(pat) != CAIRO_STATUS_SUCCESS)\n\t\tuiprivImplBug(\"error creating pattern in mkbrush(): %s\",\n\t\t\tcairo_status_to_string(cairo_pattern_status(pat)));\n\tswitch (b->Type) {\n\tcase uiDrawBrushTypeLinearGradient:\n\tcase uiDrawBrushTypeRadialGradient:\n\t\tfor (i = 0; i < b->NumStops; i++)\n\t\t\tcairo_pattern_add_color_stop_rgba(pat,\n\t\t\t\tb->Stops[i].Pos,\n\t\t\t\tb->Stops[i].R,\n\t\t\t\tb->Stops[i].G,\n\t\t\t\tb->Stops[i].B,\n\t\t\t\tb->Stops[i].A);\n\t}\n\treturn pat;\n}\n\nvoid uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStrokeParams *p)\n{\n\tcairo_pattern_t *pat;\n\n\tuiprivRunPath(path, c->cr);\n\tpat = mkbrush(b);\n\tcairo_set_source(c->cr, pat);\n\tswitch (p->Cap) {\n\tcase uiDrawLineCapFlat:\n\t\tcairo_set_line_cap(c->cr, CAIRO_LINE_CAP_BUTT);\n\t\tbreak;\n\tcase uiDrawLineCapRound:\n\t\tcairo_set_line_cap(c->cr, CAIRO_LINE_CAP_ROUND);\n\t\tbreak;\n\tcase uiDrawLineCapSquare:\n\t\tcairo_set_line_cap(c->cr, CAIRO_LINE_CAP_SQUARE);\n\t\tbreak;\n\t}\n\tswitch (p->Join) {\n\tcase uiDrawLineJoinMiter:\n\t\tcairo_set_line_join(c->cr, CAIRO_LINE_JOIN_MITER);\n\t\tcairo_set_miter_limit(c->cr, p->MiterLimit);\n\t\tbreak;\n\tcase uiDrawLineJoinRound:\n\t\tcairo_set_line_join(c->cr, CAIRO_LINE_JOIN_ROUND);\n\t\tbreak;\n\tcase uiDrawLineJoinBevel:\n\t\tcairo_set_line_join(c->cr, CAIRO_LINE_JOIN_BEVEL);\n\t\tbreak;\n\t}\n\tcairo_set_line_width(c->cr, p->Thickness);\n\tcairo_set_dash(c->cr, p->Dashes, p->NumDashes, p->DashPhase);\n\tcairo_stroke(c->cr);\n\tcairo_pattern_destroy(pat);\n}\n\nvoid uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b)\n{\n\tcairo_pattern_t *pat;\n\n\tuiprivRunPath(path, c->cr);\n\tpat = mkbrush(b);\n\tcairo_set_source(c->cr, pat);\n\tswitch (uiprivPathFillMode(path)) {\n\tcase uiDrawFillModeWinding:\n\t\tcairo_set_fill_rule(c->cr, CAIRO_FILL_RULE_WINDING);\n\t\tbreak;\n\tcase uiDrawFillModeAlternate:\n\t\tcairo_set_fill_rule(c->cr, CAIRO_FILL_RULE_EVEN_ODD);\n\t\tbreak;\n\t}\n\tcairo_fill(c->cr);\n\tcairo_pattern_destroy(pat);\n}\n\nvoid uiDrawTransform(uiDrawContext *c, uiDrawMatrix *m)\n{\n\tcairo_matrix_t cm;\n\n\tuiprivM2C(m, &cm);\n\tcairo_transform(c->cr, &cm);\n}\n\nvoid uiDrawClip(uiDrawContext *c, uiDrawPath *path)\n{\n\tuiprivRunPath(path, c->cr);\n\tswitch (uiprivPathFillMode(path)) {\n\tcase uiDrawFillModeWinding:\n\t\tcairo_set_fill_rule(c->cr, CAIRO_FILL_RULE_WINDING);\n\t\tbreak;\n\tcase uiDrawFillModeAlternate:\n\t\tcairo_set_fill_rule(c->cr, CAIRO_FILL_RULE_EVEN_ODD);\n\t\tbreak;\n\t}\n\tcairo_clip(c->cr);\n}\n\nvoid uiDrawSave(uiDrawContext *c)\n{\n\tcairo_save(c->cr);\n}\n\nvoid uiDrawRestore(uiDrawContext *c)\n{\n\tcairo_restore(c->cr);\n}\n"
  },
  {
    "path": "unix/draw.h",
    "content": "// 5 may 2016\n\n// draw.c\nstruct uiDrawContext {\n\tcairo_t *cr;\n\tGtkStyleContext *style;\n};\n\n// drawpath.c\nextern void uiprivRunPath(uiDrawPath *p, cairo_t *cr);\nextern uiDrawFillMode uiprivPathFillMode(uiDrawPath *path);\n\n// drawmatrix.c\nextern void uiprivM2C(uiDrawMatrix *m, cairo_matrix_t *c);\n"
  },
  {
    "path": "unix/drawmatrix.c",
    "content": "// 6 september 2015\n#include \"uipriv_unix.h\"\n#include \"draw.h\"\n\nstatic void m2c(uiDrawMatrix *m, cairo_matrix_t *c)\n{\n\tc->xx = m->M11;\n\tc->yx = m->M12;\n\tc->xy = m->M21;\n\tc->yy = m->M22;\n\tc->x0 = m->M31;\n\tc->y0 = m->M32;\n}\n\n// needed by uiDrawTransform()\nvoid uiprivM2C(uiDrawMatrix *m, cairo_matrix_t *c)\n{\n\tm2c(m, c);\n}\n\nstatic void c2m(cairo_matrix_t *c, uiDrawMatrix *m)\n{\n\tm->M11 = c->xx;\n\tm->M12 = c->yx;\n\tm->M21 = c->xy;\n\tm->M22 = c->yy;\n\tm->M31 = c->x0;\n\tm->M32 = c->y0;\n}\n\nvoid uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y)\n{\n\tcairo_matrix_t c;\n\n\tm2c(m, &c);\n\tcairo_matrix_translate(&c, x, y);\n\tc2m(&c, m);\n}\n\nvoid uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x, double y)\n{\n\tcairo_matrix_t c;\n\tdouble xt, yt;\n\n\tm2c(m, &c);\n\txt = x;\n\tyt = y;\n\tuiprivScaleCenter(xCenter, yCenter, &xt, &yt);\n\tcairo_matrix_translate(&c, xt, yt);\n\tcairo_matrix_scale(&c, x, y);\n\tcairo_matrix_translate(&c, -xt, -yt);\n\tc2m(&c, m);\n}\n\nvoid uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount)\n{\n\tcairo_matrix_t c;\n\n\tm2c(m, &c);\n\tcairo_matrix_translate(&c, x, y);\n\tcairo_matrix_rotate(&c, amount);\n\tcairo_matrix_translate(&c, -x, -y);\n\tc2m(&c, m);\n}\n\nvoid uiDrawMatrixSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount)\n{\n\tuiprivFallbackSkew(m, x, y, xamount, yamount);\n}\n\nvoid uiDrawMatrixMultiply(uiDrawMatrix *dest, uiDrawMatrix *src)\n{\n\tcairo_matrix_t c;\n\tcairo_matrix_t d;\n\n\tm2c(dest, &c);\n\tm2c(src, &d);\n\tcairo_matrix_multiply(&c, &c, &d);\n\tc2m(&c, dest);\n}\n\nint uiDrawMatrixInvertible(uiDrawMatrix *m)\n{\n\tcairo_matrix_t c;\n\n\tm2c(m, &c);\n\treturn cairo_matrix_invert(&c) == CAIRO_STATUS_SUCCESS;\n}\n\nint uiDrawMatrixInvert(uiDrawMatrix *m)\n{\n\tcairo_matrix_t c;\n\n\tm2c(m, &c);\n\tif (cairo_matrix_invert(&c) != CAIRO_STATUS_SUCCESS)\n\t\treturn 0;\n\tc2m(&c, m);\n\treturn 1;\n}\n\nvoid uiDrawMatrixTransformPoint(uiDrawMatrix *m, double *x, double *y)\n{\n\tcairo_matrix_t c;\n\n\tm2c(m, &c);\n\tcairo_matrix_transform_point(&c, x, y);\n}\n\nvoid uiDrawMatrixTransformSize(uiDrawMatrix *m, double *x, double *y)\n{\n\tcairo_matrix_t c;\n\n\tm2c(m, &c);\n\tcairo_matrix_transform_distance(&c, x, y);\n}\n"
  },
  {
    "path": "unix/drawpath.c",
    "content": "// 6 september 2015\n#include \"uipriv_unix.h\"\n#include \"draw.h\"\n\nstruct uiDrawPath {\n\tGArray *pieces;\n\tuiDrawFillMode fillMode;\n\tgboolean ended;\n};\n\nstruct piece {\n\tint type;\n\tdouble d[8];\n\tint b;\n};\n\nenum {\n\tnewFigure,\n\tnewFigureArc,\n\tlineTo,\n\tarcTo,\n\tbezierTo,\n\tcloseFigure,\n\taddRect,\n};\n\nuiDrawPath *uiDrawNewPath(uiDrawFillMode mode)\n{\n\tuiDrawPath *p;\n\n\tp = uiprivNew(uiDrawPath);\n\tp->pieces = g_array_new(FALSE, TRUE, sizeof (struct piece));\n\tp->fillMode = mode;\n\treturn p;\n}\n\nvoid uiDrawFreePath(uiDrawPath *p)\n{\n\tg_array_free(p->pieces, TRUE);\n\tuiprivFree(p);\n}\n\nstatic void add(uiDrawPath *p, struct piece *piece)\n{\n\tif (p->ended)\n\t\tuiprivUserBug(\"You cannot modify a uiDrawPath that has been ended. (path: %p)\", p);\n\tg_array_append_vals(p->pieces, piece, 1);\n}\n\nvoid uiDrawPathNewFigure(uiDrawPath *p, double x, double y)\n{\n\tstruct piece piece;\n\n\tpiece.type = newFigure;\n\tpiece.d[0] = x;\n\tpiece.d[1] = y;\n\tadd(p, &piece);\n}\n\nvoid uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)\n{\n\tstruct piece piece;\n\n\tif (sweep > 2 * uiPi)\n\t\tsweep = 2 * uiPi;\n\tpiece.type = newFigureArc;\n\tpiece.d[0] = xCenter;\n\tpiece.d[1] = yCenter;\n\tpiece.d[2] = radius;\n\tpiece.d[3] = startAngle;\n\tpiece.d[4] = sweep;\n\tpiece.b = negative;\n\tadd(p, &piece);\n}\n\nvoid uiDrawPathLineTo(uiDrawPath *p, double x, double y)\n{\n\tstruct piece piece;\n\n\tpiece.type = lineTo;\n\tpiece.d[0] = x;\n\tpiece.d[1] = y;\n\tadd(p, &piece);\n}\n\nvoid uiDrawPathArcTo(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)\n{\n\tstruct piece piece;\n\n\tif (sweep > 2 * uiPi)\n\t\tsweep = 2 * uiPi;\n\tpiece.type = arcTo;\n\tpiece.d[0] = xCenter;\n\tpiece.d[1] = yCenter;\n\tpiece.d[2] = radius;\n\tpiece.d[3] = startAngle;\n\tpiece.d[4] = sweep;\n\tpiece.b = negative;\n\tadd(p, &piece);\n}\n\nvoid uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, double c2y, double endX, double endY)\n{\n\tstruct piece piece;\n\n\tpiece.type = bezierTo;\n\tpiece.d[0] = c1x;\n\tpiece.d[1] = c1y;\n\tpiece.d[2] = c2x;\n\tpiece.d[3] = c2y;\n\tpiece.d[4] = endX;\n\tpiece.d[5] = endY;\n\tadd(p, &piece);\n}\n\nvoid uiDrawPathCloseFigure(uiDrawPath *p)\n{\n\tstruct piece piece;\n\n\tpiece.type = closeFigure;\n\tadd(p, &piece);\n}\n\nvoid uiDrawPathAddRectangle(uiDrawPath *p, double x, double y, double width, double height)\n{\n\tstruct piece piece;\n\n\tpiece.type = addRect;\n\tpiece.d[0] = x;\n\tpiece.d[1] = y;\n\tpiece.d[2] = width;\n\tpiece.d[3] = height;\n\tadd(p, &piece);\n}\n\nvoid uiDrawPathEnd(uiDrawPath *p)\n{\n\tp->ended = TRUE;\n}\n\nvoid uiprivRunPath(uiDrawPath *p, cairo_t *cr)\n{\n\tguint i;\n\tstruct piece *piece;\n\tvoid (*arc)(cairo_t *, double, double, double, double, double);\n\n\tif (!p->ended)\n\t\tuiprivUserBug(\"You cannot draw with a uiDrawPath that has not been ended. (path: %p)\", p);\n\tcairo_new_path(cr);\n\tfor (i = 0; i < p->pieces->len; i++) {\n\t\tpiece = &g_array_index(p->pieces, struct piece, i);\n\t\tswitch (piece->type) {\n\t\tcase newFigure:\n\t\t\tcairo_move_to(cr, piece->d[0], piece->d[1]);\n\t\t\tbreak;\n\t\tcase newFigureArc:\n\t\t\tcairo_new_sub_path(cr);\n\t\t\t// fall through\n\t\tcase arcTo:\n\t\t\tarc = cairo_arc;\n\t\t\tif (piece->b)\n\t\t\t\tarc = cairo_arc_negative;\n\t\t\t(*arc)(cr,\n\t\t\t\tpiece->d[0],\n\t\t\t\tpiece->d[1],\n\t\t\t\tpiece->d[2],\n\t\t\t\tpiece->d[3],\n\t\t\t\tpiece->d[3] + piece->d[4]);\n\t\t\tbreak;\n\t\tcase lineTo:\n\t\t\tcairo_line_to(cr, piece->d[0], piece->d[1]);\n\t\t\tbreak;\n\t\tcase bezierTo:\n\t\t\tcairo_curve_to(cr,\n\t\t\t\tpiece->d[0],\n\t\t\t\tpiece->d[1],\n\t\t\t\tpiece->d[2],\n\t\t\t\tpiece->d[3],\n\t\t\t\tpiece->d[4],\n\t\t\t\tpiece->d[5]);\n\t\t\tbreak;\n\t\tcase closeFigure:\n\t\t\tcairo_close_path(cr);\n\t\t\tbreak;\n\t\tcase addRect:\n\t\t\tcairo_rectangle(cr,\n\t\t\t\tpiece->d[0],\n\t\t\t\tpiece->d[1],\n\t\t\t\tpiece->d[2],\n\t\t\t\tpiece->d[3]);\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nuiDrawFillMode uiprivPathFillMode(uiDrawPath *path)\n{\n\treturn path->fillMode;\n}\n"
  },
  {
    "path": "unix/drawtext.c",
    "content": "// 11 march 2018\n#include \"uipriv_unix.h\"\n#include \"draw.h\"\n#include \"attrstr.h\"\n\nstruct uiDrawTextLayout {\n\tPangoLayout *layout;\n};\n\n// we need a context for a few things\n// 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\n// we could use pango_font_map_create_context(pango_cairo_font_map_get_default()) but that will ignore GDK-specific settings\n// so let's use gdk_pango_context_get() instead; even though it's for the default screen only, it's good enough for us\n#define mkGenericPangoCairoContext() (gdk_pango_context_get())\n\nstatic const PangoAlignment pangoAligns[] = {\n\t[uiDrawTextAlignLeft] = PANGO_ALIGN_LEFT,\n\t[uiDrawTextAlignCenter] = PANGO_ALIGN_CENTER,\n\t[uiDrawTextAlignRight] = PANGO_ALIGN_RIGHT,\n};\n\nuiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *p)\n{\n\tuiDrawTextLayout *tl;\n\tPangoContext *context;\n\tPangoFontDescription *desc;\n\tPangoAttrList *attrs;\n\tint pangoWidth;\n\n\ttl = uiprivNew(uiDrawTextLayout);\n\n\t// in this case, the context is necessary to create the layout\n\t// the layout takes a ref on the context so we can unref it afterward\n\tcontext = mkGenericPangoCairoContext();\n\ttl->layout = pango_layout_new(context);\n\tg_object_unref(context);\n\n\t// this is safe; pango_layout_set_text() copies the string\n\tpango_layout_set_text(tl->layout, uiAttributedStringString(p->String), -1);\n\n\tdesc = uiprivFontDescriptorToPangoFontDescription(p->DefaultFont);\n\tpango_layout_set_font_description(tl->layout, desc);\n\t// this is safe; the description is copied\n\tpango_font_description_free(desc);\n\n\tpangoWidth = cairoToPango(p->Width);\n\tif (p->Width < 0)\n\t\tpangoWidth = -1;\n\tpango_layout_set_width(tl->layout, pangoWidth);\n\n\tpango_layout_set_alignment(tl->layout, pangoAligns[p->Align]);\n\n\tattrs = uiprivAttributedStringToPangoAttrList(p);\n\tpango_layout_set_attributes(tl->layout, attrs);\n\tpango_attr_list_unref(attrs);\n\n\treturn tl;\n}\n\nvoid uiDrawFreeTextLayout(uiDrawTextLayout *tl)\n{\n\tg_object_unref(tl->layout);\n\tuiprivFree(tl);\n}\n\nvoid uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)\n{\n\t// TODO have an implicit save/restore on each drawing functions instead? and is this correct?\n\tcairo_set_source_rgb(c->cr, 0.0, 0.0, 0.0);\n\tcairo_move_to(c->cr, x, y);\n\tpango_cairo_show_layout(c->cr, tl->layout);\n}\n\nvoid uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height)\n{\n\tPangoRectangle logical;\n\n\tpango_layout_get_extents(tl->layout, NULL, &logical);\n\t*width = pangoToCairo(logical.width);\n\t*height = pangoToCairo(logical.height);\n}\n"
  },
  {
    "path": "unix/editablecombo.c",
    "content": "// 11 june 2015\n#include \"uipriv_unix.h\"\n\nstruct uiEditableCombobox {\n\tuiUnixControl c;\n\tGtkWidget *widget;\n\tGtkBin *bin;\n\tGtkComboBox *combobox;\n\tGtkComboBoxText *comboboxText;\n\tvoid (*onChanged)(uiEditableCombobox *, void *);\n\tvoid *onChangedData;\n\tgulong onChangedSignal;\n};\n\nuiUnixControlAllDefaults(uiEditableCombobox)\n\nstatic void onChanged(GtkComboBox *cbox, gpointer data)\n{\n\tuiEditableCombobox *c = uiEditableCombobox(data);\n\n\t(*(c->onChanged))(c, c->onChangedData);\n}\n\nstatic void defaultOnChanged(uiEditableCombobox *c, void *data)\n{\n\t// do nothing\n}\n\nvoid uiEditableComboboxAppend(uiEditableCombobox *c, const char *text)\n{\n\tgtk_combo_box_text_append(c->comboboxText, NULL, text);\n}\n\nchar *uiEditableComboboxText(uiEditableCombobox *c)\n{\n\tchar *s;\n\tchar *out;\n\n\ts = gtk_combo_box_text_get_active_text(c->comboboxText);\n\t// s will always be non-NULL in the case of a combobox with an entry (according to the source code)\n\tout = uiUnixStrdupText(s);\n\tg_free(s);\n\treturn out;\n}\n\nvoid uiEditableComboboxSetText(uiEditableCombobox *c, const char *text)\n{\n\tGtkEntry *e;\n\n\t// we need to inhibit sending of ::changed because this WILL send a ::changed otherwise\n\tg_signal_handler_block(c->combobox, c->onChangedSignal);\n\t// since there isn't a gtk_combo_box_text_set_active_text()...\n\te = GTK_ENTRY(gtk_bin_get_child(c->bin));\n\tgtk_entry_set_text(e, text);\n\tg_signal_handler_unblock(c->combobox, c->onChangedSignal);\n}\n\nvoid uiEditableComboboxOnChanged(uiEditableCombobox *c, void (*f)(uiEditableCombobox *c, void *data), void *data)\n{\n\tc->onChanged = f;\n\tc->onChangedData = data;\n}\n\nuiEditableCombobox *uiNewEditableCombobox(void)\n{\n\tuiEditableCombobox *c;\n\n\tuiUnixNewControl(uiEditableCombobox, c);\n\n\tc->widget = gtk_combo_box_text_new_with_entry();\n\tc->bin = GTK_BIN(c->widget);\n\tc->combobox = GTK_COMBO_BOX(c->widget);\n\tc->comboboxText = GTK_COMBO_BOX_TEXT(c->widget);\n\n\tc->onChangedSignal = g_signal_connect(c->widget, \"changed\", G_CALLBACK(onChanged), c);\n\tuiEditableComboboxOnChanged(c, defaultOnChanged, NULL);\n\n\treturn c;\n}\n"
  },
  {
    "path": "unix/entry.c",
    "content": "// 11 june 2015\n#include \"uipriv_unix.h\"\n\nstruct uiEntry {\n\tuiUnixControl c;\n\tGtkWidget *widget;\n\tGtkEntry *entry;\n\tGtkEditable *editable;\n\tvoid (*onChanged)(uiEntry *, void *);\n\tvoid *onChangedData;\n\tgulong onChangedSignal;\n};\n\nuiUnixControlAllDefaults(uiEntry)\n\nstatic void onChanged(GtkEditable *editable, gpointer data)\n{\n\tuiEntry *e = uiEntry(data);\n\n\t(*(e->onChanged))(e, e->onChangedData);\n}\n\nstatic void defaultOnChanged(uiEntry *e, void *data)\n{\n\t// do nothing\n}\n\nchar *uiEntryText(uiEntry *e)\n{\n\treturn uiUnixStrdupText(gtk_entry_get_text(e->entry));\n}\n\nvoid uiEntrySetText(uiEntry *e, const char *text)\n{\n\t// we need to inhibit sending of ::changed because this WILL send a ::changed otherwise\n\tg_signal_handler_block(e->editable, e->onChangedSignal);\n\tgtk_entry_set_text(e->entry, text);\n\tg_signal_handler_unblock(e->editable, e->onChangedSignal);\n\t// don't queue the control for resize; entry sizes are independent of their contents\n}\n\nvoid uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *, void *), void *data)\n{\n\te->onChanged = f;\n\te->onChangedData = data;\n}\n\nint uiEntryReadOnly(uiEntry *e)\n{\n\treturn gtk_editable_get_editable(e->editable) == FALSE;\n}\n\nvoid uiEntrySetReadOnly(uiEntry *e, int readonly)\n{\n\tgboolean editable;\n\n\teditable = TRUE;\n\tif (readonly)\n\t\teditable = FALSE;\n\tgtk_editable_set_editable(e->editable, editable);\n}\n\nstatic uiEntry *finishNewEntry(GtkWidget *w, const gchar *signal)\n{\n\tuiEntry *e;\n\n\tuiUnixNewControl(uiEntry, e);\n\n\te->widget = w;\n\te->entry = GTK_ENTRY(e->widget);\n\te->editable = GTK_EDITABLE(e->widget);\n\n\te->onChangedSignal = g_signal_connect(e->widget, signal, G_CALLBACK(onChanged), e);\n\tuiEntryOnChanged(e, defaultOnChanged, NULL);\n\n\treturn e;\n}\n\nuiEntry *uiNewEntry(void)\n{\n\treturn finishNewEntry(gtk_entry_new(), \"changed\");\n}\n\nuiEntry *uiNewPasswordEntry(void)\n{\n\tGtkWidget *e;\n\n\te = gtk_entry_new();\n\tgtk_entry_set_visibility(GTK_ENTRY(e), FALSE);\n\treturn finishNewEntry(e, \"changed\");\n}\n\n// TODO make it use a separate function to be type-safe\nuiEntry *uiNewSearchEntry(void)\n{\n\treturn finishNewEntry(gtk_search_entry_new(), \"search-changed\");\n}\n"
  },
  {
    "path": "unix/fontbutton.c",
    "content": "// 14 april 2016\n#include \"uipriv_unix.h\"\n#include \"attrstr.h\"\n\nstruct uiFontButton {\n\tuiUnixControl c;\n\tGtkWidget *widget;\n\tGtkButton *button;\n\tGtkFontButton *fb;\n\tGtkFontChooser *fc;\n\tvoid (*onChanged)(uiFontButton *, void *);\n\tvoid *onChangedData;\n};\n\nuiUnixControlAllDefaults(uiFontButton)\n\n// TODO NOTE no need to inhibit the signal; font-set is documented as only being sent when the user changes the font\nstatic void onFontSet(GtkFontButton *button, gpointer data)\n{\n\tuiFontButton *b = uiFontButton(data);\n\n\t(*(b->onChanged))(b, b->onChangedData);\n}\n\nstatic void defaultOnChanged(uiFontButton *b, void *data)\n{\n\t// do nothing\n}\n\nvoid uiFontButtonFont(uiFontButton *b, uiFontDescriptor *desc)\n{\n\tPangoFontDescription *pdesc;\n\n\tpdesc = gtk_font_chooser_get_font_desc(b->fc);\n\tuiprivFontDescriptorFromPangoFontDescription(pdesc, desc);\n\t// pdesc is transfer-full and thus is a copy\n\tpango_font_description_free(pdesc);\n}\n\nvoid uiFontButtonOnChanged(uiFontButton *b, void (*f)(uiFontButton *, void *), void *data)\n{\n\tb->onChanged = f;\n\tb->onChangedData = data;\n}\n\nuiFontButton *uiNewFontButton(void)\n{\n\tuiFontButton *b;\n\n\tuiUnixNewControl(uiFontButton, b);\n\n\tb->widget = gtk_font_button_new();\n\tb->button = GTK_BUTTON(b->widget);\n\tb->fb = GTK_FONT_BUTTON(b->widget);\n\tb->fc = GTK_FONT_CHOOSER(b->widget);\n\n\t// match behavior on other platforms\n\tgtk_font_button_set_show_style(b->fb, TRUE);\n\tgtk_font_button_set_show_size(b->fb, TRUE);\n\tgtk_font_button_set_use_font(b->fb, FALSE);\n\tgtk_font_button_set_use_size(b->fb, FALSE);\n\t// other customizations\n\tgtk_font_chooser_set_show_preview_entry(b->fc, TRUE);\n\n\tg_signal_connect(b->widget, \"font-set\", G_CALLBACK(onFontSet), b);\n\tuiFontButtonOnChanged(b, defaultOnChanged, NULL);\n\n\treturn b;\n}\n\nvoid uiFreeFontButtonFont(uiFontDescriptor *desc)\n{\n\t// TODO ensure this is synchronized with fontmatch.c\n\tuiFreeText((char *) (desc->Family));\n}\n"
  },
  {
    "path": "unix/fontmatch.c",
    "content": "// 11 march 2018\n#include \"uipriv_unix.h\"\n#include \"attrstr.h\"\n\nstatic const PangoStyle pangoItalics[] = {\n\t[uiTextItalicNormal] = PANGO_STYLE_NORMAL,\n\t[uiTextItalicOblique] = PANGO_STYLE_OBLIQUE,\n\t[uiTextItalicItalic] = PANGO_STYLE_ITALIC,\n};\n\nstatic const PangoStretch pangoStretches[] = {\n\t[uiTextStretchUltraCondensed] = PANGO_STRETCH_ULTRA_CONDENSED,\n\t[uiTextStretchExtraCondensed] = PANGO_STRETCH_EXTRA_CONDENSED,\n\t[uiTextStretchCondensed] = PANGO_STRETCH_CONDENSED,\n\t[uiTextStretchSemiCondensed] = PANGO_STRETCH_SEMI_CONDENSED,\n\t[uiTextStretchNormal] = PANGO_STRETCH_NORMAL,\n\t[uiTextStretchSemiExpanded] = PANGO_STRETCH_SEMI_EXPANDED,\n\t[uiTextStretchExpanded] = PANGO_STRETCH_EXPANDED,\n\t[uiTextStretchExtraExpanded] = PANGO_STRETCH_EXTRA_EXPANDED,\n\t[uiTextStretchUltraExpanded] = PANGO_STRETCH_ULTRA_EXPANDED,\n};\n\n// for the most part, pango weights correlate to ours\n// the differences:\n// - Book — libui: 350, Pango: 380\n// - Ultra Heavy — libui: 950, Pango: 1000\n// TODO figure out what to do about this misalignment\nPangoWeight uiprivWeightToPangoWeight(uiTextWeight w)\n{\n\treturn (PangoWeight) w;\n}\n\nPangoStyle uiprivItalicToPangoStyle(uiTextItalic i)\n{\n\treturn pangoItalics[i];\n}\n\nPangoStretch uiprivStretchToPangoStretch(uiTextStretch s)\n{\n\treturn pangoStretches[s];\n}\n\nPangoFontDescription *uiprivFontDescriptorToPangoFontDescription(const uiFontDescriptor *uidesc)\n{\n\tPangoFontDescription *desc;\n\n\tdesc = pango_font_description_new();\n\tpango_font_description_set_family(desc, uidesc->Family);\n\t// 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\n\tpango_font_description_set_size(desc, pango_units_from_double(uidesc->Size));\n\tpango_font_description_set_weight(desc, uiprivWeightToPangoWeight(uidesc->Weight));\n\tpango_font_description_set_style(desc, uiprivItalicToPangoStyle(uidesc->Italic));\n\tpango_font_description_set_stretch(desc, uiprivStretchToPangoStretch(uidesc->Stretch));\n\treturn desc;\n}\n\nvoid uiprivFontDescriptorFromPangoFontDescription(PangoFontDescription *pdesc, uiFontDescriptor *uidesc)\n{\n\tPangoStyle pitalic;\n\tPangoStretch pstretch;\n\n\tuidesc->Family = uiUnixStrdupText(pango_font_description_get_family(pdesc));\n\tpitalic = pango_font_description_get_style(pdesc);\n\t// TODO reverse the above misalignment if it is corrected\n\tuidesc->Weight = pango_font_description_get_weight(pdesc);\n\tpstretch = pango_font_description_get_stretch(pdesc);\n\t// absolute size does not matter because, as above, 1 device unit == 1 cairo point\n\tuidesc->Size = pango_units_to_double(pango_font_description_get_size(pdesc));\n\n\tfor (uidesc->Italic = uiTextItalicNormal; uidesc->Italic < uiTextItalicItalic; uidesc->Italic++)\n\t\tif (pangoItalics[uidesc->Italic] == pitalic)\n\t\t\tbreak;\n\tfor (uidesc->Stretch = uiTextStretchUltraCondensed; uidesc->Stretch < uiTextStretchUltraExpanded; uidesc->Stretch++)\n\t\tif (pangoStretches[uidesc->Stretch] == pstretch)\n\t\t\tbreak;\n}\n"
  },
  {
    "path": "unix/form.c",
    "content": "// 8 june 2016\n#include \"uipriv_unix.h\"\n\nstruct formChild {\n\tuiControl *c;\n\tint stretchy;\n\tGtkWidget *label;\n\tgboolean oldhexpand;\n\tGtkAlign oldhalign;\n\tgboolean oldvexpand;\n\tGtkAlign oldvalign;\n\tGBinding *labelBinding;\n};\n\nstruct uiForm {\n\tuiUnixControl c;\n\tGtkWidget *widget;\n\tGtkContainer *container;\n\tGtkGrid *grid;\n\tGArray *children;\n\tint padded;\n\tGtkSizeGroup *stretchygroup;\t\t// ensures all stretchy controls have the same size\n};\n\nuiUnixControlAllDefaultsExceptDestroy(uiForm)\n\n#define ctrl(f, i) &g_array_index(f->children, struct formChild, i)\n\nstatic void uiFormDestroy(uiControl *c)\n{\n\tuiForm *f = uiForm(c);\n\tstruct formChild *fc;\n\tguint i;\n\n\t// kill the size group\n\tg_object_unref(f->stretchygroup);\n\t// free all controls\n\tfor (i = 0; i < f->children->len; i++) {\n\t\tfc = ctrl(f, i);\n\t\tuiControlSetParent(fc->c, NULL);\n\t\tuiUnixControlSetContainer(uiUnixControl(fc->c), f->container, TRUE);\n\t\tuiControlDestroy(fc->c);\n\t\tgtk_widget_destroy(fc->label);\n\t}\n\tg_array_free(f->children, TRUE);\n\t// and then ourselves\n\tg_object_unref(f->widget);\n\tuiFreeControl(uiControl(f));\n}\n\nvoid uiFormAppend(uiForm *f, const char *label, uiControl *c, int stretchy)\n{\n\tstruct formChild fc;\n\tGtkWidget *widget;\n\tguint row;\n\n\tfc.c = c;\n\twidget = GTK_WIDGET(uiControlHandle(fc.c));\n\tfc.stretchy = stretchy;\n\tfc.oldhexpand = gtk_widget_get_hexpand(widget);\n\tfc.oldhalign = gtk_widget_get_halign(widget);\n\tfc.oldvexpand = gtk_widget_get_vexpand(widget);\n\tfc.oldvalign = gtk_widget_get_valign(widget);\n\n\tif (stretchy) {\n\t\tgtk_widget_set_vexpand(widget, TRUE);\n\t\tgtk_widget_set_valign(widget, GTK_ALIGN_FILL);\n\t\tgtk_size_group_add_widget(f->stretchygroup, widget);\n\t} else\n\t\tgtk_widget_set_vexpand(widget, FALSE);\n\t// and make them fill horizontally\n\tgtk_widget_set_hexpand(widget, TRUE);\n\tgtk_widget_set_halign(widget, GTK_ALIGN_FILL);\n\n\tfc.label = gtk_label_new(label);\n\tgtk_widget_set_hexpand(fc.label, FALSE);\n\tgtk_widget_set_halign(fc.label, GTK_ALIGN_END);\n\tgtk_widget_set_vexpand(fc.label, FALSE);\n\tif (GTK_IS_SCROLLED_WINDOW(widget))\n\t\tgtk_widget_set_valign(fc.label, GTK_ALIGN_START);\n\telse\n\t\tgtk_widget_set_valign(fc.label, GTK_ALIGN_CENTER);\n\tgtk_style_context_add_class(gtk_widget_get_style_context(fc.label), \"dim-label\");\n\trow = f->children->len;\n\tgtk_grid_attach(f->grid, fc.label,\n\t\t0, row,\n\t\t1, 1);\n\t// and make them share visibility so if the control is hidden, so is its label\n\tfc.labelBinding = g_object_bind_property(GTK_WIDGET(uiControlHandle(fc.c)), \"visible\",\n\t\tfc.label, \"visible\",\n\t\tG_BINDING_SYNC_CREATE);\n\n\tuiControlSetParent(fc.c, uiControl(f));\n\tuiUnixControlSetContainer(uiUnixControl(fc.c), f->container, FALSE);\n\tg_array_append_val(f->children, fc);\n\n\t// move the widget to the correct place\n\tgtk_container_child_set(f->container, widget,\n\t\t\"left-attach\", 1,\n\t\t\"top-attach\", row,\n\t\tNULL);\n}\n\nvoid uiFormDelete(uiForm *f, int index)\n{\n\tstruct formChild *fc;\n\tGtkWidget *widget;\n\n\tfc = ctrl(f, index);\n\twidget = GTK_WIDGET(uiControlHandle(fc->c));\n\n\tgtk_widget_destroy(fc->label);\n\n\tuiControlSetParent(fc->c, NULL);\n\tuiUnixControlSetContainer(uiUnixControl(fc->c), f->container, TRUE);\n\n\tif (fc->stretchy)\n\t\tgtk_size_group_remove_widget(f->stretchygroup, widget);\n\tgtk_widget_set_hexpand(widget, fc->oldhexpand);\n\tgtk_widget_set_halign(widget, fc->oldhalign);\n\tgtk_widget_set_vexpand(widget, fc->oldvexpand);\n\tgtk_widget_set_valign(widget, fc->oldvalign);\n\n\tg_array_remove_index(f->children, index);\n}\n\nint uiFormPadded(uiForm *f)\n{\n\treturn f->padded;\n}\n\nvoid uiFormSetPadded(uiForm *f, int padded)\n{\n\tf->padded = padded;\n\tif (f->padded) {\n\t\tgtk_grid_set_row_spacing(f->grid, uiprivGTKYPadding);\n\t\tgtk_grid_set_column_spacing(f->grid, uiprivGTKXPadding);\n\t} else {\n\t\tgtk_grid_set_row_spacing(f->grid, 0);\n\t\tgtk_grid_set_column_spacing(f->grid, 0);\n\t}\n}\n\nuiForm *uiNewForm(void)\n{\n\tuiForm *f;\n\n\tuiUnixNewControl(uiForm, f);\n\n\tf->widget = gtk_grid_new();\n\tf->container = GTK_CONTAINER(f->widget);\n\tf->grid = GTK_GRID(f->widget);\n\n\tf->stretchygroup = gtk_size_group_new(GTK_SIZE_GROUP_VERTICAL);\n\n\tf->children = g_array_new(FALSE, TRUE, sizeof (struct formChild));\n\n\treturn f;\n}\n"
  },
  {
    "path": "unix/future.c",
    "content": "// 29 june 2016\n#include \"uipriv_unix.h\"\n\n// functions FROM THE FUTURE!\n// in some cases, because being held back by LTS releases sucks :/\n// in others, because parts of GTK+ being unstable until recently also sucks :/\n\n// added in pango 1.38; we need 1.36\nstatic PangoAttribute *(*newFeaturesAttr)(const gchar *features) = NULL;\nstatic PangoAttribute *(*newFGAlphaAttr)(guint16 alpha) = NULL;\nstatic PangoAttribute *(*newBGAlphaAttr)(guint16 alpha) = NULL;\n\n// added in GTK+ 3.20; we need 3.10\nstatic void (*gwpIterSetObjectName)(GtkWidgetPath *path, gint pos, const char *name) = NULL;\n\n// note that we treat any error as \"the symbols aren't there\" (and don't care if dlclose() failed)\nvoid uiprivLoadFutures(void)\n{\n\tvoid *handle;\n\n\t// dlsym() walks the dependency chain, so opening the current process should be sufficient\n\thandle = dlopen(NULL, RTLD_LAZY);\n\tif (handle == NULL)\n\t\treturn;\n#define GET(var, fn) *((void **) (&var)) = dlsym(handle, #fn)\n\tGET(newFeaturesAttr, pango_attr_font_features_new);\n\tGET(newFGAlphaAttr, pango_attr_foreground_alpha_new);\n\tGET(newBGAlphaAttr, pango_attr_background_alpha_new);\n\tGET(gwpIterSetObjectName, gtk_widget_path_iter_set_object_name);\n\tdlclose(handle);\n}\n\nPangoAttribute *uiprivFUTURE_pango_attr_font_features_new(const gchar *features)\n{\n\tif (newFeaturesAttr == NULL)\n\t\treturn NULL;\n\treturn (*newFeaturesAttr)(features);\n}\n\nPangoAttribute *uiprivFUTURE_pango_attr_foreground_alpha_new(guint16 alpha)\n{\n\tif (newFGAlphaAttr == NULL)\n\t\treturn NULL;\n\treturn (*newFGAlphaAttr)(alpha);\n}\n\nPangoAttribute *uiprivFUTURE_pango_attr_background_alpha_new(guint16 alpha)\n{\n\tif (newBGAlphaAttr == NULL)\n\t\treturn NULL;\n\treturn (*newBGAlphaAttr)(alpha);\n}\n\ngboolean uiprivFUTURE_gtk_widget_path_iter_set_object_name(GtkWidgetPath *path, gint pos, const char *name)\n{\n\tif (gwpIterSetObjectName == NULL)\n\t\treturn FALSE;\n\t(*gwpIterSetObjectName)(path, pos, name);\n\treturn TRUE;\n}\n"
  },
  {
    "path": "unix/graphemes.c",
    "content": "// 25 may 2016\n#include \"uipriv_unix.h\"\n#include \"attrstr.h\"\n\nint uiprivGraphemesTakesUTF16(void)\n{\n\treturn 0;\n}\n\nuiprivGraphemes *uiprivNewGraphemes(void *s, size_t len)\n{\n\tuiprivGraphemes *g;\n\tchar *text = (char *) s;\n\tsize_t lenchars;\n\tPangoLogAttr *logattrs;\n\tsize_t i;\n\tsize_t *op;\n\n\tg = uiprivNew(uiprivGraphemes);\n\n\t// TODO see if we can use the utf routines\n\tlenchars = g_utf8_strlen(text, -1);\n\tlogattrs = (PangoLogAttr *) uiprivAlloc((lenchars + 1) * sizeof (PangoLogAttr), \"PangoLogAttr[] (graphemes)\");\n\tpango_get_log_attrs(text, len,\n\t\t-1, NULL,\n\t\tlogattrs, lenchars + 1);\n\n\t// first figure out how many graphemes there are\n\tg->len = 0;\n\tfor (i = 0; i < lenchars; i++)\n\t\tif (logattrs[i].is_cursor_position != 0)\n\t\t\tg->len++;\n\n\tg->pointsToGraphemes = (size_t *) uiprivAlloc((len + 1) * sizeof (size_t), \"size_t[] (graphemes)\");\n\tg->graphemesToPoints = (size_t *) uiprivAlloc((g->len + 1) * sizeof (size_t), \"size_t[] (graphemes)\");\n\n\t// compute the graphemesToPoints array\n\t// TODO merge with the next for loop somehow?\n\top = g->graphemesToPoints;\n\tfor (i = 0; i < lenchars; i++)\n\t\tif (logattrs[i].is_cursor_position != 0)\n\t\t\t// TODO optimize this\n\t\t\t*op++ = g_utf8_offset_to_pointer(text, i) - text;\n\t// and do the last one\n\t*op++ = len;\n\n\t// and finally build the pointsToGraphemes array\n\top = g->pointsToGraphemes;\n\tfor (i = 0; i < g->len; i++) {\n\t\tsize_t j;\n\t\tsize_t first, last;\n\n\t\tfirst = g->graphemesToPoints[i];\n\t\tlast = g->graphemesToPoints[i + 1];\n\t\tfor (j = first; j < last; j++)\n\t\t\t*op++ = i;\n\t}\n\t// and do the last one\n\t*op++ = i;\n\n\tuiprivFree(logattrs);\n\treturn g;\n}\n"
  },
  {
    "path": "unix/grid.c",
    "content": "// 9 june 2016\n#include \"uipriv_unix.h\"\n\nstruct gridChild {\n\tuiControl *c;\n\tGtkWidget *label;\n\tgboolean oldhexpand;\n\tGtkAlign oldhalign;\n\tgboolean oldvexpand;\n\tGtkAlign oldvalign;\n};\n\nstruct uiGrid {\n\tuiUnixControl c;\n\tGtkWidget *widget;\n\tGtkContainer *container;\n\tGtkGrid *grid;\n\tGArray *children;\n\tint padded;\n};\n\nuiUnixControlAllDefaultsExceptDestroy(uiGrid)\n\n#define ctrl(g, i) &g_array_index(g->children, struct gridChild, i)\n\nstatic void uiGridDestroy(uiControl *c)\n{\n\tuiGrid *g = uiGrid(c);\n\tstruct gridChild *gc;\n\tguint i;\n\n\t// free all controls\n\tfor (i = 0; i < g->children->len; i++) {\n\t\tgc = ctrl(g, i);\n\t\tuiControlSetParent(gc->c, NULL);\n\t\tuiUnixControlSetContainer(uiUnixControl(gc->c), g->container, TRUE);\n\t\tuiControlDestroy(gc->c);\n\t}\n\tg_array_free(g->children, TRUE);\n\t// and then ourselves\n\tg_object_unref(g->widget);\n\tuiFreeControl(uiControl(g));\n}\n\n#define TODO_MASSIVE_HACK(c) \\\n\tif (!uiUnixControl(c)->addedBefore) { \\\n\t\tg_object_ref_sink(GTK_WIDGET(uiControlHandle(uiControl(c)))); \\\n\t\tgtk_widget_show(GTK_WIDGET(uiControlHandle(uiControl(c)))); \\\n\t\tuiUnixControl(c)->addedBefore = TRUE; \\\n\t}\n\nstatic const GtkAlign gtkAligns[] = {\n\t[uiAlignFill] = GTK_ALIGN_FILL,\n\t[uiAlignStart] = GTK_ALIGN_START,\n\t[uiAlignCenter] = GTK_ALIGN_CENTER,\n\t[uiAlignEnd] = GTK_ALIGN_END,\n};\n\nstatic const GtkPositionType gtkPositions[] = {\n\t[uiAtLeading] = GTK_POS_LEFT,\n\t[uiAtTop] = GTK_POS_TOP,\n\t[uiAtTrailing] = GTK_POS_RIGHT,\n\t[uiAtBottom] = GTK_POS_BOTTOM,\n};\n\nstatic GtkWidget *prepare(struct gridChild *gc, uiControl *c, int hexpand, uiAlign halign, int vexpand, uiAlign valign)\n{\n\tGtkWidget *widget;\n\n\tgc->c = c;\n\twidget = GTK_WIDGET(uiControlHandle(gc->c));\n\tgc->oldhexpand = gtk_widget_get_hexpand(widget);\n\tgc->oldhalign = gtk_widget_get_halign(widget);\n\tgc->oldvexpand = gtk_widget_get_vexpand(widget);\n\tgc->oldvalign = gtk_widget_get_valign(widget);\n\tgtk_widget_set_hexpand(widget, hexpand != 0);\n\tgtk_widget_set_halign(widget, gtkAligns[halign]);\n\tgtk_widget_set_vexpand(widget, vexpand != 0);\n\tgtk_widget_set_valign(widget, gtkAligns[valign]);\n\treturn widget;\n}\n\nvoid uiGridAppend(uiGrid *g, uiControl *c, int left, int top, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign)\n{\n\tstruct gridChild gc;\n\tGtkWidget *widget;\n\n\twidget = prepare(&gc, c, hexpand, halign, vexpand, valign);\n\tuiControlSetParent(gc.c, uiControl(g));\n\tTODO_MASSIVE_HACK(uiUnixControl(gc.c));\n\tgtk_grid_attach(g->grid, widget,\n\t\tleft, top,\n\t\txspan, yspan);\n\tg_array_append_val(g->children, gc);\n}\n\nvoid uiGridInsertAt(uiGrid *g, uiControl *c, uiControl *existing, uiAt at, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign)\n{\n\tstruct gridChild gc;\n\tGtkWidget *widget;\n\n\twidget = prepare(&gc, c, hexpand, halign, vexpand, valign);\n\tuiControlSetParent(gc.c, uiControl(g));\n\tTODO_MASSIVE_HACK(uiUnixControl(gc.c));\n\tgtk_grid_attach_next_to(g->grid, widget,\n\t\tGTK_WIDGET(uiControlHandle(existing)), gtkPositions[at],\n\t\txspan, yspan);\n\tg_array_append_val(g->children, gc);\n}\n\nint uiGridPadded(uiGrid *g)\n{\n\treturn g->padded;\n}\n\nvoid uiGridSetPadded(uiGrid *g, int padded)\n{\n\tg->padded = padded;\n\tif (g->padded) {\n\t\tgtk_grid_set_row_spacing(g->grid, uiprivGTKYPadding);\n\t\tgtk_grid_set_column_spacing(g->grid, uiprivGTKXPadding);\n\t} else {\n\t\tgtk_grid_set_row_spacing(g->grid, 0);\n\t\tgtk_grid_set_column_spacing(g->grid, 0);\n\t}\n}\n\nuiGrid *uiNewGrid(void)\n{\n\tuiGrid *g;\n\n\tuiUnixNewControl(uiGrid, g);\n\n\tg->widget = gtk_grid_new();\n\tg->container = GTK_CONTAINER(g->widget);\n\tg->grid = GTK_GRID(g->widget);\n\n\tg->children = g_array_new(FALSE, TRUE, sizeof (struct gridChild));\n\n\treturn g;\n}\n"
  },
  {
    "path": "unix/group.c",
    "content": "// 11 june 2015\n#include \"uipriv_unix.h\"\n\nstruct uiGroup {\n\tuiUnixControl c;\n\tGtkWidget *widget;\n\tGtkContainer *container;\n\tGtkBin *bin;\n\tGtkFrame *frame;\n\n\t// 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\n\tuiprivChild *child;\n\n\tint margined;\n};\n\nuiUnixControlAllDefaultsExceptDestroy(uiGroup)\n\nstatic void uiGroupDestroy(uiControl *c)\n{\n\tuiGroup *g = uiGroup(c);\n\n\tif (g->child != NULL)\n\t\tuiprivChildDestroy(g->child);\n\tg_object_unref(g->widget);\n\tuiFreeControl(uiControl(g));\n}\n\nchar *uiGroupTitle(uiGroup *g)\n{\n\treturn uiUnixStrdupText(gtk_frame_get_label(g->frame));\n}\n\nvoid uiGroupSetTitle(uiGroup *g, const char *text)\n{\n\tgtk_frame_set_label(g->frame, text);\n}\n\nvoid uiGroupSetChild(uiGroup *g, uiControl *child)\n{\n\tif (g->child != NULL)\n\t\tuiprivChildRemove(g->child);\n\tg->child = uiprivNewChildWithBox(child, uiControl(g), g->container, g->margined);\n}\n\nint uiGroupMargined(uiGroup *g)\n{\n\treturn g->margined;\n}\n\nvoid uiGroupSetMargined(uiGroup *g, int margined)\n{\n\tg->margined = margined;\n\tif (g->child != NULL)\n\t\tuiprivChildSetMargined(g->child, g->margined);\n}\n\nuiGroup *uiNewGroup(const char *text)\n{\n\tuiGroup *g;\n\tgfloat yalign;\n\tGtkLabel *label;\n\tPangoAttribute *bold;\n\tPangoAttrList *boldlist;\n\n\tuiUnixNewControl(uiGroup, g);\n\n\tg->widget = gtk_frame_new(text);\n\tg->container = GTK_CONTAINER(g->widget);\n\tg->bin = GTK_BIN(g->widget);\n\tg->frame = GTK_FRAME(g->widget);\n\n\t// with GTK+, groupboxes by default have frames and slightly x-offset regular text\n\t// they should have no frame and fully left-justified, bold text\n\t// preserve default y-alignment\n\tgtk_frame_get_label_align(g->frame, NULL, &yalign);\n\tgtk_frame_set_label_align(g->frame, 0, yalign);\n\tgtk_frame_set_shadow_type(g->frame, GTK_SHADOW_NONE);\n\tlabel = GTK_LABEL(gtk_frame_get_label_widget(g->frame));\n\t// this is the boldness level used by GtkPrintUnixDialog\n\t// (it technically uses \"bold\" but see pango's pango-enum-types.c for the name conversion; GType is weird)\n\tbold = pango_attr_weight_new(PANGO_WEIGHT_BOLD);\n\tboldlist = pango_attr_list_new();\n\tpango_attr_list_insert(boldlist, bold);\n\tgtk_label_set_attributes(label, boldlist);\n\tpango_attr_list_unref(boldlist);\t\t// thanks baedert in irc.gimp.net/#gtk+\n\n\treturn g;\n}\n"
  },
  {
    "path": "unix/image.c",
    "content": "// 27 june 2016\n#include \"uipriv_unix.h\"\n\nstruct uiImage {\n\tdouble width;\n\tdouble height;\n\tGPtrArray *images;\n};\n\nstatic void freeImageRep(gpointer item)\n{\n\tcairo_surface_t *cs = (cairo_surface_t *) item;\n\n\tcairo_surface_destroy(cs);\n}\n\nuiImage *uiNewImage(double width, double height)\n{\n\tuiImage *i;\n\n\ti = uiprivNew(uiImage);\n\ti->width = width;\n\ti->height = height;\n\ti->images = g_ptr_array_new_with_free_func(freeImageRep);\n\treturn i;\n}\n\nvoid uiFreeImage(uiImage *i)\n{\n\tg_ptr_array_free(i->images, TRUE);\n\tuiprivFree(i);\n}\n\nvoid uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int byteStride)\n{\n\tcairo_surface_t *cs;\n\tuint8_t *data, *pix;\n\tint realStride;\n\tint x, y;\n\n\t// note that this is native-endian\n\tcs = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,\n\t\tpixelWidth, pixelHeight);\n\tif (cairo_surface_status(cs) != CAIRO_STATUS_SUCCESS)\n\t\t/* TODO */;\n\tcairo_surface_flush(cs);\n\n\tpix = (uint8_t *) pixels;\n\tdata = (uint8_t *) cairo_image_surface_get_data(cs);\n\trealStride = cairo_image_surface_get_stride(cs);\n\tfor (y = 0; y < pixelHeight; y++) {\n\t\tfor (x = 0; x < pixelWidth * 4; x += 4) {\n\t\t\tunion {\n\t\t\t\tuint32_t v32;\n\t\t\t\tuint8_t v8[4];\n\t\t\t} v;\n\n\t\t\tv.v32 = ((uint32_t) (pix[x + 3])) << 24;\n\t\t\tv.v32 |= ((uint32_t) (pix[x])) << 16;\n\t\t\tv.v32 |= ((uint32_t) (pix[x + 1])) << 8;\n\t\t\tv.v32 |= ((uint32_t) (pix[x + 2]));\n\t\t\tdata[x] = v.v8[0];\n\t\t\tdata[x + 1] = v.v8[1];\n\t\t\tdata[x + 2] = v.v8[2];\n\t\t\tdata[x + 3] = v.v8[3];\n\t\t}\n\t\tpix += byteStride;\n\t\tdata += realStride;\n\t}\n\n\tcairo_surface_mark_dirty(cs);\n\tg_ptr_array_add(i->images, cs);\n}\n\nstruct matcher {\n\tcairo_surface_t *best;\n\tint distX;\n\tint distY;\n\tint targetX;\n\tint targetY;\n\tgboolean foundLarger;\n};\n\n// TODO is this the right algorithm?\nstatic void match(gpointer surface, gpointer data)\n{\n\tcairo_surface_t *cs = (cairo_surface_t *) surface;\n\tstruct matcher *m = (struct matcher *) data;\n\tint x, y;\n\tint x2, y2;\n\n\tx = cairo_image_surface_get_width(cs);\n\ty = cairo_image_surface_get_height(cs);\n\tif (m->best == NULL)\n\t\tgoto writeMatch;\n\n\tif (x < m->targetX && y < m->targetY)\n\t\tif (m->foundLarger)\n\t\t\t// always prefer larger ones\n\t\t\treturn;\n\tif (x >= m->targetX && y >= m->targetY && !m->foundLarger)\n\t\t// we set foundLarger below\n\t\tgoto writeMatch;\n\n\tx2 = abs(m->targetX - x);\n\ty2 = abs(m->targetY - y);\n\tif (x2 < m->distX && y2 < m->distY)\n\t\tgoto writeMatch;\n\n\t// TODO weight one dimension? threshhold?\n\treturn;\n\nwriteMatch:\n\t// must set this here too; otherwise the first image will never have ths set\n\tif (x >= m->targetX && y >= m->targetY && !m->foundLarger)\n\t\tm->foundLarger = TRUE;\n\tm->best = cs;\n\tm->distX = abs(m->targetX - x);\n\tm->distY = abs(m->targetY - y);\n}\n\ncairo_surface_t *uiprivImageAppropriateSurface(uiImage *i, GtkWidget *w)\n{\n\tstruct matcher m;\n\n\tm.best = NULL;\n\tm.distX = G_MAXINT;\n\tm.distY = G_MAXINT;\n\tm.targetX = i->width * gtk_widget_get_scale_factor(w);\n\tm.targetY = i->height * gtk_widget_get_scale_factor(w);\n\tm.foundLarger = FALSE;\n\tg_ptr_array_foreach(i->images, match, &m);\n\treturn m.best;\n}\n"
  },
  {
    "path": "unix/label.c",
    "content": "// 11 june 2015\n#include \"uipriv_unix.h\"\n\nstruct uiLabel {\n\tuiUnixControl c;\n\tGtkWidget *widget;\n\tGtkMisc *misc;\n\tGtkLabel *label;\n};\n\nuiUnixControlAllDefaults(uiLabel)\n\nchar *uiLabelText(uiLabel *l)\n{\n\treturn uiUnixStrdupText(gtk_label_get_text(l->label));\n}\n\nvoid uiLabelSetText(uiLabel *l, const char *text)\n{\n\tgtk_label_set_text(l->label, text);\n}\n\nuiLabel *uiNewLabel(const char *text)\n{\n\tuiLabel *l;\n\n\tuiUnixNewControl(uiLabel, l);\n\n\tl->widget = gtk_label_new(text);\n\tl->misc = GTK_MISC(l->widget);\n\tl->label = GTK_LABEL(l->widget);\n\n\tgtk_misc_set_alignment(l->misc, 0, 0);\n\n\treturn l;\n}\n"
  },
  {
    "path": "unix/main.c",
    "content": "// 6 april 2015\n#include \"uipriv_unix.h\"\n\nuiInitOptions uiprivOptions;\n\nstatic GHashTable *timers;\n\nconst char *uiInit(uiInitOptions *o)\n{\n\tGError *err = NULL;\n\tconst char *msg;\n\n\tuiprivOptions = *o;\n\tif (gtk_init_with_args(NULL, NULL, NULL, NULL, NULL, &err) == FALSE) {\n\t\tmsg = g_strdup(err->message);\n\t\tg_error_free(err);\n\t\treturn msg;\n\t}\n\tuiprivInitAlloc();\n\tuiprivLoadFutures();\n\ttimers = g_hash_table_new(g_direct_hash, g_direct_equal);\n\treturn NULL;\n}\n\nstruct timer;\t\t// TODO get rid of forward declaration\n\nstatic void uninitTimer(gpointer key, gpointer value, gpointer data)\n{\n\tuiprivFree((struct timer *) key);\n}\n\nvoid uiUninit(void)\n{\n\tg_hash_table_foreach(timers, uninitTimer, NULL);\n\tg_hash_table_destroy(timers);\n\tuiprivUninitMenus();\n\tuiprivUninitAlloc();\n}\n\nvoid uiFreeInitError(const char *err)\n{\n\tg_free((gpointer) err);\n}\n\nstatic gboolean (*iteration)(gboolean) = NULL;\n\nvoid uiMain(void)\n{\n\titeration = gtk_main_iteration_do;\n\tgtk_main();\n}\n\nstatic gboolean stepsQuit = FALSE;\n\n// 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\n// gtk_main_iteration_do() will still run the main loop regardless\nstatic gboolean stepsIteration(gboolean block)\n{\n\tgtk_main_iteration_do(block);\n\treturn stepsQuit;\n}\n\nvoid uiMainSteps(void)\n{\n\titeration = stepsIteration;\n}\n\nint uiMainStep(int wait)\n{\n\tgboolean block;\n\n\tblock = FALSE;\n\tif (wait)\n\t\tblock = TRUE;\n\treturn (*iteration)(block) == FALSE;\n}\n\n// gtk_main_quit() may run immediately, or it may wait for other pending events; \"it depends\" (thanks mclasen in irc.gimp.net/#gtk+)\n// PostQuitMessage() on Windows always waits, so we must do so too\n// we'll do it by using an idle callback\nstatic gboolean quit(gpointer data)\n{\n\tif (iteration == stepsIteration)\n\t\tstepsQuit = TRUE;\n\t\t// 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\n\telse\n\t\tgtk_main_quit();\n\treturn FALSE;\n}\n\nvoid uiQuit(void)\n{\n\tgdk_threads_add_idle(quit, NULL);\n}\n\nstruct queued {\n\tvoid (*f)(void *);\n\tvoid *data;\n};\n\nstatic gboolean doqueued(gpointer data)\n{\n\tstruct queued *q = (struct queued *) data;\n\n\t(*(q->f))(q->data);\n\tg_free(q);\n\treturn FALSE;\n}\n\nvoid uiQueueMain(void (*f)(void *data), void *data)\n{\n\tstruct queued *q;\n\n\t// we have to use g_new0()/g_free() because uiprivAlloc() is only safe to call on the main thread\n\t// for some reason it didn't affect me, but it did affect krakjoe\n\tq = g_new0(struct queued, 1);\n\tq->f = f;\n\tq->data = data;\n\tgdk_threads_add_idle(doqueued, q);\n}\n\nstruct timer {\n\tint (*f)(void *);\n\tvoid *data;\n};\n\nstatic gboolean doTimer(gpointer data)\n{\n\tstruct timer *t = (struct timer *) data;\n\n\tif (!(*(t->f))(t->data)) {\n\t\tg_hash_table_remove(timers, t);\n\t\tuiprivFree(t);\n\t\treturn FALSE;\n\t}\n\treturn TRUE;\n}\n\nvoid uiTimer(int milliseconds, int (*f)(void *data), void *data)\n{\n\tstruct timer *t;\n\n\tt = uiprivNew(struct timer);\n\tt->f = f;\n\tt->data = data;\n\tg_timeout_add(milliseconds, doTimer, t);\n\tg_hash_table_add(timers, t);\n}\n"
  },
  {
    "path": "unix/menu.c",
    "content": "// 23 april 2015\n#include \"uipriv_unix.h\"\n\nstatic GArray *menus = NULL;\nstatic gboolean menusFinalized = FALSE;\nstatic gboolean hasQuit = FALSE;\nstatic gboolean hasPreferences = FALSE;\nstatic gboolean hasAbout = FALSE;\n\nstruct uiMenu {\n\tchar *name;\n\tGArray *items;\t\t\t\t\t// []*uiMenuItem\n};\n\nstruct uiMenuItem {\n\tchar *name;\n\tint type;\n\tvoid (*onClicked)(uiMenuItem *, uiWindow *, void *);\n\tvoid *onClickedData;\n\tGType gtype;\t\t\t\t\t// template for new instances; kept in sync with everything else\n\tgboolean disabled;\n\tgboolean checked;\n\tGHashTable *windows;\t\t\t// map[GtkMenuItem]*menuItemWindow\n};\n\nstruct menuItemWindow {\n\tuiWindow *w;\n\tgulong signal;\n};\n\nenum {\n\ttypeRegular,\n\ttypeCheckbox,\n\ttypeQuit,\n\ttypePreferences,\n\ttypeAbout,\n\ttypeSeparator,\n};\n\n// we do NOT want programmatic updates to raise an ::activated signal\nstatic void singleSetChecked(GtkCheckMenuItem *menuitem, gboolean checked, gulong signal)\n{\n\tg_signal_handler_block(menuitem, signal);\n\tgtk_check_menu_item_set_active(menuitem, checked);\n\tg_signal_handler_unblock(menuitem, signal);\n}\n\nstatic void setChecked(uiMenuItem *item, gboolean checked)\n{\n\tGHashTableIter iter;\n\tgpointer widget;\n\tgpointer ww;\n\tstruct menuItemWindow *w;\n\n\titem->checked = checked;\n\tg_hash_table_iter_init(&iter, item->windows);\n\twhile (g_hash_table_iter_next(&iter, &widget, &ww)) {\n\t\tw = (struct menuItemWindow *) ww;\n\t\tsingleSetChecked(GTK_CHECK_MENU_ITEM(widget), item->checked, w->signal);\n\t}\n}\n\nstatic void onClicked(GtkMenuItem *menuitem, gpointer data)\n{\n\tuiMenuItem *item = uiMenuItem(data);\n\tstruct menuItemWindow *w;\n\n\t// we need to manually update the checked states of all menu items if one changes\n\t// notice that this is getting the checked state of the menu item that this signal is sent from\n\tif (item->type == typeCheckbox)\n\t\tsetChecked(item, gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem)));\n\n\tw = (struct menuItemWindow *) g_hash_table_lookup(item->windows, menuitem);\n\t(*(item->onClicked))(item, w->w, item->onClickedData);\n}\n\nstatic void defaultOnClicked(uiMenuItem *item, uiWindow *w, void *data)\n{\n\t// do nothing\n}\n\nstatic void onQuitClicked(uiMenuItem *item, uiWindow *w, void *data)\n{\n\tif (uiprivShouldQuit())\n\t\tuiQuit();\n}\n\nstatic void menuItemEnableDisable(uiMenuItem *item, gboolean enabled)\n{\n\tGHashTableIter iter;\n\tgpointer widget;\n\n\titem->disabled = !enabled;\n\tg_hash_table_iter_init(&iter, item->windows);\n\twhile (g_hash_table_iter_next(&iter, &widget, NULL))\n\t\tgtk_widget_set_sensitive(GTK_WIDGET(widget), enabled);\n}\n\nvoid uiMenuItemEnable(uiMenuItem *item)\n{\n\tmenuItemEnableDisable(item, TRUE);\n}\n\nvoid uiMenuItemDisable(uiMenuItem *item)\n{\n\tmenuItemEnableDisable(item, FALSE);\n}\n\nvoid uiMenuItemOnClicked(uiMenuItem *item, void (*f)(uiMenuItem *, uiWindow *, void *), void *data)\n{\n\tif (item->type == typeQuit)\n\t\tuiprivUserBug(\"You cannot call uiMenuItemOnClicked() on a Quit item; use uiOnShouldQuit() instead.\");\n\titem->onClicked = f;\n\titem->onClickedData = data;\n}\n\nint uiMenuItemChecked(uiMenuItem *item)\n{\n\treturn item->checked != FALSE;\n}\n\nvoid uiMenuItemSetChecked(uiMenuItem *item, int checked)\n{\n\tgboolean c;\n\n\t// use explicit values\n\tc = FALSE;\n\tif (checked)\n\t\tc = TRUE;\n\tsetChecked(item, c);\n}\n\nstatic uiMenuItem *newItem(uiMenu *m, int type, const char *name)\n{\n\tuiMenuItem *item;\n\n\tif (menusFinalized)\n\t\tuiprivUserBug(\"You cannot create a new menu item after menus have been finalized.\");\n\n\titem = uiprivNew(uiMenuItem);\n\n\tg_array_append_val(m->items, item);\n\n\titem->type = type;\n\tswitch (item->type) {\n\tcase typeQuit:\n\t\titem->name = g_strdup(\"Quit\");\n\t\tbreak;\n\tcase typePreferences:\n\t\titem->name = g_strdup(\"Preferences...\");\n\t\tbreak;\n\tcase typeAbout:\n\t\titem->name = g_strdup(\"About\");\n\t\tbreak;\n\tcase typeSeparator:\n\t\tbreak;\n\tdefault:\n\t\titem->name = g_strdup(name);\n\t\tbreak;\n\t}\n\n\tif (item->type == typeQuit) {\n\t\t// can't call uiMenuItemOnClicked() here\n\t\titem->onClicked = onQuitClicked;\n\t\titem->onClickedData = NULL;\n\t} else\n\t\tuiMenuItemOnClicked(item, defaultOnClicked, NULL);\n\n\tswitch (item->type) {\n\tcase typeCheckbox:\n\t\titem->gtype = GTK_TYPE_CHECK_MENU_ITEM;\n\t\tbreak;\n\tcase typeSeparator:\n\t\titem->gtype = GTK_TYPE_SEPARATOR_MENU_ITEM;\n\t\tbreak;\n\tdefault:\n\t\titem->gtype = GTK_TYPE_MENU_ITEM;\n\t\tbreak;\n\t}\n\n\titem->windows = g_hash_table_new(g_direct_hash, g_direct_equal);\n\n\treturn item;\n}\n\nuiMenuItem *uiMenuAppendItem(uiMenu *m, const char *name)\n{\n\treturn newItem(m, typeRegular, name);\n}\n\nuiMenuItem *uiMenuAppendCheckItem(uiMenu *m, const char *name)\n{\n\treturn newItem(m, typeCheckbox, name);\n}\n\nuiMenuItem *uiMenuAppendQuitItem(uiMenu *m)\n{\n\tif (hasQuit)\n\t\tuiprivUserBug(\"You cannot have multiple Quit menu items in the same program.\");\n\thasQuit = TRUE;\n\tnewItem(m, typeSeparator, NULL);\n\treturn newItem(m, typeQuit, NULL);\n}\n\nuiMenuItem *uiMenuAppendPreferencesItem(uiMenu *m)\n{\n\tif (hasPreferences)\n\t\tuiprivUserBug(\"You cannot have multiple Preferences menu items in the same program.\");\n\thasPreferences = TRUE;\n\tnewItem(m, typeSeparator, NULL);\n\treturn newItem(m, typePreferences, NULL);\n}\n\nuiMenuItem *uiMenuAppendAboutItem(uiMenu *m)\n{\n\tif (hasAbout)\n\t\tuiprivUserBug(\"You cannot have multiple About menu items in the same program.\");\n\thasAbout = TRUE;\n\tnewItem(m, typeSeparator, NULL);\n\treturn newItem(m, typeAbout, NULL);\n}\n\nvoid uiMenuAppendSeparator(uiMenu *m)\n{\n\tnewItem(m, typeSeparator, NULL);\n}\n\nuiMenu *uiNewMenu(const char *name)\n{\n\tuiMenu *m;\n\n\tif (menusFinalized)\n\t\tuiprivUserBug(\"You cannot create a new menu after menus have been finalized.\");\n\tif (menus == NULL)\n\t\tmenus = g_array_new(FALSE, TRUE, sizeof (uiMenu *));\n\n\tm = uiprivNew(uiMenu);\n\n\tg_array_append_val(menus, m);\n\n\tm->name = g_strdup(name);\n\tm->items = g_array_new(FALSE, TRUE, sizeof (uiMenuItem *));\n\n\treturn m;\n}\n\nstatic void appendMenuItem(GtkMenuShell *submenu, uiMenuItem *item, uiWindow *w)\n{\n\tGtkWidget *menuitem;\n\tgulong signal;\n\tstruct menuItemWindow *ww;\n\n\tmenuitem = g_object_new(item->gtype, NULL);\n\tif (item->name != NULL)\n\t\tgtk_menu_item_set_label(GTK_MENU_ITEM(menuitem), item->name);\n\tif (item->type != typeSeparator) {\n\t\tsignal = g_signal_connect(menuitem, \"activate\", G_CALLBACK(onClicked), item);\n\t\tgtk_widget_set_sensitive(menuitem, !item->disabled);\n\t\tif (item->type == typeCheckbox)\n\t\t\tsingleSetChecked(GTK_CHECK_MENU_ITEM(menuitem), item->checked, signal);\n\t}\n\tgtk_menu_shell_append(submenu, menuitem);\n\tww = uiprivNew(struct menuItemWindow);\n\tww->w = w;\n\tww->signal = signal;\n\tg_hash_table_insert(item->windows, menuitem, ww);\n}\n\nGtkWidget *uiprivMakeMenubar(uiWindow *w)\n{\n\tGtkWidget *menubar;\n\tguint i, j;\n\tuiMenu *m;\n\tGtkWidget *menuitem;\n\tGtkWidget *submenu;\n\n\tmenusFinalized = TRUE;\n\n\tmenubar = gtk_menu_bar_new();\n\n\tif (menus != NULL)\n\t\tfor (i = 0; i < menus->len; i++) {\n\t\t\tm = g_array_index(menus, uiMenu *, i);\n\t\t\tmenuitem = gtk_menu_item_new_with_label(m->name);\n\t\t\tsubmenu = gtk_menu_new();\n\t\t\tgtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);\n\t\t\tfor (j = 0; j < m->items->len; j++)\n\t\t\t\tappendMenuItem(GTK_MENU_SHELL(submenu), g_array_index(m->items, uiMenuItem *, j), w);\n\t\t\tgtk_menu_shell_append(GTK_MENU_SHELL(menubar), menuitem);\n\t\t}\n\n\tgtk_widget_set_hexpand(menubar, TRUE);\n\tgtk_widget_set_halign(menubar, GTK_ALIGN_FILL);\n\treturn menubar;\n}\n\nstruct freeMenuItemData {\n\tGArray *items;\n\tguint i;\n};\n\nstatic void freeMenuItem(GtkWidget *widget, gpointer data)\n{\n\tstruct freeMenuItemData *fmi = (struct freeMenuItemData *) data;\n\tuiMenuItem *item;\n\tstruct menuItemWindow *w;\n\n\titem = g_array_index(fmi->items, uiMenuItem *, fmi->i);\n\tw = (struct menuItemWindow *) g_hash_table_lookup(item->windows, widget);\n\tif (g_hash_table_remove(item->windows, widget) == FALSE)\n\t\tuiprivImplBug(\"GtkMenuItem %p not in menu item's item/window map\", widget);\n\tuiprivFree(w);\n\tfmi->i++;\n}\n\nstatic void freeMenu(GtkWidget *widget, gpointer data)\n{\n\tguint *i = (guint *) data;\n\tuiMenu *m;\n\tGtkMenuItem *item;\n\tGtkWidget *submenu;\n\tstruct freeMenuItemData fmi;\n\n\tm = g_array_index(menus, uiMenu *, *i);\n\titem = GTK_MENU_ITEM(widget);\n\tsubmenu = gtk_menu_item_get_submenu(item);\n\tfmi.items = m->items;\n\tfmi.i = 0;\n\tgtk_container_foreach(GTK_CONTAINER(submenu), freeMenuItem, &fmi);\n\t(*i)++;\n}\n\nvoid uiprivFreeMenubar(GtkWidget *mb)\n{\n\tguint i;\n\n\ti = 0;\n\tgtk_container_foreach(GTK_CONTAINER(mb), freeMenu, &i);\n\t// no need to worry about destroying any widgets; destruction of the window they're in will do it for us\n}\n\nvoid uiprivUninitMenus(void)\n{\n\tuiMenu *m;\n\tuiMenuItem *item;\n\tguint i, j;\n\n\tif (menus == NULL)\n\t\treturn;\n\tfor (i = 0; i < menus->len; i++) {\n\t\tm = g_array_index(menus, uiMenu *, i);\n\t\tg_free(m->name);\n\t\tfor (j = 0; j < m->items->len; j++) {\n\t\t\titem = g_array_index(m->items, uiMenuItem *, j);\n\t\t\tif (g_hash_table_size(item->windows) != 0)\n\t\t\t\t// TODO is this really a uiprivUserBug()?\n\t\t\t\tuiprivImplBug(\"menu item %p (%s) still has uiWindows attached; did you forget to destroy some windows?\", item, item->name);\n\t\t\tg_free(item->name);\n\t\t\tg_hash_table_destroy(item->windows);\n\t\t\tuiprivFree(item);\n\t\t}\n\t\tg_array_free(m->items, TRUE);\n\t\tuiprivFree(m);\n\t}\n\tg_array_free(menus, TRUE);\n}\n"
  },
  {
    "path": "unix/meson.build",
    "content": "# 23 march 2019\n\nlibui_sources += [\n\t'unix/alloc.c',\n\t'unix/area.c',\n\t'unix/attrstr.c',\n\t'unix/box.c',\n\t'unix/button.c',\n\t'unix/cellrendererbutton.c',\n\t'unix/checkbox.c',\n\t'unix/child.c',\n\t'unix/colorbutton.c',\n\t'unix/combobox.c',\n\t'unix/control.c',\n\t'unix/datetimepicker.c',\n\t'unix/debug.c',\n\t'unix/draw.c',\n\t'unix/drawmatrix.c',\n\t'unix/drawpath.c',\n\t'unix/drawtext.c',\n\t'unix/editablecombo.c',\n\t'unix/entry.c',\n\t'unix/fontbutton.c',\n\t'unix/fontmatch.c',\n\t'unix/form.c',\n\t'unix/future.c',\n\t'unix/graphemes.c',\n\t'unix/grid.c',\n\t'unix/group.c',\n\t'unix/image.c',\n\t'unix/label.c',\n\t'unix/main.c',\n\t'unix/menu.c',\n\t'unix/multilineentry.c',\n\t'unix/opentype.c',\n\t'unix/progressbar.c',\n\t'unix/radiobuttons.c',\n\t'unix/separator.c',\n\t'unix/slider.c',\n\t'unix/spinbox.c',\n\t'unix/stddialogs.c',\n\t'unix/tab.c',\n\t'unix/table.c',\n\t'unix/tablemodel.c',\n\t'unix/text.c',\n\t'unix/util.c',\n\t'unix/window.c',\n]\n\nlibui_deps += [\n\tdependency('gtk+-3.0',\n\t\tversion: '>=3.10.0',\n\t\tmethod: 'pkg-config',\n\t\trequired: true),\n\t# We specify these as not required because some Unix systems include them with libc instead of providing them as separate files (thanks textshell and jpakkane in freenode #mesonbuild)\n\tmeson.get_compiler('c').find_library('m',\n\t\trequired: false),\n\tmeson.get_compiler('c').find_library('dl',\n\t\trequired: false),\n]\nlibui_soversion = '0'\nlibui_rpath = '$ORIGIN'\n"
  },
  {
    "path": "unix/multilineentry.c",
    "content": "// 6 december 2015\n#include \"uipriv_unix.h\"\n\n// TODO GTK_WRAP_WORD_CHAR to avoid spurious resizes?\n\nstruct uiMultilineEntry {\n\tuiUnixControl c;\n\tGtkWidget *widget;\n\tGtkContainer *scontainer;\n\tGtkScrolledWindow *sw;\n\tGtkWidget *textviewWidget;\n\tGtkTextView *textview;\n\tGtkTextBuffer *textbuf;\n\tvoid (*onChanged)(uiMultilineEntry *, void *);\n\tvoid *onChangedData;\n\tgulong onChangedSignal;\n};\n\nuiUnixControlAllDefaults(uiMultilineEntry)\n\nstatic void onChanged(GtkTextBuffer *textbuf, gpointer data)\n{\n\tuiMultilineEntry *e = uiMultilineEntry(data);\n\n\t(*(e->onChanged))(e, e->onChangedData);\n}\n\nstatic void defaultOnChanged(uiMultilineEntry *e, void *data)\n{\n\t// do nothing\n}\n\nchar *uiMultilineEntryText(uiMultilineEntry *e)\n{\n\tGtkTextIter start, end;\n\tchar *tret, *out;\n\n\tgtk_text_buffer_get_start_iter(e->textbuf, &start);\n\tgtk_text_buffer_get_end_iter(e->textbuf, &end);\n\ttret = gtk_text_buffer_get_text(e->textbuf, &start, &end, TRUE);\n\t// theoretically we could just return tret because uiUnixStrdupText() is just g_strdup(), but if that ever changes we can't, so let's do it this way to be safe\n\tout = uiUnixStrdupText(tret);\n\tg_free(tret);\n\treturn out;\n}\n\nvoid uiMultilineEntrySetText(uiMultilineEntry *e, const char *text)\n{\n\t// we need to inhibit sending of ::changed because this WILL send a ::changed otherwise\n\tg_signal_handler_block(e->textbuf, e->onChangedSignal);\n\tgtk_text_buffer_set_text(e->textbuf, text, -1);\n\tg_signal_handler_unblock(e->textbuf, e->onChangedSignal);\n}\n\n// TODO scroll to end?\nvoid uiMultilineEntryAppend(uiMultilineEntry *e, const char *text)\n{\n\tGtkTextIter end;\n\n\tgtk_text_buffer_get_end_iter(e->textbuf, &end);\n\t// we need to inhibit sending of ::changed because this WILL send a ::changed otherwise\n\tg_signal_handler_block(e->textbuf, e->onChangedSignal);\n\tgtk_text_buffer_insert(e->textbuf, &end, text, -1);\n\tg_signal_handler_unblock(e->textbuf, e->onChangedSignal);\n}\n\nvoid uiMultilineEntryOnChanged(uiMultilineEntry *e, void (*f)(uiMultilineEntry *e, void *data), void *data)\n{\n\te->onChanged = f;\n\te->onChangedData = data;\n}\n\nint uiMultilineEntryReadOnly(uiMultilineEntry *e)\n{\n\treturn gtk_text_view_get_editable(e->textview) == FALSE;\n}\n\nvoid uiMultilineEntrySetReadOnly(uiMultilineEntry *e, int readonly)\n{\n\tgboolean editable;\n\n\teditable = TRUE;\n\tif (readonly)\n\t\teditable = FALSE;\n\tgtk_text_view_set_editable(e->textview, editable);\n}\n\nstatic uiMultilineEntry *finishMultilineEntry(GtkPolicyType hpolicy, GtkWrapMode wrapMode)\n{\n\tuiMultilineEntry *e;\n\n\tuiUnixNewControl(uiMultilineEntry, e);\n\n\te->widget = gtk_scrolled_window_new(NULL, NULL);\n\te->scontainer = GTK_CONTAINER(e->widget);\n\te->sw = GTK_SCROLLED_WINDOW(e->widget);\n\tgtk_scrolled_window_set_policy(e->sw,\n\t\thpolicy,\n\t\tGTK_POLICY_AUTOMATIC);\n\tgtk_scrolled_window_set_shadow_type(e->sw, GTK_SHADOW_IN);\n\n\te->textviewWidget = gtk_text_view_new();\n\te->textview = GTK_TEXT_VIEW(e->textviewWidget);\n\tgtk_text_view_set_wrap_mode(e->textview, wrapMode);\n\n\tgtk_container_add(e->scontainer, e->textviewWidget);\n\t// and make the text view visible; only the scrolled window's visibility is controlled by libui\n\tgtk_widget_show(e->textviewWidget);\n\n\te->textbuf = gtk_text_view_get_buffer(e->textview);\n\n\te->onChangedSignal = g_signal_connect(e->textbuf, \"changed\", G_CALLBACK(onChanged), e);\n\tuiMultilineEntryOnChanged(e, defaultOnChanged, NULL);\n\n\treturn e;\n}\n\nuiMultilineEntry *uiNewMultilineEntry(void)\n{\n\treturn finishMultilineEntry(GTK_POLICY_NEVER, GTK_WRAP_WORD);\n}\n\nuiMultilineEntry *uiNewNonWrappingMultilineEntry(void)\n{\n\treturn finishMultilineEntry(GTK_POLICY_AUTOMATIC, GTK_WRAP_NONE);\n}\n"
  },
  {
    "path": "unix/opentype.c",
    "content": "// 11 may 2017\n#include \"uipriv_unix.h\"\n#include \"attrstr.h\"\n\n// see https://developer.mozilla.org/en/docs/Web/CSS/font-feature-settings\nstatic uiForEach toCSS(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value, void *data)\n{\n\tGString *s = (GString *) data;\n\n\t// the last trailing comma is removed after foreach is done\n\tg_string_append_printf(s, \"\\\"%c%c%c%c\\\" %\" PRIu32 \", \",\n\t\ta, b, c, d, value);\n\treturn uiForEachContinue;\n}\n\nGString *uiprivOpenTypeFeaturesToPangoCSSFeaturesString(const uiOpenTypeFeatures *otf)\n{\n\tGString *s;\n\n\ts = g_string_new(\"\");\n\tuiOpenTypeFeaturesForEach(otf, toCSS, s);\n\tif (s->len != 0)\n\t\t// and remove the last comma\n\t\tg_string_truncate(s, s->len - 2);\n\treturn s;\n}\n"
  },
  {
    "path": "unix/progressbar.c",
    "content": "// 11 june 2015\n#include \"uipriv_unix.h\"\n\n// LONGTERM:\n// - 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\n\nstruct uiProgressBar {\n\tuiUnixControl c;\n\tGtkWidget *widget;\n\tGtkProgressBar *pbar;\n\tgboolean indeterminate;\n\tguint pulser;\n};\n\nuiUnixControlAllDefaultsExceptDestroy(uiProgressBar)\n\nstatic void uiProgressBarDestroy(uiControl *c)\n{\n\tuiProgressBar *p = uiProgressBar(c);\n\n\t// be sure to stop the timeout now\n\tif (p->indeterminate)\n\t\tg_source_remove(p->pulser);\n\tg_object_unref(p->widget);\n\tuiFreeControl(uiControl(p));\n}\n\nint uiProgressBarValue(uiProgressBar *p)\n{\n\tif (p->indeterminate)\n\t\treturn -1;\n\treturn (int) (gtk_progress_bar_get_fraction(p->pbar) * 100);\n}\n\nstatic gboolean pulse(void* data)\n{\n\tuiProgressBar *p = uiProgressBar(data);\n\n\tgtk_progress_bar_pulse(p->pbar);\n\treturn TRUE;\n}\n\nvoid uiProgressBarSetValue(uiProgressBar *p, int value)\n{\n\tif (value == -1) {\n\t\tif (!p->indeterminate) {\n\t\t\tp->indeterminate = TRUE;\n\t\t\t// TODO verify the timeout\n\t\t\tp->pulser = g_timeout_add(100, pulse, p);\n\t\t}\n\t\treturn;\n\t}\n\tif (p->indeterminate) {\n\t\tp->indeterminate = FALSE;\n\t\tg_source_remove(p->pulser);\n\t}\n\n\tif (value < 0 || value > 100)\n\t\tuiprivUserBug(\"Value %d is out of range for a uiProgressBar.\", value);\n\n\tgtk_progress_bar_set_fraction(p->pbar, ((gdouble) value) / 100);\n}\n\nuiProgressBar *uiNewProgressBar(void)\n{\n\tuiProgressBar *p;\n\n\tuiUnixNewControl(uiProgressBar, p);\n\n\tp->widget = gtk_progress_bar_new();\n\tp->pbar = GTK_PROGRESS_BAR(p->widget);\n\n\treturn p;\n}\n"
  },
  {
    "path": "unix/radiobuttons.c",
    "content": "// 11 june 2015\n#include \"uipriv_unix.h\"\n\n// on GTK+ a uiRadioButtons is a GtkBox with each of the GtkRadioButtons as children\n\nstruct uiRadioButtons {\n\tuiUnixControl c;\n\tGtkWidget *widget;\n\tGtkContainer *container;\n\tGtkBox *box;\n\tGPtrArray *buttons;\n\tvoid (*onSelected)(uiRadioButtons *, void *);\n\tvoid *onSelectedData;\n\tgboolean changing;\n};\n\nuiUnixControlAllDefaultsExceptDestroy(uiRadioButtons)\n\nstatic void defaultOnSelected(uiRadioButtons *r, void *data)\n{\n\t// do nothing\n}\n\nstatic void onToggled(GtkToggleButton *tb, gpointer data)\n{\n\tuiRadioButtons *r = uiRadioButtons(data);\n\n\t// only care if a button is selected\n\tif (!gtk_toggle_button_get_active(tb))\n\t\treturn;\n\t// ignore programmatic changes\n\tif (r->changing)\n\t\treturn;\n\t(*(r->onSelected))(r, r->onSelectedData);\n}\n\nstatic void uiRadioButtonsDestroy(uiControl *c)\n{\n\tuiRadioButtons *r = uiRadioButtons(c);\n\tGtkWidget *b;\n\n\twhile (r->buttons->len != 0) {\n\t\tb = GTK_WIDGET(g_ptr_array_remove_index(r->buttons, 0));\n\t\tgtk_widget_destroy(b);\n\t}\n\tg_ptr_array_free(r->buttons, TRUE);\n\t// and free ourselves\n\tg_object_unref(r->widget);\n\tuiFreeControl(uiControl(r));\n}\n\nvoid uiRadioButtonsAppend(uiRadioButtons *r, const char *text)\n{\n\tGtkWidget *rb;\n\tGtkRadioButton *previous;\n\n\tprevious = NULL;\n\tif (r->buttons->len > 0)\n\t\tprevious = GTK_RADIO_BUTTON(g_ptr_array_index(r->buttons, 0));\n\trb = gtk_radio_button_new_with_label_from_widget(previous, text);\n\tg_signal_connect(rb, \"toggled\", G_CALLBACK(onToggled), r);\n\tgtk_container_add(r->container, rb);\n\tg_ptr_array_add(r->buttons, rb);\n\tgtk_widget_show(rb);\n}\n\nint uiRadioButtonsSelected(uiRadioButtons *r)\n{\n\tGtkToggleButton *tb;\n\tguint i;\n\n\tfor (i = 0; i < r->buttons->len; i++) {\n\t\ttb = GTK_TOGGLE_BUTTON(g_ptr_array_index(r->buttons, i));\n\t\tif (gtk_toggle_button_get_active(tb))\n\t\t\treturn i;\n\t}\n\treturn -1;\n}\n\nvoid uiRadioButtonsSetSelected(uiRadioButtons *r, int n)\n{\n\tGtkToggleButton *tb;\n\tgboolean active;\n\n\tactive = TRUE;\n\t// TODO this doesn't work\n\tif (n == -1) {\n\t\tn = uiRadioButtonsSelected(r);\n\t\tif (n == -1)\t\t// no selection; keep it that way\n\t\t\treturn;\n\t\tactive = FALSE;\n\t}\n\ttb = GTK_TOGGLE_BUTTON(g_ptr_array_index(r->buttons, n));\n\t// this is easier than remembering all the signals\n\tr->changing = TRUE;\n\tgtk_toggle_button_set_active(tb, active);\n\tr->changing = FALSE;\n}\n\nvoid uiRadioButtonsOnSelected(uiRadioButtons *r, void (*f)(uiRadioButtons *, void *), void *data)\n{\n\tr->onSelected = f;\n\tr->onSelectedData = data;\n}\n\nuiRadioButtons *uiNewRadioButtons(void)\n{\n\tuiRadioButtons *r;\n\n\tuiUnixNewControl(uiRadioButtons, r);\n\n\tr->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);\n\tr->container = GTK_CONTAINER(r->widget);\n\tr->box = GTK_BOX(r->widget);\n\n\tr->buttons = g_ptr_array_new();\n\n\tuiRadioButtonsOnSelected(r, defaultOnSelected, NULL);\n\n\treturn r;\n}\n"
  },
  {
    "path": "unix/separator.c",
    "content": "// 11 june 2015\n#include \"uipriv_unix.h\"\n\nstruct uiSeparator {\n\tuiUnixControl c;\n\tGtkWidget *widget;\n\tGtkSeparator *separator;\n};\n\nuiUnixControlAllDefaults(uiSeparator)\n\nuiSeparator *uiNewHorizontalSeparator(void)\n{\n\tuiSeparator *s;\n\n\tuiUnixNewControl(uiSeparator, s);\n\n\ts->widget = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);\n\ts->separator = GTK_SEPARATOR(s->widget);\n\n\treturn s;\n}\n\nuiSeparator *uiNewVerticalSeparator(void)\n{\n\tuiSeparator *s;\n\n\tuiUnixNewControl(uiSeparator, s);\n\n\ts->widget = gtk_separator_new(GTK_ORIENTATION_VERTICAL);\n\ts->separator = GTK_SEPARATOR(s->widget);\n\n\treturn s;\n}\n"
  },
  {
    "path": "unix/slider.c",
    "content": "// 11 june 2015\n#include \"uipriv_unix.h\"\n\nstruct uiSlider {\n\tuiUnixControl c;\n\tGtkWidget *widget;\n\tGtkRange *range;\n\tGtkScale *scale;\n\tvoid (*onChanged)(uiSlider *, void *);\n\tvoid *onChangedData;\n\tgulong onChangedSignal;\n};\n\nuiUnixControlAllDefaults(uiSlider)\n\nstatic void onChanged(GtkRange *range, gpointer data)\n{\n\tuiSlider *s = uiSlider(data);\n\n\t(*(s->onChanged))(s, s->onChangedData);\n}\n\nstatic void defaultOnChanged(uiSlider *s, void *data)\n{\n\t// do nothing\n}\n\nint uiSliderValue(uiSlider *s)\n{\n\treturn gtk_range_get_value(s->range);\n}\n\nvoid uiSliderSetValue(uiSlider *s, int value)\n{\n\t// we need to inhibit sending of ::value-changed because this WILL send a ::value-changed otherwise\n\tg_signal_handler_block(s->range, s->onChangedSignal);\n\tgtk_range_set_value(s->range, value);\n\tg_signal_handler_unblock(s->range, s->onChangedSignal);\n}\n\nvoid uiSliderOnChanged(uiSlider *s, void (*f)(uiSlider *, void *), void *data)\n{\n\ts->onChanged = f;\n\ts->onChangedData = data;\n}\n\nuiSlider *uiNewSlider(int min, int max)\n{\n\tuiSlider *s;\n\tint temp;\n\n\tif (min >= max) {\n\t\ttemp = min;\n\t\tmin = max;\n\t\tmax = temp;\n\t}\n\n\tuiUnixNewControl(uiSlider, s);\n\n\ts->widget = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL, min, max, 1);\n\ts->range = GTK_RANGE(s->widget);\n\ts->scale = GTK_SCALE(s->widget);\n\n\t// ensure integers, just to be safe\n\tgtk_scale_set_digits(s->scale, 0);\n\n\ts->onChangedSignal = g_signal_connect(s->scale, \"value-changed\", G_CALLBACK(onChanged), s);\n\tuiSliderOnChanged(s, defaultOnChanged, NULL);\n\n\treturn s;\n}\n"
  },
  {
    "path": "unix/spinbox.c",
    "content": "// 11 june 2015\n#include \"uipriv_unix.h\"\n\nstruct uiSpinbox {\n\tuiUnixControl c;\n\tGtkWidget *widget;\n\tGtkEntry *entry;\n\tGtkSpinButton *spinButton;\n\tvoid (*onChanged)(uiSpinbox *, void *);\n\tvoid *onChangedData;\n\tgulong onChangedSignal;\n};\n\nuiUnixControlAllDefaults(uiSpinbox)\n\nstatic void onChanged(GtkSpinButton *sb, gpointer data)\n{\n\tuiSpinbox *s = uiSpinbox(data);\n\n\t(*(s->onChanged))(s, s->onChangedData);\n}\n\nstatic void defaultOnChanged(uiSpinbox *s, void *data)\n{\n\t// do nothing\n}\n\nint uiSpinboxValue(uiSpinbox *s)\n{\n\treturn gtk_spin_button_get_value(s->spinButton);\n}\n\nvoid uiSpinboxSetValue(uiSpinbox *s, int value)\n{\n\t// we need to inhibit sending of ::value-changed because this WILL send a ::value-changed otherwise\n\tg_signal_handler_block(s->spinButton, s->onChangedSignal);\n\t// this clamps for us\n\tgtk_spin_button_set_value(s->spinButton, (gdouble) value);\n\tg_signal_handler_unblock(s->spinButton, s->onChangedSignal);\n}\n\nvoid uiSpinboxOnChanged(uiSpinbox *s, void (*f)(uiSpinbox *, void *), void *data)\n{\n\ts->onChanged = f;\n\ts->onChangedData = data;\n}\n\nuiSpinbox *uiNewSpinbox(int min, int max)\n{\n\tuiSpinbox *s;\n\tint temp;\n\n\tif (min >= max) {\n\t\ttemp = min;\n\t\tmin = max;\n\t\tmax = temp;\n\t}\n\n\tuiUnixNewControl(uiSpinbox, s);\n\n\ts->widget = gtk_spin_button_new_with_range(min, max, 1);\n\ts->entry = GTK_ENTRY(s->widget);\n\ts->spinButton = GTK_SPIN_BUTTON(s->widget);\n\n\t// ensure integers, just to be safe\n\tgtk_spin_button_set_digits(s->spinButton, 0);\n\n\ts->onChangedSignal = g_signal_connect(s->spinButton, \"value-changed\", G_CALLBACK(onChanged), s);\n\tuiSpinboxOnChanged(s, defaultOnChanged, NULL);\n\n\treturn s;\n}\n"
  },
  {
    "path": "unix/stddialogs.c",
    "content": "// 26 june 2015\n#include \"uipriv_unix.h\"\n\n// LONGTERM figure out why, and describe, that this is the desired behavior\n// LONGTERM also point out that font and color buttons also work like this\n\n#define windowWindow(w) (GTK_WINDOW(uiControlHandle(uiControl(w))))\n\nstatic char *filedialog(GtkWindow *parent, GtkFileChooserAction mode, const gchar *confirm)\n{\n\tGtkWidget *fcd;\n\tGtkFileChooser *fc;\n\tgint response;\n\tchar *filename;\n\n\tfcd = gtk_file_chooser_dialog_new(NULL, parent, mode,\n\t\t\"_Cancel\", GTK_RESPONSE_CANCEL,\n\t\tconfirm, GTK_RESPONSE_ACCEPT,\n\t\tNULL);\n\tfc = GTK_FILE_CHOOSER(fcd);\n\tgtk_file_chooser_set_local_only(fc, FALSE);\n\tgtk_file_chooser_set_select_multiple(fc, FALSE);\n\tgtk_file_chooser_set_show_hidden(fc, TRUE);\n\tgtk_file_chooser_set_do_overwrite_confirmation(fc, TRUE);\n\tgtk_file_chooser_set_create_folders(fc, TRUE);\n\tresponse = gtk_dialog_run(GTK_DIALOG(fcd));\n\tif (response != GTK_RESPONSE_ACCEPT) {\n\t\tgtk_widget_destroy(fcd);\n\t\treturn NULL;\n\t}\n\tfilename = uiUnixStrdupText(gtk_file_chooser_get_filename(fc));\n\tgtk_widget_destroy(fcd);\n\treturn filename;\n}\n\nchar *uiOpenFile(uiWindow *parent)\n{\n\treturn filedialog(windowWindow(parent), GTK_FILE_CHOOSER_ACTION_OPEN, \"_Open\");\n}\n\nchar *uiSaveFile(uiWindow *parent)\n{\n\treturn filedialog(windowWindow(parent), GTK_FILE_CHOOSER_ACTION_SAVE, \"_Save\");\n}\n\nstatic void msgbox(GtkWindow *parent, const char *title, const char *description, GtkMessageType type, GtkButtonsType buttons)\n{\n\tGtkWidget *md;\n\n\tmd = gtk_message_dialog_new(parent, GTK_DIALOG_MODAL,\n\t\ttype, buttons,\n\t\t\"%s\", title);\n\tgtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(md), \"%s\", description);\n\tgtk_dialog_run(GTK_DIALOG(md));\n\tgtk_widget_destroy(md);\n}\n\nvoid uiMsgBox(uiWindow *parent, const char *title, const char *description)\n{\n\tmsgbox(windowWindow(parent), title, description, GTK_MESSAGE_OTHER, GTK_BUTTONS_OK);\n}\n\nvoid uiMsgBoxError(uiWindow *parent, const char *title, const char *description)\n{\n\tmsgbox(windowWindow(parent), title, description, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK);\n}\n"
  },
  {
    "path": "unix/tab.c",
    "content": "// 11 june 2015\n#include \"uipriv_unix.h\"\n\nstruct uiTab {\n\tuiUnixControl c;\n\n\tGtkWidget *widget;\n\tGtkContainer *container;\n\tGtkNotebook *notebook;\n\n\tGArray *pages;\t\t\t\t// []*uiprivChild\n};\n\nuiUnixControlAllDefaultsExceptDestroy(uiTab)\n\nstatic void uiTabDestroy(uiControl *c)\n{\n\tuiTab *t = uiTab(c);\n\tguint i;\n\tuiprivChild *page;\n\n\tfor (i = 0; i < t->pages->len; i++) {\n\t\tpage = g_array_index(t->pages, uiprivChild *, i);\n\t\tuiprivChildDestroy(page);\n\t}\n\tg_array_free(t->pages, TRUE);\n\t// and free ourselves\n\tg_object_unref(t->widget);\n\tuiFreeControl(uiControl(t));\n}\n\nvoid uiTabAppend(uiTab *t, const char *name, uiControl *child)\n{\n\tuiTabInsertAt(t, name, t->pages->len, child);\n}\n\nvoid uiTabInsertAt(uiTab *t, const char *name, int n, uiControl *child)\n{\n\tuiprivChild *page;\n\n\t// this will create a tab, because of gtk_container_add()\n\tpage = uiprivNewChildWithBox(child, uiControl(t), t->container, 0);\n\n\tgtk_notebook_set_tab_label_text(t->notebook, uiprivChildBox(page), name);\n\tgtk_notebook_reorder_child(t->notebook, uiprivChildBox(page), n);\n\n\tg_array_insert_val(t->pages, n, page);\n}\n\nvoid uiTabDelete(uiTab *t, int n)\n{\n\tuiprivChild *page;\n\n\tpage = g_array_index(t->pages, uiprivChild *, n);\n\t// this will remove the tab, because gtk_widget_destroy() calls gtk_container_remove()\n\tuiprivChildRemove(page);\n\tg_array_remove_index(t->pages, n);\n}\n\nint uiTabNumPages(uiTab *t)\n{\n\treturn t->pages->len;\n}\n\nint uiTabMargined(uiTab *t, int n)\n{\n\tuiprivChild *page;\n\n\tpage = g_array_index(t->pages, uiprivChild *, n);\n\treturn uiprivChildFlag(page);\n}\n\nvoid uiTabSetMargined(uiTab *t, int n, int margined)\n{\n\tuiprivChild *page;\n\n\tpage = g_array_index(t->pages, uiprivChild *, n);\n\tuiprivChildSetFlag(page, margined);\n\tuiprivChildSetMargined(page, uiprivChildFlag(page));\n}\n\nuiTab *uiNewTab(void)\n{\n\tuiTab *t;\n\n\tuiUnixNewControl(uiTab, t);\n\n\tt->widget = gtk_notebook_new();\n\tt->container = GTK_CONTAINER(t->widget);\n\tt->notebook = GTK_NOTEBOOK(t->widget);\n\n\tgtk_notebook_set_scrollable(t->notebook, TRUE);\n\n\tt->pages = g_array_new(FALSE, TRUE, sizeof (uiprivChild *));\n\n\treturn t;\n}\n"
  },
  {
    "path": "unix/table.c",
    "content": "// 26 june 2016\n#include \"uipriv_unix.h\"\n#include \"table.h\"\n\n// TODO with GDK_SCALE set to 2 the 32x32 images are scaled up to 64x64?\n\nstruct uiTable {\n\tuiUnixControl c;\n\tGtkWidget *widget;\n\tGtkContainer *scontainer;\n\tGtkScrolledWindow *sw;\n\tGtkWidget *treeWidget;\n\tGtkTreeView *tv;\n\tuiTableModel *model;\n\tGPtrArray *columnParams;\n\tint backgroundColumn;\n\t// keys are struct rowcol, values are gint\n\t// TODO document this properly\n\tGHashTable *indeterminatePositions;\n\tguint indeterminateTimer;\n};\n\n// use the same size as GtkFileChooserWidget's treeview\n// TODO refresh when icon theme changes\n// TODO doesn't work when scaled?\n// TODO is this even necessary?\nstatic void setImageSize(GtkCellRenderer *r)\n{\n\tgint size;\n\tgint width, height;\n\tgint xpad, ypad;\n\n\tsize = 16;\t\t// fallback used by GtkFileChooserWidget\n\tif (gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height) != FALSE)\n\t\tsize = MAX(width, height);\n\tgtk_cell_renderer_get_padding(r, &xpad, &ypad);\n\tgtk_cell_renderer_set_fixed_size(r,\n\t\t2 * xpad + size,\n\t\t2 * ypad + size);\n}\n\nstatic void applyColor(GtkTreeModel *m, GtkTreeIter *iter, int modelColumn, GtkCellRenderer *r, const char *prop, const char *propSet)\n{\n\tGValue value = G_VALUE_INIT;\n\tGdkRGBA *rgba;\n\n\tgtk_tree_model_get_value(m, iter, modelColumn, &value);\n\trgba = (GdkRGBA *) g_value_get_boxed(&value);\n\tif (rgba != NULL)\n\t\tg_object_set(r, prop, rgba, NULL);\n\telse\n\t\tg_object_set(r, propSet, FALSE, NULL);\n\tg_value_unset(&value);\n}\n\nstatic void setEditable(uiTableModel *m, GtkTreeIter *iter, int modelColumn, GtkCellRenderer *r, const char *prop)\n{\n\tGtkTreePath *path;\n\tint row;\n\tgboolean editable;\n\n\t// TODO avoid the need for this\n\tpath = gtk_tree_model_get_path(GTK_TREE_MODEL(m), iter);\n\trow = gtk_tree_path_get_indices(path)[0];\n\tgtk_tree_path_free(path);\n\teditable = uiprivTableModelCellEditable(m, row, modelColumn) != 0;\n\tg_object_set(r, prop, editable, NULL);\n}\n\nstatic void applyBackgroundColor(uiTable *t, GtkTreeModel *m, GtkTreeIter *iter, GtkCellRenderer *r)\n{\n\tif (t->backgroundColumn != -1)\n\t\tapplyColor(m, iter, t->backgroundColumn,\n\t\t\tr, \"cell-background-rgba\", \"cell-background-set\");\n}\n\nstatic void onEdited(uiTableModel *m, int column, const char *pathstr, const uiTableValue *tvalue, GtkTreeIter *iter)\n{\n\tGtkTreePath *path;\n\tint row;\n\n\tpath = gtk_tree_path_new_from_string(pathstr);\n\trow = gtk_tree_path_get_indices(path)[0];\n\tif (iter != NULL)\n\t\tgtk_tree_model_get_iter(GTK_TREE_MODEL(m), iter, path);\n\tgtk_tree_path_free(path);\n\tuiprivTableModelSetCellValue(m, row, column, tvalue);\n}\n\nstruct textColumnParams {\n\tuiTable *t;\n\tuiTableModel *m;\n\tint modelColumn;\n\tint editableColumn;\n\tuiTableTextColumnOptionalParams params;\n};\n\nstatic void textColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data)\n{\n\tstruct textColumnParams *p = (struct textColumnParams *) data;\n\tGValue value = G_VALUE_INIT;\n\tconst gchar *str;\n\n\tgtk_tree_model_get_value(m, iter, p->modelColumn, &value);\n\tstr = g_value_get_string(&value);\n\tg_object_set(r, \"text\", str, NULL);\n\tg_value_unset(&value);\n\n\tsetEditable(p->m, iter, p->editableColumn, r, \"editable\");\n\n\tif (p->params.ColorModelColumn != -1)\n\t\tapplyColor(m, iter, p->params.ColorModelColumn,\n\t\t\tr, \"foreground-rgba\", \"foreground-set\");\n\n\tapplyBackgroundColor(p->t, m, iter, r);\n}\n\nstatic void textColumnEdited(GtkCellRendererText *r, gchar *path, gchar *newText, gpointer data)\n{\n\tstruct textColumnParams *p = (struct textColumnParams *) data;\n\tuiTableValue *tvalue;\n\tGtkTreeIter iter;\n\n\ttvalue = uiNewTableValueString(newText);\n\tonEdited(p->m, p->modelColumn, path, tvalue, &iter);\n\tuiFreeTableValue(tvalue);\n\t// and update the column TODO copy comment here\n\ttextColumnDataFunc(NULL, GTK_CELL_RENDERER(r), GTK_TREE_MODEL(p->m), &iter, data);\n}\n\nstruct imageColumnParams {\n\tuiTable *t;\n\tint modelColumn;\n};\n\nstatic void imageColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data)\n{\n\tstruct imageColumnParams *p = (struct imageColumnParams *) data;\n\tGValue value = G_VALUE_INIT;\n\tuiImage *img;\n\n//TODO\tsetImageSize(r);\n\tgtk_tree_model_get_value(m, iter, p->modelColumn, &value);\n\timg = (uiImage *) g_value_get_pointer(&value);\n\tg_object_set(r, \"surface\",\n\t\tuiprivImageAppropriateSurface(img, p->t->treeWidget),\n\t\tNULL);\n\tg_value_unset(&value);\n\n\tapplyBackgroundColor(p->t, m, iter, r);\n}\n\nstruct checkboxColumnParams {\n\tuiTable *t;\n\tuiTableModel *m;\n\tint modelColumn;\n\tint editableColumn;\n};\n\nstatic void checkboxColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data)\n{\n\tstruct checkboxColumnParams *p = (struct checkboxColumnParams *) data;\n\tGValue value = G_VALUE_INIT;\n\tgboolean active;\n\n\tgtk_tree_model_get_value(m, iter, p->modelColumn, &value);\n\tactive = g_value_get_int(&value) != 0;\n\tg_object_set(r, \"active\", active, NULL);\n\tg_value_unset(&value);\n\n\tsetEditable(p->m, iter, p->editableColumn, r, \"activatable\");\n\n\tapplyBackgroundColor(p->t, m, iter, r);\n}\n\nstatic void checkboxColumnToggled(GtkCellRendererToggle *r, gchar *pathstr, gpointer data)\n{\n\tstruct checkboxColumnParams *p = (struct checkboxColumnParams *) data;\n\tGValue value = G_VALUE_INIT;\n\tint v;\n\tuiTableValue *tvalue;\n\tGtkTreePath *path;\n\tGtkTreeIter iter;\n\n\tpath = gtk_tree_path_new_from_string(pathstr);\n\tgtk_tree_model_get_iter(GTK_TREE_MODEL(p->m), &iter, path);\n\tgtk_tree_path_free(path);\n\tgtk_tree_model_get_value(GTK_TREE_MODEL(p->m), &iter, p->modelColumn, &value);\n\tv = g_value_get_int(&value);\n\tg_value_unset(&value);\n\ttvalue = uiNewTableValueInt(!v);\n\tonEdited(p->m, p->modelColumn, pathstr, tvalue, NULL);\n\tuiFreeTableValue(tvalue);\n\t// and update the column TODO copy comment here\n\t// TODO avoid fetching the model data twice\n\tcheckboxColumnDataFunc(NULL, GTK_CELL_RENDERER(r), GTK_TREE_MODEL(p->m), &iter, data);\n}\n\nstruct progressBarColumnParams {\n\tuiTable *t;\n\tint modelColumn;\n};\n\nstruct rowcol {\n\tint row;\n\tint col;\n};\n\nstatic guint rowcolHash(gconstpointer key)\n{\n\tconst struct rowcol *rc = (const struct rowcol *) key;\n\tguint row, col;\n\n\trow = (guint) (rc->row);\n\tcol = (guint) (rc->col);\n\treturn row ^ col;\n}\n\nstatic gboolean rowcolEqual(gconstpointer a, gconstpointer b)\n{\n\tconst struct rowcol *ra = (const struct rowcol *) a;\n\tconst struct rowcol *rb = (const struct rowcol *) b;\n\n\treturn (ra->row == rb->row) && (ra->col == rb->col);\n}\n\nstatic void pulseOne(gpointer key, gpointer value, gpointer data)\n{\n\tuiTable *t = uiTable(data);\n\tstruct rowcol *rc = (struct rowcol *) key;\n\n\t// 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\n\t// however, a proper fix would require decoupling progress from normal integers, which we could do...\n\tuiTableModelRowChanged(t->model, rc->row);\n}\n\nstatic gboolean indeterminatePulse(gpointer data)\n{\n\tuiTable *t = uiTable(data);\n\n\tg_hash_table_foreach(t->indeterminatePositions, pulseOne, t);\n\treturn TRUE;\n}\n\nstatic void progressBarColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data)\n{\n\tstruct progressBarColumnParams *p = (struct progressBarColumnParams *) data;\n\tGValue value = G_VALUE_INIT;\n\tint pval;\n\tstruct rowcol *rc;\n\tgint *val;\n\tGtkTreePath *path;\n\n\tgtk_tree_model_get_value(m, iter, p->modelColumn, &value);\n\tpval = g_value_get_int(&value);\n\trc = uiprivNew(struct rowcol);\n\t// TODO avoid the need for this\n\tpath = gtk_tree_model_get_path(GTK_TREE_MODEL(m), iter);\n\trc->row = gtk_tree_path_get_indices(path)[0];\n\tgtk_tree_path_free(path);\n\trc->col = p->modelColumn;\n\tval = (gint *) g_hash_table_lookup(p->t->indeterminatePositions, rc);\n\tif (pval == -1) {\n\t\tif (val == NULL) {\n\t\t\tval = uiprivNew(gint);\n\t\t\t*val = 1;\n\t\t\tg_hash_table_insert(p->t->indeterminatePositions, rc, val);\n\t\t} else {\n\t\t\tuiprivFree(rc);\n\t\t\t(*val)++;\n\t\t\tif (*val == G_MAXINT)\n\t\t\t\t*val = 1;\n\t\t}\n\t\tg_object_set(r,\n\t\t\t\"pulse\", *val,\n\t\t\tNULL);\n\t\tif (p->t->indeterminateTimer == 0)\n\t\t\t// TODO verify the timeout\n\t\t\tp->t->indeterminateTimer = g_timeout_add(100, indeterminatePulse, p->t);\n\t} else {\n\t\tif (val != NULL) {\n\t\t\tg_hash_table_remove(p->t->indeterminatePositions, rc);\n\t\t\tif (g_hash_table_size(p->t->indeterminatePositions) == 0) {\n\t\t\t\tg_source_remove(p->t->indeterminateTimer);\n\t\t\t\tp->t->indeterminateTimer = 0;\n\t\t\t}\n\t\t}\n\t\tuiprivFree(rc);\n\t\tg_object_set(r,\n\t\t\t\"pulse\", -1,\n\t\t\t\"value\", pval,\n\t\t\tNULL);\n\t}\n\tg_value_unset(&value);\n\n\tapplyBackgroundColor(p->t, m, iter, r);\n}\n\nstruct buttonColumnParams {\n\tuiTable *t;\n\tuiTableModel *m;\n\tint modelColumn;\n\tint clickableColumn;\n};\n\nstatic void buttonColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data)\n{\n\tstruct buttonColumnParams *p = (struct buttonColumnParams *) data;\n\tGValue value = G_VALUE_INIT;\n\tconst gchar *str;\n\n\tgtk_tree_model_get_value(m, iter, p->modelColumn, &value);\n\tstr = g_value_get_string(&value);\n\tg_object_set(r, \"text\", str, NULL);\n\tg_value_unset(&value);\n\n\tsetEditable(p->m, iter, p->clickableColumn, r, \"sensitive\");\n\n\tapplyBackgroundColor(p->t, m, iter, r);\n}\n\n// TODO wrong type here\nstatic void buttonColumnClicked(GtkCellRenderer *r, gchar *pathstr, gpointer data)\n{\n\tstruct buttonColumnParams *p = (struct buttonColumnParams *) data;\n\n\tonEdited(p->m, p->modelColumn, pathstr, NULL, NULL);\n}\n\nstatic GtkTreeViewColumn *addColumn(uiTable *t, const char *name)\n{\n\tGtkTreeViewColumn *c;\n\n\tc = gtk_tree_view_column_new();\n\tgtk_tree_view_column_set_resizable(c, TRUE);\n\tgtk_tree_view_column_set_title(c, name);\n\tgtk_tree_view_append_column(t->tv, c);\n\treturn c;\n}\n\nstatic void addTextColumn(uiTable *t, GtkTreeViewColumn *c, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams)\n{\n\tstruct textColumnParams *p;\n\tGtkCellRenderer *r;\n\n\tp = uiprivNew(struct textColumnParams);\n\tp->t = t;\n\t// TODO get rid of these fields AND rename t->model in favor of t->m\n\tp->m = t->model;\n\tp->modelColumn = textModelColumn;\n\tp->editableColumn = textEditableModelColumn;\n\tif (textParams != NULL)\n\t\tp->params = *textParams;\n\telse\n\t\tp->params = uiprivDefaultTextColumnOptionalParams;\n\n\tr = gtk_cell_renderer_text_new();\n\tgtk_tree_view_column_pack_start(c, r, TRUE);\n\tgtk_tree_view_column_set_cell_data_func(c, r, textColumnDataFunc, p, NULL);\n\tg_signal_connect(r, \"edited\", G_CALLBACK(textColumnEdited), p);\n\tg_ptr_array_add(t->columnParams, p);\n}\n\n// TODO rename modelCOlumn and params everywhere\nvoid uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams)\n{\n\tGtkTreeViewColumn *c;\n\n\tc = addColumn(t, name);\n\taddTextColumn(t, c, textModelColumn, textEditableModelColumn, textParams);\n}\n\nstatic void addImageColumn(uiTable *t, GtkTreeViewColumn *c, int imageModelColumn)\n{\n\tstruct imageColumnParams *p;\n\tGtkCellRenderer *r;\n\n\tp = uiprivNew(struct imageColumnParams);\n\tp->t = t;\n\tp->modelColumn = imageModelColumn;\n\n\tr = gtk_cell_renderer_pixbuf_new();\n\tgtk_tree_view_column_pack_start(c, r, FALSE);\n\tgtk_tree_view_column_set_cell_data_func(c, r, imageColumnDataFunc, p, NULL);\n\tg_ptr_array_add(t->columnParams, p);\n}\n\nvoid uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn)\n{\n\tGtkTreeViewColumn *c;\n\n\tc = addColumn(t, name);\n\taddImageColumn(t, c, imageModelColumn);\n}\n\nvoid uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams)\n{\n\tGtkTreeViewColumn *c;\n\n\tc = addColumn(t, name);\n\taddImageColumn(t, c, imageModelColumn);\n\taddTextColumn(t, c, textModelColumn, textEditableModelColumn, textParams);\n}\n\nstatic void addCheckboxColumn(uiTable *t, GtkTreeViewColumn *c, int checkboxModelColumn, int checkboxEditableModelColumn)\n{\n\tstruct checkboxColumnParams *p;\n\tGtkCellRenderer *r;\n\n\tp = uiprivNew(struct checkboxColumnParams);\n\tp->t = t;\n\tp->m = t->model;\n\tp->modelColumn = checkboxModelColumn;\n\tp->editableColumn = checkboxEditableModelColumn;\n\n\tr = gtk_cell_renderer_toggle_new();\n\tgtk_tree_view_column_pack_start(c, r, FALSE);\n\tgtk_tree_view_column_set_cell_data_func(c, r, checkboxColumnDataFunc, p, NULL);\n\tg_signal_connect(r, \"toggled\", G_CALLBACK(checkboxColumnToggled), p);\n\tg_ptr_array_add(t->columnParams, p);\n}\n\nvoid uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn)\n{\n\tGtkTreeViewColumn *c;\n\n\tc = addColumn(t, name);\n\taddCheckboxColumn(t, c, checkboxModelColumn, checkboxEditableModelColumn);\n}\n\nvoid uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams)\n{\n\tGtkTreeViewColumn *c;\n\n\tc = addColumn(t, name);\n\taddCheckboxColumn(t, c, checkboxModelColumn, checkboxEditableModelColumn);\n\taddTextColumn(t, c, textModelColumn, textEditableModelColumn, textParams);\n}\n\nvoid uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressModelColumn)\n{\n\tGtkTreeViewColumn *c;\n\tstruct progressBarColumnParams *p;\n\tGtkCellRenderer *r;\n\n\tc = addColumn(t, name);\n\n\tp = uiprivNew(struct progressBarColumnParams);\n\tp->t = t;\n\t// TODO make progress and progressBar consistent everywhere\n\tp->modelColumn = progressModelColumn;\n\n\tr = gtk_cell_renderer_progress_new();\n\tgtk_tree_view_column_pack_start(c, r, TRUE);\n\tgtk_tree_view_column_set_cell_data_func(c, r, progressBarColumnDataFunc, p, NULL);\n\tg_ptr_array_add(t->columnParams, p);\n}\n\nvoid uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonModelColumn, int buttonClickableModelColumn)\n{\n\tGtkTreeViewColumn *c;\n\tstruct buttonColumnParams *p;\n\tGtkCellRenderer *r;\n\n\tc = addColumn(t, name);\n\n\tp = uiprivNew(struct buttonColumnParams);\n\tp->t = t;\n\tp->m = t->model;\n\tp->modelColumn = buttonModelColumn;\n\tp->clickableColumn = buttonClickableModelColumn;\n\n\tr = uiprivNewCellRendererButton();\n\tgtk_tree_view_column_pack_start(c, r, TRUE);\n\tgtk_tree_view_column_set_cell_data_func(c, r, buttonColumnDataFunc, p, NULL);\n\tg_signal_connect(r, \"clicked\", G_CALLBACK(buttonColumnClicked), p);\n\tg_ptr_array_add(t->columnParams, p);\n}\n\nuiUnixControlAllDefaultsExceptDestroy(uiTable)\n\nstatic void uiTableDestroy(uiControl *c)\n{\n\tuiTable *t = uiTable(c);\n\tguint i;\n\n\tfor (i = 0; i < t->columnParams->len; i++)\n\t\tuiprivFree(g_ptr_array_index(t->columnParams, i));\n\tg_ptr_array_free(t->columnParams, TRUE);\n\tif (g_hash_table_size(t->indeterminatePositions) != 0)\n\t\tg_source_remove(t->indeterminateTimer);\n\tg_hash_table_destroy(t->indeterminatePositions);\n\tg_object_unref(t->widget);\n\tuiFreeControl(uiControl(t));\n}\n\nuiTable *uiNewTable(uiTableParams *p)\n{\n\tuiTable *t;\n\n\tuiUnixNewControl(uiTable, t);\n\n\tt->model = p->Model;\n\tt->columnParams = g_ptr_array_new();\n\tt->backgroundColumn = p->RowBackgroundColorModelColumn;\n\n\tt->widget = gtk_scrolled_window_new(NULL, NULL);\n\tt->scontainer = GTK_CONTAINER(t->widget);\n\tt->sw = GTK_SCROLLED_WINDOW(t->widget);\n\tgtk_scrolled_window_set_shadow_type(t->sw, GTK_SHADOW_IN);\n\n\tt->treeWidget = gtk_tree_view_new_with_model(GTK_TREE_MODEL(t->model));\n\tt->tv = GTK_TREE_VIEW(t->treeWidget);\n\t// TODO set up t->tv\n\n\tgtk_container_add(t->scontainer, t->treeWidget);\n\t// and make the tree view visible; only the scrolled window's visibility is controlled by libui\n\tgtk_widget_show(t->treeWidget);\n\n\tt->indeterminatePositions = g_hash_table_new_full(rowcolHash, rowcolEqual,\n\t\tuiprivFree, uiprivFree);\n\n\treturn t;\n}\n"
  },
  {
    "path": "unix/table.h",
    "content": "// 4 june 2018\n#include \"../common/table.h\"\n\n// tablemodel.c\n#define uiTableModelType (uiTableModel_get_type())\n#define uiTableModel(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), uiTableModelType, uiTableModel))\n#define isuiTableModel(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), uiTableModelType))\n#define uiTableModelClass(class) (G_TYPE_CHECK_CLASS_CAST((class), uiTableModelType, uiTableModelClass))\n#define isuiTableModelClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), uiTableModel))\n#define getuiTableModelClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), uiTableModelType, uiTableModelClass))\ntypedef struct uiTableModelClass uiTableModelClass;\nstruct uiTableModel {\n\tGObject parent_instance;\n\tuiTableModelHandler *mh;\n};\nstruct uiTableModelClass {\n\tGObjectClass parent_class;\n};\nextern GType uiTableModel_get_type(void);\n"
  },
  {
    "path": "unix/tablemodel.c",
    "content": "// 26 june 2016\n#include \"uipriv_unix.h\"\n#include \"table.h\"\n\nstatic void uiTableModel_gtk_tree_model_interface_init(GtkTreeModelIface *iface);\n\nG_DEFINE_TYPE_WITH_CODE(uiTableModel, uiTableModel, G_TYPE_OBJECT,\n\tG_IMPLEMENT_INTERFACE(GTK_TYPE_TREE_MODEL, uiTableModel_gtk_tree_model_interface_init))\n\nstatic void uiTableModel_init(uiTableModel *m)\n{\n\t// nothing to do\n}\n\nstatic void uiTableModel_dispose(GObject *obj)\n{\n\tG_OBJECT_CLASS(uiTableModel_parent_class)->dispose(obj);\n}\n\nstatic void uiTableModel_finalize(GObject *obj)\n{\n\tG_OBJECT_CLASS(uiTableModel_parent_class)->finalize(obj);\n}\n\nstatic GtkTreeModelFlags uiTableModel_get_flags(GtkTreeModel *mm)\n{\n\treturn GTK_TREE_MODEL_LIST_ONLY;\n}\n\nstatic gint uiTableModel_get_n_columns(GtkTreeModel *mm)\n{\n\tuiTableModel *m = uiTableModel(mm);\n\n\treturn uiprivTableModelNumColumns(m);\n}\n\nstatic GType uiTableModel_get_column_type(GtkTreeModel *mm, gint index)\n{\n\tuiTableModel *m = uiTableModel(mm);\n\n\tswitch (uiprivTableModelColumnType(m, index)) {\n\tcase uiTableValueTypeString:\n\t\treturn G_TYPE_STRING;\n\tcase uiTableValueTypeImage:\n\t\treturn G_TYPE_POINTER;\n\tcase uiTableValueTypeInt:\n\t\treturn G_TYPE_INT;\n\tcase uiTableValueTypeColor:\n\t\treturn GDK_TYPE_RGBA;\n\t}\n\t// TODO\n\treturn G_TYPE_INVALID;\n}\n\n#define STAMP_GOOD 0x1234\n#define STAMP_BAD 0x5678\n\nstatic gboolean uiTableModel_get_iter(GtkTreeModel *mm, GtkTreeIter *iter, GtkTreePath *path)\n{\n\tuiTableModel *m = uiTableModel(mm);\n\tgint row;\n\n\tif (gtk_tree_path_get_depth(path) != 1)\n\t\tgoto bad;\n\trow = gtk_tree_path_get_indices(path)[0];\n\tif (row < 0)\n\t\tgoto bad;\n\tif (row >= uiprivTableModelNumRows(m))\n\t\tgoto bad;\n\titer->stamp = STAMP_GOOD;\n\titer->user_data = GINT_TO_POINTER(row);\n\treturn TRUE;\nbad:\n\titer->stamp = STAMP_BAD;\n\treturn FALSE;\n}\n\n// GtkListStore returns NULL on error; let's do that too\nstatic GtkTreePath *uiTableModel_get_path(GtkTreeModel *mm, GtkTreeIter  *iter)\n{\n\tgint row;\n\n\tif (iter->stamp != STAMP_GOOD)\n\t\treturn NULL;\n\trow = GPOINTER_TO_INT(iter->user_data);\n\treturn gtk_tree_path_new_from_indices(row, -1);\n}\n\n// GtkListStore leaves value empty on failure; let's do the same\nstatic void uiTableModel_get_value(GtkTreeModel *mm, GtkTreeIter *iter, gint column, GValue *value)\n{\n\tuiTableModel *m = uiTableModel(mm);\n\tgint row;\n\tuiTableValue *tvalue;\n\tdouble r, g, b, a;\n\tGdkRGBA rgba;\n\n\tif (iter->stamp != STAMP_GOOD)\n\t\treturn;\n\trow = GPOINTER_TO_INT(iter->user_data);\n\ttvalue = uiprivTableModelCellValue(m, row, column);\n\tswitch (uiprivTableModelColumnType(m, column)) {\n\tcase uiTableValueTypeString:\n\t\tg_value_init(value, G_TYPE_STRING);\n\t\tg_value_set_string(value, uiTableValueString(tvalue));\n\t\tuiFreeTableValue(tvalue);\n\t\treturn;\n\tcase uiTableValueTypeImage:\n\t\tg_value_init(value, G_TYPE_POINTER);\n\t\tg_value_set_pointer(value, uiTableValueImage(tvalue));\n\t\tuiFreeTableValue(tvalue);\n\t\treturn;\n\tcase uiTableValueTypeInt:\n\t\tg_value_init(value, G_TYPE_INT);\n\t\tg_value_set_int(value, uiTableValueInt(tvalue));\n\t\tuiFreeTableValue(tvalue);\n\t\treturn;\n\tcase uiTableValueTypeColor:\n\t\tg_value_init(value, GDK_TYPE_RGBA);\n\t\tif (tvalue == NULL) {\n\t\t\tg_value_set_boxed(value, NULL);\n\t\t\treturn;\n\t\t}\n\t\tuiTableValueColor(tvalue, &r, &g, &b, &a);\n\t\tuiFreeTableValue(tvalue);\n\t\trgba.red = r;\n\t\trgba.green = g;\n\t\trgba.blue = b;\n\t\trgba.alpha = a;\n\t\tg_value_set_boxed(value, &rgba);\n\t\treturn;\n\t}\n\t// TODO\n}\n\nstatic gboolean uiTableModel_iter_next(GtkTreeModel *mm, GtkTreeIter *iter)\n{\n\tuiTableModel *m = uiTableModel(mm);\n\tgint row;\n\n\tif (iter->stamp != STAMP_GOOD)\n\t\treturn FALSE;\n\trow = GPOINTER_TO_INT(iter->user_data);\n\trow++;\n\tif (row >= uiprivTableModelNumRows(m)) {\n\t\titer->stamp = STAMP_BAD;\n\t\treturn FALSE;\n\t}\n\titer->user_data = GINT_TO_POINTER(row);\n\treturn TRUE;\n}\n\nstatic gboolean uiTableModel_iter_previous(GtkTreeModel *mm, GtkTreeIter *iter)\n{\n\tgint row;\n\n\tif (iter->stamp != STAMP_GOOD)\n\t\treturn FALSE;\n\trow = GPOINTER_TO_INT(iter->user_data);\n\trow--;\n\tif (row < 0) {\n\t\titer->stamp = STAMP_BAD;\n\t\treturn FALSE;\n\t}\n\titer->user_data = GINT_TO_POINTER(row);\n\treturn TRUE;\n}\n\nstatic gboolean uiTableModel_iter_children(GtkTreeModel *mm, GtkTreeIter *iter, GtkTreeIter *parent)\n{\n\treturn gtk_tree_model_iter_nth_child(mm, iter, parent, 0);\n}\n\nstatic gboolean uiTableModel_iter_has_child(GtkTreeModel *mm, GtkTreeIter *iter)\n{\n\treturn FALSE;\n}\n\nstatic gint uiTableModel_iter_n_children(GtkTreeModel *mm, GtkTreeIter *iter)\n{\n\tuiTableModel *m = uiTableModel(mm);\n\n\tif (iter != NULL)\n\t\treturn 0;\n\treturn uiprivTableModelNumRows(m);\n}\n\nstatic gboolean uiTableModel_iter_nth_child(GtkTreeModel *mm, GtkTreeIter *iter, GtkTreeIter *parent, gint n)\n{\n\tuiTableModel *m = uiTableModel(mm);\n\n\tif (iter->stamp != STAMP_GOOD)\n\t\treturn FALSE;\n\tif (parent != NULL)\n\t\tgoto bad;\n\tif (n < 0)\n\t\tgoto bad;\n\tif (n >= uiprivTableModelNumRows(m))\n\t\tgoto bad;\n\titer->stamp = STAMP_GOOD;\n\titer->user_data = GINT_TO_POINTER(n);\n\treturn TRUE;\nbad:\n\titer->stamp = STAMP_BAD;\n\treturn FALSE;\n}\n\ngboolean uiTableModel_iter_parent(GtkTreeModel *mm, GtkTreeIter *iter, GtkTreeIter *child)\n{\n\titer->stamp = STAMP_BAD;\n\treturn FALSE;\n}\n\nstatic void uiTableModel_class_init(uiTableModelClass *class)\n{\n\tG_OBJECT_CLASS(class)->dispose = uiTableModel_dispose;\n\tG_OBJECT_CLASS(class)->finalize = uiTableModel_finalize;\n}\n\nstatic void uiTableModel_gtk_tree_model_interface_init(GtkTreeModelIface *iface)\n{\n\tiface->get_flags = uiTableModel_get_flags;\n\tiface->get_n_columns = uiTableModel_get_n_columns;\n\tiface->get_column_type = uiTableModel_get_column_type;\n\tiface->get_iter = uiTableModel_get_iter;\n\tiface->get_path = uiTableModel_get_path;\n\tiface->get_value = uiTableModel_get_value;\n\tiface->iter_next = uiTableModel_iter_next;\n\tiface->iter_previous = uiTableModel_iter_previous;\n\tiface->iter_children = uiTableModel_iter_children;\n\tiface->iter_has_child = uiTableModel_iter_has_child;\n\tiface->iter_n_children = uiTableModel_iter_n_children;\n\tiface->iter_nth_child = uiTableModel_iter_nth_child;\n\tiface->iter_parent = uiTableModel_iter_parent;\n\t// don't specify ref_node() or unref_node()\n}\n\nuiTableModel *uiNewTableModel(uiTableModelHandler *mh)\n{\n\tuiTableModel *m;\n\n\tm = uiTableModel(g_object_new(uiTableModelType, NULL));\n\tm->mh = mh;\n\treturn m;\n}\n\nvoid uiFreeTableModel(uiTableModel *m)\n{\n\tg_object_unref(m);\n}\n\nvoid uiTableModelRowInserted(uiTableModel *m, int newIndex)\n{\n\tGtkTreePath *path;\n\tGtkTreeIter iter;\n\n\tpath = gtk_tree_path_new_from_indices(newIndex, -1);\n\titer.stamp = STAMP_GOOD;\n\titer.user_data = GINT_TO_POINTER(newIndex);\n\tgtk_tree_model_row_inserted(GTK_TREE_MODEL(m), path, &iter);\n\tgtk_tree_path_free(path);\n}\n\nvoid uiTableModelRowChanged(uiTableModel *m, int index)\n{\n\tGtkTreePath *path;\n\tGtkTreeIter iter;\n\n\tpath = gtk_tree_path_new_from_indices(index, -1);\n\titer.stamp = STAMP_GOOD;\n\titer.user_data = GINT_TO_POINTER(index);\n\tgtk_tree_model_row_changed(GTK_TREE_MODEL(m), path, &iter);\n\tgtk_tree_path_free(path);\n}\n\nvoid uiTableModelRowDeleted(uiTableModel *m, int oldIndex)\n{\n\tGtkTreePath *path;\n\n\tpath = gtk_tree_path_new_from_indices(oldIndex, -1);\n\tgtk_tree_model_row_deleted(GTK_TREE_MODEL(m), path);\n\tgtk_tree_path_free(path);\n}\n\nuiTableModelHandler *uiprivTableModelHandler(uiTableModel *m)\n{\n\treturn m->mh;\n}\n"
  },
  {
    "path": "unix/text.c",
    "content": "// 9 april 2015\n#include \"uipriv_unix.h\"\n\nchar *uiUnixStrdupText(const char *t)\n{\n\treturn g_strdup(t);\n}\n\nvoid uiFreeText(char *t)\n{\n\tg_free(t);\n}\n\nint uiprivStricmp(const char *a, const char *b)\n{\n\treturn strcasecmp(a, b);\n}\n"
  },
  {
    "path": "unix/uipriv_unix.h",
    "content": "// 22 april 2015\n#define GLIB_VERSION_MIN_REQUIRED GLIB_VERSION_2_40\n#define GLIB_VERSION_MAX_ALLOWED GLIB_VERSION_2_40\n#define GDK_VERSION_MIN_REQUIRED GDK_VERSION_3_10\n#define GDK_VERSION_MAX_ALLOWED GDK_VERSION_3_10\n#include <gtk/gtk.h>\n#include <math.h>\n#include <dlfcn.h>\t\t// see future.c\n#include <langinfo.h>\n#include <string.h>\n#include <stdlib.h>\n#include <inttypes.h>\n#include \"../ui.h\"\n#include \"../ui_unix.h\"\n#include \"../common/uipriv.h\"\n\n#define uiprivGTKXMargin 12\n#define uiprivGTKYMargin 12\n#define uiprivGTKXPadding 12\n#define uiprivGTKYPadding 6\n\n// menu.c\nextern GtkWidget *uiprivMakeMenubar(uiWindow *);\nextern void uiprivFreeMenubar(GtkWidget *);\nextern void uiprivUninitMenus(void);\n\n// alloc.c\nextern void uiprivInitAlloc(void);\nextern void uiprivUninitAlloc(void);\n\n// util.c\nextern void uiprivSetMargined(GtkContainer *, int);\n\n// child.c\ntypedef struct uiprivChild uiprivChild;\nextern uiprivChild *uiprivNewChild(uiControl *child, uiControl *parent, GtkContainer *parentContainer);\nextern uiprivChild *uiprivNewChildWithBox(uiControl *child, uiControl *parent, GtkContainer *parentContainer, int margined);\nextern void uiprivChildRemove(uiprivChild *c);\nextern void uiprivChildDestroy(uiprivChild *c);\nextern GtkWidget *uiprivChildWidget(uiprivChild *c);\nextern int uiprivChildFlag(uiprivChild *c);\nextern void uiprivChildSetFlag(uiprivChild *c, int flag);\nextern GtkWidget *uiprivChildBox(uiprivChild *c);\nextern void uiprivChildSetMargined(uiprivChild *c, int margined);\n\n// draw.c\nextern uiDrawContext *uiprivNewContext(cairo_t *cr, GtkStyleContext *style);\nextern void uiprivFreeContext(uiDrawContext *);\n\n// image.c\nextern cairo_surface_t *uiprivImageAppropriateSurface(uiImage *i, GtkWidget *w);\n\n// cellrendererbutton.c\nextern GtkCellRenderer *uiprivNewCellRendererButton(void);\n\n// future.c\nextern void uiprivLoadFutures(void);\nextern PangoAttribute *uiprivFUTURE_pango_attr_font_features_new(const gchar *features);\nextern PangoAttribute *uiprivFUTURE_pango_attr_foreground_alpha_new(guint16 alpha);\nextern PangoAttribute *uiprivFUTURE_pango_attr_background_alpha_new(guint16 alpha);\nextern gboolean uiprivFUTURE_gtk_widget_path_iter_set_object_name(GtkWidgetPath *path, gint pos, const char *name);\n"
  },
  {
    "path": "unix/util.c",
    "content": "// 18 april 2015\n#include \"uipriv_unix.h\"\n\nvoid uiprivSetMargined(GtkContainer *c, int margined)\n{\n\tif (margined)\n\t\tgtk_container_set_border_width(c, uiprivGTKXMargin);\n\telse\n\t\tgtk_container_set_border_width(c, 0);\n}\n"
  },
  {
    "path": "unix/window.c",
    "content": "// 11 june 2015\n#include \"uipriv_unix.h\"\n\nstruct uiWindow {\n\tuiUnixControl c;\n\n\tGtkWidget *widget;\n\tGtkContainer *container;\n\tGtkWindow *window;\n\n\tGtkWidget *vboxWidget;\n\tGtkContainer *vboxContainer;\n\tGtkBox *vbox;\n\n\tGtkWidget *childHolderWidget;\n\tGtkContainer *childHolderContainer;\n\n\tGtkWidget *menubar;\n\n\tuiControl *child;\n\tint margined;\n\n\tint (*onClosing)(uiWindow *, void *);\n\tvoid *onClosingData;\n\tvoid (*onContentSizeChanged)(uiWindow *, void *);\n\tvoid *onContentSizeChangedData;\n\tgboolean fullscreen;\n};\n\nstatic gboolean onClosing(GtkWidget *win, GdkEvent *e, gpointer data)\n{\n\tuiWindow *w = uiWindow(data);\n\n\t// manually destroy the window ourselves; don't let the delete-event handler do it\n\tif ((*(w->onClosing))(w, w->onClosingData))\n\t\tuiControlDestroy(uiControl(w));\n\t// don't continue to the default delete-event handler; we destroyed the window by now\n\treturn TRUE;\n}\n\nstatic void onSizeAllocate(GtkWidget *widget, GdkRectangle *allocation, gpointer data)\n{\n\tuiWindow *w = uiWindow(data);\n\n\t// TODO deal with spurious size-allocates\n\t(*(w->onContentSizeChanged))(w, w->onContentSizeChangedData);\n}\n\nstatic int defaultOnClosing(uiWindow *w, void *data)\n{\n\treturn 0;\n}\n\nstatic void defaultOnPositionContentSizeChanged(uiWindow *w, void *data)\n{\n\t// do nothing\n}\n\nstatic void uiWindowDestroy(uiControl *c)\n{\n\tuiWindow *w = uiWindow(c);\n\n\t// first hide ourselves\n\tgtk_widget_hide(w->widget);\n\t// now destroy the child\n\tif (w->child != NULL) {\n\t\tuiControlSetParent(w->child, NULL);\n\t\tuiUnixControlSetContainer(uiUnixControl(w->child), w->childHolderContainer, TRUE);\n\t\tuiControlDestroy(w->child);\n\t}\n\t// now destroy the menus, if any\n\tif (w->menubar != NULL)\n\t\tuiprivFreeMenubar(w->menubar);\n\tgtk_widget_destroy(w->childHolderWidget);\n\tgtk_widget_destroy(w->vboxWidget);\n\t// and finally free ourselves\n\t// use gtk_widget_destroy() instead of g_object_unref() because GTK+ has internal references (see #165)\n\tgtk_widget_destroy(w->widget);\n\tuiFreeControl(uiControl(w));\n}\n\nuiUnixControlDefaultHandle(uiWindow)\n\nuiControl *uiWindowParent(uiControl *c)\n{\n\treturn NULL;\n}\n\nvoid uiWindowSetParent(uiControl *c, uiControl *parent)\n{\n\tuiUserBugCannotSetParentOnToplevel(\"uiWindow\");\n}\n\nstatic int uiWindowToplevel(uiControl *c)\n{\n\treturn 1;\n}\n\nuiUnixControlDefaultVisible(uiWindow)\n\nstatic void uiWindowShow(uiControl *c)\n{\n\tuiWindow *w = uiWindow(c);\n\n\t// don't use gtk_widget_show_all() as that will show all children, regardless of user settings\n\t// don't use gtk_widget_show(); that doesn't bring to front or give keyboard focus\n\t// (gtk_window_present() does call gtk_widget_show() though)\n\tgtk_window_present(w->window);\n}\n\nuiUnixControlDefaultHide(uiWindow)\nuiUnixControlDefaultEnabled(uiWindow)\nuiUnixControlDefaultEnable(uiWindow)\nuiUnixControlDefaultDisable(uiWindow)\n// TODO?\nuiUnixControlDefaultSetContainer(uiWindow)\n\nchar *uiWindowTitle(uiWindow *w)\n{\n\treturn uiUnixStrdupText(gtk_window_get_title(w->window));\n}\n\nvoid uiWindowSetTitle(uiWindow *w, const char *title)\n{\n\tgtk_window_set_title(w->window, title);\n}\n\nvoid uiWindowContentSize(uiWindow *w, int *width, int *height)\n{\n\tGtkAllocation allocation;\n\n\tgtk_widget_get_allocation(w->childHolderWidget, &allocation);\n\t*width = allocation.width;\n\t*height = allocation.height;\n}\n\nvoid uiWindowSetContentSize(uiWindow *w, int width, int height)\n{\n\tGtkAllocation childAlloc;\n\tgint winWidth, winHeight;\n\n\t// we need to resize the child holder widget to the given size\n\t// we can't resize that without running the event loop\n\t// but we can do gtk_window_set_size()\n\t// so how do we deal with the differences in sizes?\n\t// simple arithmetic, of course!\n\n\t// 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\n\t// this is not affected by Wayland and not affected by GTK+ builtin CSD\n\t// so we can safely juse use them to get the real window size!\n\t// since we're using gtk_window_resize(), use the latter\n\tgtk_window_get_size(w->window, &winWidth, &winHeight);\n\n\t// now get the child holder widget's current allocation\n\tgtk_widget_get_allocation(w->childHolderWidget, &childAlloc);\n\t// and punch that out of the window size\n\twinWidth -= childAlloc.width;\n\twinHeight -= childAlloc.height;\n\n\t// now we just need to add the new size back in\n\twinWidth += width;\n\twinHeight += height;\n\t// and set it\n\t// this will not move the window in my tests, so we're good\n\tgtk_window_resize(w->window, winWidth, winHeight);\n}\n\nint uiWindowFullscreen(uiWindow *w)\n{\n\treturn w->fullscreen;\n}\n\n// TODO use window-state-event to track\n// TODO does this send an extra size changed?\n// TODO what behavior do we want?\nvoid uiWindowSetFullscreen(uiWindow *w, int fullscreen)\n{\n\tw->fullscreen = fullscreen;\n\tif (w->fullscreen)\n\t\tgtk_window_fullscreen(w->window);\n\telse\n\t\tgtk_window_unfullscreen(w->window);\n}\n\nvoid uiWindowOnContentSizeChanged(uiWindow *w, void (*f)(uiWindow *, void *), void *data)\n{\n\tw->onContentSizeChanged = f;\n\tw->onContentSizeChangedData = data;\n}\n\nvoid uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *, void *), void *data)\n{\n\tw->onClosing = f;\n\tw->onClosingData = data;\n}\n\nint uiWindowBorderless(uiWindow *w)\n{\n\treturn gtk_window_get_decorated(w->window) == FALSE;\n}\n\nvoid uiWindowSetBorderless(uiWindow *w, int borderless)\n{\n\tgtk_window_set_decorated(w->window, borderless == 0);\n}\n\n// TODO save and restore expands and aligns\nvoid uiWindowSetChild(uiWindow *w, uiControl *child)\n{\n\tif (w->child != NULL) {\n\t\tuiControlSetParent(w->child, NULL);\n\t\tuiUnixControlSetContainer(uiUnixControl(w->child), w->childHolderContainer, TRUE);\n\t}\n\tw->child = child;\n\tif (w->child != NULL) {\n\t\tuiControlSetParent(w->child, uiControl(w));\n\t\tuiUnixControlSetContainer(uiUnixControl(w->child), w->childHolderContainer, FALSE);\n\t}\n}\n\nint uiWindowMargined(uiWindow *w)\n{\n\treturn w->margined;\n}\n\nvoid uiWindowSetMargined(uiWindow *w, int margined)\n{\n\tw->margined = margined;\n\tuiprivSetMargined(w->childHolderContainer, w->margined);\n}\n\nuiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar)\n{\n\tuiWindow *w;\n\n\tuiUnixNewControl(uiWindow, w);\n\n\tw->widget = gtk_window_new(GTK_WINDOW_TOPLEVEL);\n\tw->container = GTK_CONTAINER(w->widget);\n\tw->window = GTK_WINDOW(w->widget);\n\n\tgtk_window_set_title(w->window, title);\n\tgtk_window_resize(w->window, width, height);\n\n\tw->vboxWidget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);\n\tw->vboxContainer = GTK_CONTAINER(w->vboxWidget);\n\tw->vbox = GTK_BOX(w->vboxWidget);\n\n\t// set the vbox as the GtkWindow child\n\tgtk_container_add(w->container, w->vboxWidget);\n\n\tif (hasMenubar) {\n\t\tw->menubar = uiprivMakeMenubar(uiWindow(w));\n\t\tgtk_container_add(w->vboxContainer, w->menubar);\n\t}\n\n\tw->childHolderWidget = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);\n\tw->childHolderContainer = GTK_CONTAINER(w->childHolderWidget);\n\tgtk_widget_set_hexpand(w->childHolderWidget, TRUE);\n\tgtk_widget_set_halign(w->childHolderWidget, GTK_ALIGN_FILL);\n\tgtk_widget_set_vexpand(w->childHolderWidget, TRUE);\n\tgtk_widget_set_valign(w->childHolderWidget, GTK_ALIGN_FILL);\n\tgtk_container_add(w->vboxContainer, w->childHolderWidget);\n\n\t// show everything in the vbox, but not the GtkWindow itself\n\tgtk_widget_show_all(w->vboxWidget);\n\n\t// and connect our events\n\tg_signal_connect(w->widget, \"delete-event\", G_CALLBACK(onClosing), w);\n\tg_signal_connect(w->childHolderWidget, \"size-allocate\", G_CALLBACK(onSizeAllocate), w);\n\tuiWindowOnClosing(w, defaultOnClosing, NULL);\n\tuiWindowOnContentSizeChanged(w, defaultOnPositionContentSizeChanged, NULL);\n\n\t// normally it's SetParent() that does this, but we can't call SetParent() on a uiWindow\n\t// TODO we really need to clean this up, especially since see uiWindowDestroy() above\n\tg_object_ref(w->widget);\n\n\treturn w;\n}\n"
  },
  {
    "path": "windows/_rc2bin/build.bat",
    "content": "@rem 2 may 2018\n@echo off\n\ncl /nologo /TP /GR /EHsc /MDd /Ob0 /Od /RTC1 /W4 /wd4100 /bigobj /RTC1 /RTCs /RTCu /FS -c main.cpp\nif errorlevel 1 goto out\nrc -foresources.res resources.rc\nif errorlevel 1 goto out\nlink /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  \n\n:out\n"
  },
  {
    "path": "windows/_rc2bin/libui.manifest",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">\n<assemblyIdentity\n    version=\"1.0.0.0\"\n    processorArchitecture=\"*\"\n    name=\"CompanyName.ProductName.YourApplication\"\n    type=\"win32\"\n/>\n<description>Your application description here.</description>\n<dependency>\n    <dependentAssembly>\n        <assemblyIdentity\n            type=\"win32\"\n            name=\"Microsoft.Windows.Common-Controls\"\n            version=\"6.0.0.0\"\n            processorArchitecture=\"*\"\n            publicKeyToken=\"6595b64144ccf1df\"\n            language=\"*\"\n        />\n    </dependentAssembly>\n</dependency>\n<compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\">\n    <application>\n        <!--The ID below indicates application support for Windows Vista -->\n        <supportedOS Id=\"{e2011457-1546-43c5-a5fe-008deee3d3f0}\"/>\n        <!--The ID below indicates application support for Windows 7 -->\n        <supportedOS Id=\"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}\"/>\n    </application>\n</compatibility>\n</assembly>\n\n"
  },
  {
    "path": "windows/_rc2bin/main.cpp",
    "content": "// 2 may 2018\n#include \"winapi.hpp\"\n#include <stdio.h>\n#include <stdlib.h>\n#include \"resources.hpp\"\n\n// TODO make sure there are no CRs in the output\n\nvoid die(const char *f, const char *constname)\n{\n\tDWORD le;\n\n\tle = GetLastError();\n\tfprintf(stderr, \"error calling %s for %s: %I32d\\n\", f, constname, le);\n\texit(1);\n}\n\nvoid dumpResource(const char *constname, const WCHAR *name, const WCHAR *type)\n{\n\tHRSRC hrsrc;\n\tHGLOBAL res;\n\tuint8_t *b, *bp;\n\tDWORD i, n;\n\tDWORD j;\n\n\thrsrc = FindResourceW(NULL, name, type);\n\tif (hrsrc == NULL)\n\t\tdie(\"FindResourceW()\", constname);\n\tn = SizeofResource(NULL, hrsrc);\n\tif (n == 0)\n\t\tdie(\"SizeofResource()\", constname);\n\tres = LoadResource(NULL, hrsrc);\n\tif (res == NULL)\n\t\tdie(\"LoadResource()\", constname);\n\tb = (uint8_t *) LockResource(res);\n\tif (b == NULL)\n\t\tdie(\"LockResource()\", constname);\n\n\tprintf(\"static const uint8_t %s[] = {\\n\", constname);\n\tbp = b;\n\tj = 0;\n\tfor (i = 0; i < n; i++) {\n\t\tif (j == 0)\n\t\t\tprintf(\"\\t\");\n\t\tprintf(\"0x%02I32X,\", (uint32_t) (*bp));\n\t\tbp++;\n\t\tif (j == 7) {\n\t\t\tprintf(\"\\n\");\n\t\t\tj = 0;\n\t\t} else {\n\t\t\tprintf(\" \");\n\t\t\tj++;\n\t\t}\n\t}\n\tif (j != 0)\n\t\tprintf(\"\\n\");\n\tprintf(\"};\\n\");\n\tprintf(\"static_assert(ARRAYSIZE(%s) == %I32d, \\\"wrong size for resource %s\\\");\\n\", constname, n, constname);\n\tprintf(\"\\n\");\n}\n\nint main(void)\n{\n#define d(c, t) dumpResource(#c, MAKEINTRESOURCEW(c), t)\n\td(rcTabPageDialog, RT_DIALOG);\n\td(rcFontDialog, RT_DIALOG);\n\td(rcColorDialog, RT_DIALOG);\n\treturn 0;\n}\n"
  },
  {
    "path": "windows/_rc2bin/resources.hpp",
    "content": "// 30 may 2015\n\n#define rcTabPageDialog 29000\n#define rcFontDialog 29001\n#define rcColorDialog 29002\n\n// TODO normalize these\n\n#define rcFontFamilyCombobox 1000\n#define rcFontStyleCombobox 1001\n#define rcFontSizeCombobox 1002\n#define rcFontSamplePlacement 1003\n\n#define rcColorSVChooser 1100\n#define rcColorHSlider 1101\n#define rcPreview 1102\n#define rcOpacitySlider 1103\n#define rcH 1104\n#define rcS 1105\n#define rcV 1106\n#define rcRDouble 1107\n#define rcRInt 1108\n#define rcGDouble 1109\n#define rcGInt 1110\n#define rcBDouble 1111\n#define rcBInt 1112\n#define rcADouble 1113\n#define rcAInt 1114\n#define rcHex 1115\n#define rcHLabel 1116\n#define rcSLabel 1117\n#define rcVLabel 1118\n#define rcRLabel 1119\n#define rcGLabel 1120\n#define rcBLabel 1121\n#define rcALabel 1122\n#define rcHexLabel 1123\n"
  },
  {
    "path": "windows/_rc2bin/resources.rc",
    "content": "// 30 may 2015\n#include \"winapi.hpp\"\n#include \"resources.hpp\"\n\n// this is a UTF-8 file\n#pragma code_page(65001)\n\n// this is the Common Controls 6 manifest\n// we only define it in a shared build; static builds have to include the appropriate parts of the manifest in the output executable\n// LONGTERM set up the string values here\n#ifndef _UI_STATIC\nISOLATIONAWARE_MANIFEST_RESOURCE_ID RT_MANIFEST \"libui.manifest\"\n#endif\n\n// this is the dialog template used by tab pages; see windows/tabpage.c for details\nrcTabPageDialog DIALOGEX 0, 0, 100, 100\nSTYLE DS_CONTROL | WS_CHILD | WS_VISIBLE\nEXSTYLE WS_EX_CONTROLPARENT\nBEGIN\n\t// nothing\nEND\n\n// this is for our custom DirectWrite-based font dialog (see fontdialog.cpp)\n// this is based on the \"New Font Dialog with Syslink\" in Microsoft's font.dlg\n// LONGTERM look at localization\n// LONGTERM make it look tighter and nicer like the real one, including the actual heights of the font family and style comboboxes\nrcFontDialog DIALOGEX 13, 54, 243, 200\nSTYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_3DLOOK\nCAPTION \"Font\"\nFONT 9, \"Segoe UI\"\nBEGIN\n\tLTEXT\t\t\"&Font:\", -1, 7, 7, 98, 9\n\tCOMBOBOX\trcFontFamilyCombobox, 7, 16, 98, 76,\n\t\tCBS_SIMPLE | CBS_AUTOHSCROLL | CBS_DISABLENOSCROLL |\n\t\tCBS_SORT | WS_VSCROLL | WS_TABSTOP | CBS_HASSTRINGS\n\n\tLTEXT\t\t\"Font st&yle:\", -1, 114, 7, 74, 9\n\tCOMBOBOX\trcFontStyleCombobox, 114, 16, 74, 76,\n\t\tCBS_SIMPLE | CBS_AUTOHSCROLL | CBS_DISABLENOSCROLL |\n\t\tWS_VSCROLL | WS_TABSTOP | CBS_HASSTRINGS\n\n\tLTEXT\t\t\"&Size:\", -1, 198, 7, 36, 9\n\tCOMBOBOX\trcFontSizeCombobox, 198, 16, 36, 76,\n\t\tCBS_SIMPLE | CBS_AUTOHSCROLL | CBS_DISABLENOSCROLL |\n\t\tCBS_SORT | WS_VSCROLL | WS_TABSTOP | CBS_HASSTRINGS\n\n\tGROUPBOX\t\t\"Sample\", -1, 7, 97, 227, 70, WS_GROUP\n\tCTEXT\t\t\t\"AaBbYyZz\", rcFontSamplePlacement, 9, 106, 224, 60, SS_NOPREFIX | NOT WS_VISIBLE\n\n\tDEFPUSHBUTTON\t\"OK\", IDOK, 141, 181, 45, 14, WS_GROUP\n\tPUSHBUTTON\t\t\"Cancel\", IDCANCEL, 190, 181, 45, 14, WS_GROUP\nEND\n\nrcColorDialog DIALOGEX 13, 54, 344, 209\nSTYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_3DLOOK\nCAPTION \"Color\"\nFONT 9, \"Segoe UI\"\nBEGIN\n\t// this size should be big enough to get at least 256x256 on font sizes >= 8 pt\n\tCTEXT\t\t\"AaBbYyZz\", rcColorSVChooser, 7, 7, 195, 195, SS_NOPREFIX | SS_BLACKRECT\n\n\t// width is the suggested slider height since this is vertical\n\tCTEXT\t\t\"AaBbYyZz\", rcColorHSlider, 206, 7, 15, 195, SS_NOPREFIX | SS_BLACKRECT\n\n\tLTEXT\t\t\"Preview:\", -1, 230, 7, 107, 9, SS_NOPREFIX\n\tCTEXT\t\t\"AaBbYyZz\", rcPreview, 230, 16, 107, 20, SS_NOPREFIX | SS_BLACKRECT\n\n\tLTEXT\t\t\"Opacity:\", -1, 230, 45, 107, 9, SS_NOPREFIX\n\tCTEXT\t\t\"AaBbYyZz\", rcOpacitySlider, 230, 54, 107, 15, SS_NOPREFIX | SS_BLACKRECT\n\n\tLTEXT\t\t\"&H:\", rcHLabel, 230, 81, 8, 8\n\tEDITTEXT\t\trcH, 238, 78, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE\n\tLTEXT\t\t\"&S:\", rcSLabel, 230, 95, 8, 8\n\tEDITTEXT\t\trcS, 238, 92, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE\n\tLTEXT\t\t\"&V:\", rcVLabel, 230, 109, 8, 8\n\tEDITTEXT\t\trcV, 238, 106, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE\n\n\tLTEXT\t\t\"&R:\", rcRLabel, 277, 81, 8, 8\n\tEDITTEXT\t\trcRDouble, 285, 78, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE\n\tEDITTEXT\t\trcRInt, 315, 78, 20, 14, ES_LEFT | ES_AUTOHSCROLL | ES_NUMBER | WS_TABSTOP, WS_EX_CLIENTEDGE\n\tLTEXT\t\t\"&G:\", rcGLabel, 277, 95, 8, 8\n\tEDITTEXT\t\trcGDouble, 285, 92, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE\n\tEDITTEXT\t\trcGInt, 315, 92, 20, 14, ES_LEFT | ES_AUTOHSCROLL | ES_NUMBER | WS_TABSTOP, WS_EX_CLIENTEDGE\n\tLTEXT\t\t\"&B:\", rcBLabel, 277, 109, 8, 8\n\tEDITTEXT\t\trcBDouble, 285, 106, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE\n\tEDITTEXT\t\trcBInt, 315, 106, 20, 14, ES_LEFT | ES_AUTOHSCROLL | ES_NUMBER | WS_TABSTOP, WS_EX_CLIENTEDGE\n\tLTEXT\t\t\"&A:\", rcALabel, 277, 123, 8, 8\n\tEDITTEXT\t\trcADouble, 285, 120, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE\n\tEDITTEXT\t\trcAInt, 315, 120, 20, 14, ES_LEFT | ES_AUTOHSCROLL | ES_NUMBER | WS_TABSTOP, WS_EX_CLIENTEDGE\n\n\tLTEXT\t\t\"He&x:\", rcHexLabel, 269, 146, 16, 8\n\tEDITTEXT\t\trcHex, 285, 143, 50, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE\n\n\tDEFPUSHBUTTON\t\"OK\", IDOK, 243, 188, 45, 14, WS_GROUP\n\tPUSHBUTTON\t\t\"Cancel\", IDCANCEL, 292, 188, 45, 14, WS_GROUP\nEND\n"
  },
  {
    "path": "windows/_rc2bin/winapi.hpp",
    "content": "// 31 may 2015\n#define UNICODE\n#define _UNICODE\n#define STRICT\n#define STRICT_TYPED_ITEMIDS\n\n// see https://github.com/golang/go/issues/9916#issuecomment-74812211\n// TODO get rid of this\n#define INITGUID\n\n// for the manifest\n#ifndef _UI_STATIC\n#define ISOLATION_AWARE_ENABLED 1\n#endif\n\n// get Windows version right; right now Windows Vista\n// unless otherwise stated, all values from Microsoft's sdkddkver.h\n// TODO is all of this necessary? how is NTDDI_VERSION used?\n// TODO platform update sp2\n#define WINVER\t\t\t0x0600\t/* from Microsoft's winnls.h */\n#define _WIN32_WINNT\t\t0x0600\n#define _WIN32_WINDOWS\t0x0600\t/* from Microsoft's pdh.h */\n#define _WIN32_IE\t\t\t0x0700\n#define NTDDI_VERSION\t\t0x06000000\n\n// The MinGW-w64 header has an unverified IDWriteTypography definition.\n// 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.\n// TODO change the check from _MSC_VER to a MinGW-w64-specific check\n// TODO keep track of what else is guarded by this\n#ifndef _MSC_VER\n#define __MINGW_USE_BROKEN_INTERFACE\n#endif\n\n#include <windows.h>\n\n// Microsoft's resource compiler will segfault if we feed it headers it was not designed to handle\n#ifndef RC_INVOKED\n#include <commctrl.h>\n#include <uxtheme.h>\n#include <windowsx.h>\n#include <shobjidl.h>\n#include <d2d1.h>\n#include <d2d1helper.h>\n#include <dwrite.h>\n\n#include <stdint.h>\n#include <string.h>\n#include <wchar.h>\n#include <stdarg.h>\n#include <stdio.h>\n#include <math.h>\n#include <float.h>\n#include <inttypes.h>\n\n#include <vector>\n#include <map>\n#include <unordered_map>\n#include <sstream>\n#include <functional>\n#endif\n"
  },
  {
    "path": "windows/_uipriv_migrate.hpp",
    "content": "\n// menu.c\nextern HMENU makeMenubar(void);\nextern const uiMenuItem *menuIDToItem(UINT_PTR);\nextern void runMenuEvent(WORD, uiWindow *);\nextern void freeMenubar(HMENU);\nextern void uninitMenus(void);\n\n// draw.c\nextern HRESULT initDraw(void);\nextern void uninitDraw(void);\nextern ID2D1HwndRenderTarget *makeHWNDRenderTarget(HWND hwnd);\nextern uiDrawContext *newContext(ID2D1RenderTarget *);\nextern void freeContext(uiDrawContext *);\n"
  },
  {
    "path": "windows/alloc.cpp",
    "content": "// 4 december 2014\n#include \"uipriv_windows.hpp\"\n\ntypedef std::vector<uint8_t> byteArray;\n\nstatic std::map<uint8_t *, byteArray *> heap;\nstatic std::map<byteArray *, const char *> types;\n\nvoid initAlloc(void)\n{\n\t// do nothing\n}\n\nvoid uninitAlloc(void)\n{\n\tstd::ostringstream oss;\n\tstd::string ossstr;\t\t// keep alive, just to be safe\n\n\tif (heap.size() == 0)\n\t\treturn;\n\tfor (const auto &alloc : heap)\n\t\t// note the void * cast; otherwise it'll be treated as a string\n\t\toss << (void *) (alloc.first) << \" \" << types[alloc.second] << \"\\n\";\n\tossstr = oss.str();\n\tuiprivUserBug(\"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());\n}\n\n#define rawBytes(pa) (&((*pa)[0]))\n\nvoid *uiprivAlloc(size_t size, const char *type)\n{\n\tbyteArray *out;\n\n\tout = new byteArray(size, 0);\n\theap[rawBytes(out)] = out;\n\ttypes[out] = type;\n\treturn rawBytes(out);\n}\n\nvoid *uiprivRealloc(void *_p, size_t size, const char *type)\n{\n\tuint8_t *p = (uint8_t *) _p;\n\tbyteArray *arr;\n\n\tif (p == NULL)\n\t\treturn uiprivAlloc(size, type);\n\tarr = heap[p];\n\t// TODO does this fill in?\n\tarr->resize(size, 0);\n\theap.erase(p);\n\theap[rawBytes(arr)] = arr;\n\treturn rawBytes(arr);\n}\n\nvoid uiprivFree(void *_p)\n{\n\tuint8_t *p = (uint8_t *) _p;\n\n\tif (p == NULL)\n\t\tuiprivImplBug(\"attempt to uiprivFree(NULL)\");\n\ttypes.erase(heap[p]);\n\tdelete heap[p];\n\theap.erase(p);\n}\n"
  },
  {
    "path": "windows/area.cpp",
    "content": "// 8 september 2015\n#include \"uipriv_windows.hpp\"\n#include \"area.hpp\"\n\n// TODO handle WM_DESTROY/WM_NCDESTROY\n// TODO same for other Direct2D stuff\nstatic LRESULT CALLBACK areaWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)\n{\n\tuiArea *a;\n\tCREATESTRUCTW *cs = (CREATESTRUCTW *) lParam;\n\tRECT client;\n\tWINDOWPOS *wp = (WINDOWPOS *) lParam;\n\tLRESULT lResult;\n\n\ta = (uiArea *) GetWindowLongPtrW(hwnd, GWLP_USERDATA);\n\tif (a == NULL) {\n\t\tif (uMsg == WM_CREATE) {\n\t\t\ta = (uiArea *) (cs->lpCreateParams);\n\t\t\t// assign a->hwnd here so we can use it immediately\n\t\t\ta->hwnd = hwnd;\n\t\t\tSetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR) a);\n\t\t}\n\t\t// fall through to DefWindowProcW() anyway\n\t\treturn DefWindowProcW(hwnd, uMsg, wParam, lParam);\n\t}\n\n\t// always recreate the render target if necessary\n\tif (a->rt == NULL)\n\t\ta->rt = makeHWNDRenderTarget(a->hwnd);\n\n\tif (areaDoDraw(a, uMsg, wParam, lParam, &lResult) != FALSE)\n\t\treturn lResult;\n\n\tif (uMsg == WM_WINDOWPOSCHANGED) {\n\t\tif ((wp->flags & SWP_NOSIZE) != 0)\n\t\t\treturn DefWindowProcW(hwnd, uMsg, wParam, lParam);\n\t\tuiWindowsEnsureGetClientRect(a->hwnd, &client);\n\t\tareaDrawOnResize(a, &client);\n\t\tareaScrollOnResize(a, &client);\n\t\treturn 0;\n\t}\n\n\tif (areaDoScroll(a, uMsg, wParam, lParam, &lResult) != FALSE)\n\t\treturn lResult;\n\tif (areaDoEvents(a, uMsg, wParam, lParam, &lResult) != FALSE)\n\t\treturn lResult;\n\n\t// nothing done\n\treturn DefWindowProc(hwnd, uMsg, wParam, lParam);\n}\n\n// control implementation\n\nuiWindowsControlAllDefaults(uiArea)\n\nstatic void uiAreaMinimumSize(uiWindowsControl *c, int *width, int *height)\n{\n\t// TODO\n\t*width = 0;\n\t*height = 0;\n}\n\nATOM registerAreaClass(HICON hDefaultIcon, HCURSOR hDefaultCursor)\n{\n\tWNDCLASSW wc;\n\n\tZeroMemory(&wc, sizeof (WNDCLASSW));\n\twc.lpszClassName = areaClass;\n\twc.lpfnWndProc = areaWndProc;\n\twc.hInstance = hInstance;\n\twc.hIcon = hDefaultIcon;\n\twc.hCursor = hDefaultCursor;\n\twc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);\n\t// this is just to be safe; see the InvalidateRect() call in the WM_WINDOWPOSCHANGED handler for more details\n\twc.style = CS_HREDRAW | CS_VREDRAW;\n\treturn RegisterClassW(&wc);\n}\n\nvoid unregisterArea(void)\n{\n\tif (UnregisterClassW(areaClass, hInstance) == 0)\n\t\tlogLastError(L\"error unregistering uiArea window class\");\n}\n\nvoid uiAreaSetSize(uiArea *a, int width, int height)\n{\n\ta->scrollWidth = width;\n\ta->scrollHeight = height;\n\tareaUpdateScroll(a);\n}\n\nvoid uiAreaQueueRedrawAll(uiArea *a)\n{\n\t// don't erase the background; we do that ourselves in doPaint()\n\tinvalidateRect(a->hwnd, NULL, FALSE);\n}\n\nvoid uiAreaScrollTo(uiArea *a, double x, double y, double width, double height)\n{\n\t// TODO\n}\n\nvoid uiAreaBeginUserWindowMove(uiArea *a)\n{\n\tHWND toplevel;\n\n\t// TODO restrict execution\n\tReleaseCapture();\t\t// TODO use properly and reset internal data structures\n\ttoplevel = parentToplevel(a->hwnd);\n\tif (toplevel == NULL) {\n\t\t// TODO\n\t\treturn;\n\t}\n\t// see http://stackoverflow.com/questions/40249940/how-do-i-initiate-a-user-mouse-driven-move-or-resize-for-custom-window-borders-o#40250654\n\tSendMessageW(toplevel, WM_SYSCOMMAND,\n\t\tSC_MOVE | 2, 0);\n}\n\nvoid uiAreaBeginUserWindowResize(uiArea *a, uiWindowResizeEdge edge)\n{\n\tHWND toplevel;\n\tWPARAM wParam;\n\n\t// TODO restrict execution\n\tReleaseCapture();\t\t// TODO use properly and reset internal data structures\n\ttoplevel = parentToplevel(a->hwnd);\n\tif (toplevel == NULL) {\n\t\t// TODO\n\t\treturn;\n\t}\n\t// see http://stackoverflow.com/questions/40249940/how-do-i-initiate-a-user-mouse-driven-move-or-resize-for-custom-window-borders-o#40250654\n\twParam = SC_SIZE;\n\tswitch (edge) {\n\tcase uiWindowResizeEdgeLeft:\n\t\twParam |= 1;\n\t\tbreak;\n\tcase uiWindowResizeEdgeTop:\n\t\twParam |= 3;\n\t\tbreak;\n\tcase uiWindowResizeEdgeRight:\n\t\twParam |= 2;\n\t\tbreak;\n\tcase uiWindowResizeEdgeBottom:\n\t\twParam |= 6;\n\t\tbreak;\n\tcase uiWindowResizeEdgeTopLeft:\n\t\twParam |= 4;\n\t\tbreak;\n\tcase uiWindowResizeEdgeTopRight:\n\t\twParam |= 5;\n\t\tbreak;\n\tcase uiWindowResizeEdgeBottomLeft:\n\t\twParam |= 7;\n\t\tbreak;\n\tcase uiWindowResizeEdgeBottomRight:\n\t\twParam |= 8;\n\t\tbreak;\n\t}\n\tSendMessageW(toplevel, WM_SYSCOMMAND,\n\t\twParam, 0);\n}\n\nuiArea *uiNewArea(uiAreaHandler *ah)\n{\n\tuiArea *a;\n\n\tuiWindowsNewControl(uiArea, a);\n\n\ta->ah = ah;\n\ta->scrolling = FALSE;\n\tuiprivClickCounterReset(&(a->cc));\n\n\t// a->hwnd is assigned in areaWndProc()\n\tuiWindowsEnsureCreateControlHWND(0,\n\t\tareaClass, L\"\",\n\t\t0,\n\t\thInstance, a,\n\t\tFALSE);\n\n\treturn a;\n}\n\nuiArea *uiNewScrollingArea(uiAreaHandler *ah, int width, int height)\n{\n\tuiArea *a;\n\n\tuiWindowsNewControl(uiArea, a);\n\n\ta->ah = ah;\n\ta->scrolling = TRUE;\n\ta->scrollWidth = width;\n\ta->scrollHeight = height;\n\tuiprivClickCounterReset(&(a->cc));\n\n\t// a->hwnd is assigned in areaWndProc()\n\tuiWindowsEnsureCreateControlHWND(0,\n\t\tareaClass, L\"\",\n\t\tWS_HSCROLL | WS_VSCROLL,\n\t\thInstance, a,\n\t\tFALSE);\n\n\t// set initial scrolling parameters\n\tareaUpdateScroll(a);\n\n\treturn a;\n}\n"
  },
  {
    "path": "windows/area.hpp",
    "content": "// 18 december 2015\n\n// TODOs\n// - things look very wrong on initial draw\n// - initial scrolling is not set properly\n// - should background be inherited from parent control?\n\nstruct uiArea {\n\tuiWindowsControl c;\n\tHWND hwnd;\n\tuiAreaHandler *ah;\n\n\tBOOL scrolling;\n\tint scrollWidth;\n\tint scrollHeight;\n\tint hscrollpos;\n\tint vscrollpos;\n\tint hwheelCarry;\n\tint vwheelCarry;\n\n\tuiprivClickCounter cc;\n\tBOOL capturing;\n\n\tBOOL inside;\n\tBOOL tracking;\n\n\tID2D1HwndRenderTarget *rt;\n};\n\n// areadraw.cpp\nextern BOOL areaDoDraw(uiArea *a, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *lResult);\nextern void areaDrawOnResize(uiArea *, RECT *);\n\n// areascroll.cpp\nextern BOOL areaDoScroll(uiArea *a, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *lResult);\nextern void areaScrollOnResize(uiArea *, RECT *);\nextern void areaUpdateScroll(uiArea *a);\n\n// areaevents.cpp\nextern BOOL areaDoEvents(uiArea *a, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *lResult);\n\n// areautil.cpp\nextern void loadAreaSize(uiArea *a, ID2D1RenderTarget *rt, double *width, double *height);\nextern void pixelsToDIP(uiArea *a, double *x, double *y);\nextern void dipToPixels(uiArea *a, double *x, double *y);\n"
  },
  {
    "path": "windows/areadraw.cpp",
    "content": "// 8 september 2015\n#include \"uipriv_windows.hpp\"\n#include \"area.hpp\"\n\nstatic HRESULT doPaint(uiArea *a, ID2D1RenderTarget *rt, RECT *clip)\n{\n\tuiAreaHandler *ah = a->ah;\n\tuiAreaDrawParams dp;\n\tCOLORREF bgcolorref;\n\tD2D1_COLOR_F bgcolor;\n\tD2D1_MATRIX_3X2_F scrollTransform;\n\n\t// no need to save or restore the graphics state to reset transformations;  it's handled by resetTarget() in draw.c, called during the following\n\tdp.Context = newContext(rt);\n\n\tloadAreaSize(a, rt, &(dp.AreaWidth), &(dp.AreaHeight));\n\n\tdp.ClipX = clip->left;\n\tdp.ClipY = clip->top;\n\tdp.ClipWidth = clip->right - clip->left;\n\tdp.ClipHeight = clip->bottom - clip->top;\n\tif (a->scrolling) {\n\t\tdp.ClipX += a->hscrollpos;\n\t\tdp.ClipY += a->vscrollpos;\n\t}\n\n\trt->BeginDraw();\n\n\tif (a->scrolling) {\n\t\tZeroMemory(&scrollTransform, sizeof (D2D1_MATRIX_3X2_F));\n\t\tscrollTransform._11 = 1;\n\t\tscrollTransform._22 = 1;\n\t\t// negative because we want nonzero scroll positions to move the drawing area up/left\n\t\tscrollTransform._31 = -a->hscrollpos;\n\t\tscrollTransform._32 = -a->vscrollpos;\n\t\trt->SetTransform(&scrollTransform);\n\t}\n\n\t// TODO push axis aligned clip\n\n\t// TODO only clear the clip area\n\t// TODO clear with actual background brush\n\tbgcolorref = GetSysColor(COLOR_BTNFACE);\n\tbgcolor.r = ((float) GetRValue(bgcolorref)) / 255.0;\n\t// due to utter apathy on Microsoft's part, GetGValue() does not work with MSVC's Run-Time Error Checks\n\t// it has not worked since 2008 and they have *never* fixed it\n\t// TODO now that -RTCc has just been deprecated entirely, should we switch back?\n\tbgcolor.g = ((float) ((BYTE) ((bgcolorref & 0xFF00) >> 8))) / 255.0;\n\tbgcolor.b = ((float) GetBValue(bgcolorref)) / 255.0;\n\tbgcolor.a = 1.0;\n\trt->Clear(&bgcolor);\n\n\t(*(ah->Draw))(ah, a, &dp);\n\n\tfreeContext(dp.Context);\n\n\t// TODO pop axis aligned clip\n\n\treturn rt->EndDraw(NULL, NULL);\n}\n\nstatic void onWM_PAINT(uiArea *a)\n{\n\tRECT clip;\n\tHRESULT hr;\n\n\t// do not clear the update rect; we do that ourselves in doPaint()\n\tif (GetUpdateRect(a->hwnd, &clip, FALSE) == 0) {\n\t\t// set a zero clip rect just in case GetUpdateRect() didn't change clip\n\t\tclip.left = 0;\n\t\tclip.top = 0;\n\t\tclip.right = 0;\n\t\tclip.bottom = 0;\n\t}\n\thr = doPaint(a, a->rt, &clip);\n\tswitch (hr) {\n\tcase S_OK:\n\t\tif (ValidateRect(a->hwnd, NULL) == 0)\n\t\t\tlogLastError(L\"error validating rect\");\n\t\tbreak;\n\tcase D2DERR_RECREATE_TARGET:\n\t\t// DON'T validate the rect\n\t\t// instead, simply drop the render target\n\t\t// we'll get another WM_PAINT and make the render target again\n\t\t// TODO would this require us to invalidate the entire client area?\n\t\ta->rt->Release();;\n\t\ta->rt = NULL;\n\t\tbreak;\n\tdefault:\n\t\tlogHRESULT(L\"error painting\", hr);\n\t}\n}\n\nstatic void onWM_PRINTCLIENT(uiArea *a, HDC dc)\n{\n\tID2D1DCRenderTarget *rt;\n\tRECT client;\n\tHRESULT hr;\n\n\tuiWindowsEnsureGetClientRect(a->hwnd, &client);\n\trt = makeHDCRenderTarget(dc, &client);\n\thr = doPaint(a, rt, &client);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error printing uiArea client area\", hr);\n\trt->Release();\n}\n\nBOOL areaDoDraw(uiArea *a, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *lResult)\n{\n\tswitch (uMsg) {\n\tcase WM_PAINT:\n\t\tonWM_PAINT(a);\n\t\t*lResult = 0;\n\t\treturn TRUE;\n\tcase WM_PRINTCLIENT:\n\t\tonWM_PRINTCLIENT(a, (HDC) wParam);\n\t\t*lResult = 0;\n\t\treturn TRUE;\n\t}\n\treturn FALSE;\n}\n\n// TODO only if the render target wasn't just created?\nvoid areaDrawOnResize(uiArea *a, RECT *newClient)\n{\n\tD2D1_SIZE_U size;\n\n\tsize.width = newClient->right - newClient->left;\n\tsize.height = newClient->bottom - newClient->top;\n\t// don't track the error; we'll get that in EndDraw()\n\t// see https://msdn.microsoft.com/en-us/library/windows/desktop/dd370994%28v=vs.85%29.aspx\n\ta->rt->Resize(&size);\n\n\t// according to Rick Brewster, we must always redraw the entire client area after calling ID2D1RenderTarget::Resize() (see http://stackoverflow.com/a/33222983/3408572)\n\t// we used to have a uiAreaHandler.RedrawOnResize() method to decide this; now you know why we don't anymore\n\tinvalidateRect(a->hwnd, NULL, TRUE);\n}\n"
  },
  {
    "path": "windows/areaevents.cpp",
    "content": "// 8 september 2015\n#include \"uipriv_windows.hpp\"\n#include \"area.hpp\"\n\n// 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?\n\nstatic uiModifiers getModifiers(void)\n{\n\tuiModifiers m = 0;\n\n\tif ((GetKeyState(VK_CONTROL) & 0x80) != 0)\n\t\tm |= uiModifierCtrl;\n\tif ((GetKeyState(VK_MENU) & 0x80) != 0)\n\t\tm |= uiModifierAlt;\n\tif ((GetKeyState(VK_SHIFT) & 0x80) != 0)\n\t\tm |= uiModifierShift;\n\tif ((GetKeyState(VK_LWIN) & 0x80) != 0)\n\t\tm |= uiModifierSuper;\n\tif ((GetKeyState(VK_RWIN) & 0x80) != 0)\n\t\tm |= uiModifierSuper;\n\treturn m;\n}\n\n/*\nWindows doesn't natively support mouse crossing events.\n\nTrackMouseEvent() (and its comctl32.dll wrapper _TrackMouseEvent()) both allow for a window to receive the WM_MOUSELEAVE message when the mouse leaves the client area. There's no equivalent WM_MOUSEENTER because it can be simulated (https://blogs.msdn.microsoft.com/oldnewthing/20031013-00/?p=42193).\n\nUnfortunately, WM_MOUSELEAVE does not get generated while the mouse is captured. We need to capture for drag behavior to work properly, so this isn't going to mix well.\n\nSo what we do:\n- on WM_MOUSEMOVE, if we don't have the capture, start tracking\n\t- this will handle the case of the capture being released while still in the area\n- on WM_MOUSELEAVE, mark that we are no longer tracking\n\t- Windows has already done the work of that for us; it's just a flag we use for the next part\n- when starting capture, stop tracking if we are tracking\n- if capturing, manually check if the pointer is in the client rect on each area event\n*/\nstatic void track(uiArea *a, BOOL tracking)\n{\n\tTRACKMOUSEEVENT tm;\n\n\t// do nothing if there's no change\n\tif (a->tracking && tracking)\n\t\treturn;\n\tif (!a->tracking && !tracking)\n\t\treturn;\n\n\ta->tracking = tracking;\n\tZeroMemory(&tm, sizeof (TRACKMOUSEEVENT));\n\ttm.cbSize = sizeof (TRACKMOUSEEVENT);\n\ttm.dwFlags = TME_LEAVE;\n\tif (!a->tracking)\n\t\ttm.dwFlags |= TME_CANCEL;\n\ttm.hwndTrack = a->hwnd;\n\tif (_TrackMouseEvent(&tm) == 0)\n\t\tlogLastError(L\"error setting up mouse tracking\");\n}\n\nstatic void capture(uiArea *a, BOOL capturing)\n{\n\t// do nothing if there's no change\n\tif (a->capturing && capturing)\n\t\treturn;\n\tif (!a->capturing && !capturing)\n\t\treturn;\n\n\t// change flag first as ReleaseCapture() sends WM_CAPTURECHANGED\n\ta->capturing = capturing;\n\tif (a->capturing) {\n\t\ttrack(a, FALSE);\n\t\tSetCapture(a->hwnd);\n\t} else\n\t\tif (ReleaseCapture() == 0)\n\t\t\tlogLastError(L\"error releasing capture on drag\");\n}\n\nstatic void areaMouseEvent(uiArea *a, int down, int  up, WPARAM wParam, LPARAM lParam)\n{\n\tuiAreaMouseEvent me;\n\tint button;\n\tPOINT clientpt;\n\tRECT client;\n\tBOOL inClient;\n\tdouble xpix, ypix;\n\n\tif (a->capturing) {\n\t\tclientpt.x = GET_X_LPARAM(lParam);\n\t\tclientpt.y = GET_Y_LPARAM(lParam);\n\t\tuiWindowsEnsureGetClientRect(a->hwnd, &client);\n\t\tinClient = PtInRect(&client, clientpt);\n\t\tif (inClient && !a->inside) {\n\t\t\ta->inside = TRUE;\n\t\t\t(*(a->ah->MouseCrossed))(a->ah, a, 0);\n\t\t\tuiprivClickCounterReset(&(a->cc));\n\t\t} else if (!inClient && a->inside) {\n\t\t\ta->inside = FALSE;\n\t\t\t(*(a->ah->MouseCrossed))(a->ah, a, 1);\n\t\t\tuiprivClickCounterReset(&(a->cc));\n\t\t}\n\t}\n\n\txpix = (double) GET_X_LPARAM(lParam);\n\typix = (double) GET_Y_LPARAM(lParam);\n\t// these are in pixels; we need points\n\tpixelsToDIP(a, &xpix, &ypix);\n\tme.X = xpix;\n\tme.Y = ypix;\n\tif (a->scrolling) {\n\t\tme.X += a->hscrollpos;\n\t\tme.Y += a->vscrollpos;\n\t}\n\n\tloadAreaSize(a, NULL, &(me.AreaWidth), &(me.AreaHeight));\n\n\tme.Down = down;\n\tme.Up = up;\n\tme.Count = 0;\n\tif (me.Down != 0)\n\t\t// 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)\n\t\t// 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\n\t\t// GetSystemMetrics() returns int, which is int32\n\t\tme.Count = uiprivClickCounterClick(&(a->cc), me.Down,\n\t\t\tme.X, me.Y,\n\t\t\tGetMessageTime(), GetDoubleClickTime(),\n\t\t\tGetSystemMetrics(SM_CXDOUBLECLK) / 2,\n\t\t\tGetSystemMetrics(SM_CYDOUBLECLK) / 2);\n\n\t// though wparam will contain control and shift state, let's just one function to get modifiers for both keyboard and mouse events; it'll work the same anyway since we have to do this for alt and windows key (super)\n\tme.Modifiers = getModifiers();\n\n\tbutton = me.Down;\n\tif (button == 0)\n\t\tbutton = me.Up;\n\tme.Held1To64 = 0;\n\tif (button != 1 && (wParam & MK_LBUTTON) != 0)\n\t\tme.Held1To64 |= 1 << 0;\n\tif (button != 2 && (wParam & MK_MBUTTON) != 0)\n\t\tme.Held1To64 |= 1 << 1;\n\tif (button != 3 && (wParam & MK_RBUTTON) != 0)\n\t\tme.Held1To64 |= 1 << 2;\n\tif (button != 4 && (wParam & MK_XBUTTON1) != 0)\n\t\tme.Held1To64 |= 1 << 3;\n\tif (button != 5 && (wParam & MK_XBUTTON2) != 0)\n\t\tme.Held1To64 |= 1 << 4;\n\n\t// on Windows, we have to capture on drag ourselves\n\tif (me.Down != 0)\n\t\tcapture(a, TRUE);\n\t// only release capture when all buttons released\n\tif (me.Up != 0 && me.Held1To64 == 0)\n\t\tcapture(a, FALSE);\n\n\t(*(a->ah->MouseEvent))(a->ah, a, &me);\n}\n\n// TODO genericize this so it can be called above\nstatic void onMouseEntered(uiArea *a)\n{\n\tif (a->inside)\n\t\treturn;\n\tif (a->capturing)\t\t// we handle mouse crossing in areaMouseEvent()\n\t\treturn;\n\ttrack(a, TRUE);\n\t(*(a->ah->MouseCrossed))(a->ah, a, 0);\n\t// TODO figure out why we did this to begin with; either we do it on both GTK+ and Windows or not at all\n\tuiprivClickCounterReset(&(a->cc));\n}\n\n// TODO genericize it so that it can be called above\nstatic void onMouseLeft(uiArea *a)\n{\n\ta->tracking = FALSE;\n\ta->inside = FALSE;\n\t(*(a->ah->MouseCrossed))(a->ah, a, 1);\n\t// TODO figure out why we did this to begin with; either we do it on both GTK+ and Windows or not at all\n\tuiprivClickCounterReset(&(a->cc));\n}\n\n// we use VK_SNAPSHOT as a sentinel because libui will never support the print screen key; that key belongs to the user\nstruct extkeymap {\n\tWPARAM vk;\n\tuiExtKey extkey;\n};\n\n// all mappings come from GLFW - https://github.com/glfw/glfw/blob/master/src/win32_window.c#L152\nstatic const struct extkeymap numpadExtKeys[] = {\n\t{ VK_HOME, uiExtKeyN7 },\n\t{ VK_UP, uiExtKeyN8 },\n\t{ VK_PRIOR, uiExtKeyN9 },\n\t{ VK_LEFT, uiExtKeyN4 },\n\t{ VK_CLEAR, uiExtKeyN5 },\n\t{ VK_RIGHT, uiExtKeyN6 },\n\t{ VK_END, uiExtKeyN1 },\n\t{ VK_DOWN, uiExtKeyN2 },\n\t{ VK_NEXT, uiExtKeyN3 },\n\t{ VK_INSERT, uiExtKeyN0 },\n\t{ VK_DELETE, uiExtKeyNDot },\n\t{ VK_SNAPSHOT, 0 },\n};\n\nstatic const struct extkeymap extKeys[] = {\n\t{ VK_ESCAPE, uiExtKeyEscape },\n\t{ VK_INSERT, uiExtKeyInsert },\n\t{ VK_DELETE, uiExtKeyDelete },\n\t{ VK_HOME, uiExtKeyHome },\n\t{ VK_END, uiExtKeyEnd },\n\t{ VK_PRIOR, uiExtKeyPageUp },\n\t{ VK_NEXT, uiExtKeyPageDown },\n\t{ VK_UP, uiExtKeyUp },\n\t{ VK_DOWN, uiExtKeyDown },\n\t{ VK_LEFT, uiExtKeyLeft },\n\t{ VK_RIGHT, uiExtKeyRight },\n\t{ VK_F1, uiExtKeyF1 },\n\t{ VK_F2, uiExtKeyF2 },\n\t{ VK_F3, uiExtKeyF3 },\n\t{ VK_F4, uiExtKeyF4 },\n\t{ VK_F5, uiExtKeyF5 },\n\t{ VK_F6, uiExtKeyF6 },\n\t{ VK_F7, uiExtKeyF7 },\n\t{ VK_F8, uiExtKeyF8 },\n\t{ VK_F9, uiExtKeyF9 },\n\t{ VK_F10, uiExtKeyF10 },\n\t{ VK_F11, uiExtKeyF11 },\n\t{ VK_F12, uiExtKeyF12 },\n\t// numpad numeric keys and . are handled in common/areaevents.c\n\t// numpad enter is handled in code below\n\t{ VK_ADD, uiExtKeyNAdd },\n\t{ VK_SUBTRACT, uiExtKeyNSubtract },\n\t{ VK_MULTIPLY, uiExtKeyNMultiply },\n\t{ VK_DIVIDE, uiExtKeyNDivide },\n\t{ VK_SNAPSHOT, 0 },\n};\n\nstatic const struct {\n\tWPARAM vk;\n\tuiModifiers mod;\n} modKeys[] = {\n\t// even if the separate left/right aren't necessary, have them here anyway, just to be safe\n\t{ VK_CONTROL, uiModifierCtrl },\n\t{ VK_LCONTROL, uiModifierCtrl },\n\t{ VK_RCONTROL, uiModifierCtrl },\n\t{ VK_MENU, uiModifierAlt },\n\t{ VK_LMENU, uiModifierAlt },\n\t{ VK_RMENU, uiModifierAlt },\n\t{ VK_SHIFT, uiModifierShift },\n\t{ VK_LSHIFT, uiModifierShift },\n\t{ VK_RSHIFT, uiModifierShift },\n\t// there's no combined Windows key virtual-key code as there is with the others\n\t{ VK_LWIN, uiModifierSuper },\n\t{ VK_RWIN, uiModifierSuper },\n\t{ VK_SNAPSHOT, 0 },\n};\n\nstatic int areaKeyEvent(uiArea *a, int up, WPARAM wParam, LPARAM lParam)\n{\n\tuiAreaKeyEvent ke;\n\tint righthand;\n\tint i;\n\n\tke.Key = 0;\n\tke.ExtKey = 0;\n\tke.Modifier = 0;\n\n\tke.Modifiers = getModifiers();\n\n\tke.Up = up;\n\n\t// the numeric keypad keys when Num Lock is off are considered left-hand keys as the separate navigation buttons were added later\n\t// the numeric keypad Enter, however, is a right-hand key because it has the same virtual-key code as the typewriter Enter\n\trighthand = (lParam & 0x01000000) != 0;\n\tif (righthand) {\n\t\tif (wParam == VK_RETURN) {\n\t\t\tke.ExtKey = uiExtKeyNEnter;\n\t\t\tgoto keyFound;\n\t\t}\n\t} else\n\t\t// this is special handling for numpad keys to ignore the state of Num Lock and Shift; see http://blogs.msdn.com/b/oldnewthing/archive/2004/09/06/226045.aspx and https://github.com/glfw/glfw/blob/master/src/win32_window.c#L152\n\t\tfor (i = 0; numpadExtKeys[i].vk != VK_SNAPSHOT; i++)\n\t\t\tif (numpadExtKeys[i].vk == wParam) {\n\t\t\t\tke.ExtKey = numpadExtKeys[i].extkey;\n\t\t\t\tgoto keyFound;\n\t\t\t}\n\n\t// okay, those above cases didn't match anything\n\t// first try the extended keys\n\tfor (i = 0; extKeys[i].vk != VK_SNAPSHOT; i++)\n\t\tif (extKeys[i].vk == wParam) {\n\t\t\tke.ExtKey = extKeys[i].extkey;\n\t\t\tgoto keyFound;\n\t\t}\n\n\t// then try modifier keys\n\tfor (i = 0; modKeys[i].vk != VK_SNAPSHOT; i++)\n\t\tif (modKeys[i].vk == wParam) {\n\t\t\tke.Modifier = modKeys[i].mod;\n\t\t\t// and don't include the key in Modifiers\n\t\t\tke.Modifiers &= ~ke.Modifier;\n\t\t\tgoto keyFound;\n\t\t}\n\n\t// and finally everything else\n\tif (uiprivFromScancode((lParam >> 16) & 0xFF, &ke))\n\t\tgoto keyFound;\n\n\t// not a supported key, assume unhandled\n\t// TODO the original code only did this if ke.Modifiers == 0 - why?\n\treturn 0;\n\nkeyFound:\n\treturn (*(a->ah->KeyEvent))(a->ah, a, &ke);\n}\n\n// We don't handle the standard Windows keyboard messages directly, to avoid both the dialog manager and TranslateMessage().\n// Instead, we set up a message filter and do things there.\n// That stuff is later in this file.\nenum {\n\t// start at 0x40 to avoid clobbering dialog messages\n\tmsgAreaKeyDown = WM_USER + 0x40,\n\tmsgAreaKeyUp,\n};\n\nBOOL areaDoEvents(uiArea *a, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *lResult)\n{\n\tswitch (uMsg) {\n\tcase WM_ACTIVATE:\n\t\t// don't keep the double-click timer running if the user switched programs in between clicks\n\t\tuiprivClickCounterReset(&(a->cc));\n\t\t*lResult = 0;\n\t\treturn TRUE;\n\tcase WM_MOUSEMOVE:\n\t\tonMouseEntered(a);\n\t\tareaMouseEvent(a, 0, 0, wParam, lParam);\n\t\t*lResult = 0;\n\t\treturn TRUE;\n\tcase WM_MOUSELEAVE:\n\t\tonMouseLeft(a);\n\t\t*lResult = 0;\n\t\treturn TRUE;\n\tcase WM_LBUTTONDOWN:\n\t\tSetFocus(a->hwnd);\n\t\tareaMouseEvent(a, 1, 0, wParam, lParam);\n\t\t*lResult = 0;\n\t\treturn TRUE;\n\tcase WM_LBUTTONUP:\n\t\tareaMouseEvent(a, 0, 1, wParam, lParam);\n\t\t*lResult = 0;\n\t\treturn TRUE;\n\tcase WM_MBUTTONDOWN:\n\t\tSetFocus(a->hwnd);\n\t\tareaMouseEvent(a, 2, 0, wParam, lParam);\n\t\t*lResult = 0;\n\t\treturn TRUE;\n\tcase WM_MBUTTONUP:\n\t\tareaMouseEvent(a, 0, 2, wParam, lParam);\n\t\t*lResult = 0;\n\t\treturn TRUE;\n\tcase WM_RBUTTONDOWN:\n\t\tSetFocus(a->hwnd);\n\t\tareaMouseEvent(a, 3, 0, wParam, lParam);\n\t\t*lResult = 0;\n\t\treturn TRUE;\n\tcase WM_RBUTTONUP:\n\t\tareaMouseEvent(a, 0, 3, wParam, lParam);\n\t\t*lResult = 0;\n\t\treturn TRUE;\n\tcase WM_XBUTTONDOWN:\n\t\tSetFocus(a->hwnd);\n\t\t// values start at 1; we want them to start at 4\n\t\tareaMouseEvent(a,\n\t\t\tGET_XBUTTON_WPARAM(wParam) + 3, 0,\n\t\t\tGET_KEYSTATE_WPARAM(wParam), lParam);\n\t\t*lResult = TRUE;\t// XBUTTON messages are different!\n\t\treturn TRUE;\n\tcase WM_XBUTTONUP:\n\t\tareaMouseEvent(a,\n\t\t\t0, GET_XBUTTON_WPARAM(wParam) + 3,\n\t\t\tGET_KEYSTATE_WPARAM(wParam), lParam);\n\t\t*lResult = TRUE;\t// XBUTTON messages are different!\n\t\treturn TRUE;\n\tcase WM_CAPTURECHANGED:\n\t\tif (a->capturing) {\n\t\t\ta->capturing = FALSE;\n\t\t\t(*(a->ah->DragBroken))(a->ah, a);\n\t\t}\n\t\t*lResult = 0;\n\t\treturn TRUE;\n\tcase msgAreaKeyDown:\n\t\t*lResult = (LRESULT) areaKeyEvent(a, 0, wParam, lParam);\n\t\treturn TRUE;\n\tcase msgAreaKeyUp:\n\t\t*lResult = (LRESULT) areaKeyEvent(a, 1, wParam, lParam);\n\t\treturn TRUE;\n\t}\n\treturn FALSE;\n}\n\n// TODO affect visibility properly\n// TODO what did this mean\nBOOL areaFilter(MSG *msg)\n{\n\tLRESULT handled;\n\n\t// is the recipient an area?\n\tif (msg->hwnd == NULL)\t\t// this can happen; for example, WM_TIMER\n\t\treturn FALSE;\n\tif (windowClassOf(msg->hwnd, areaClass, NULL) != 0)\n\t\treturn FALSE;\t\t\t// nope\n\n\thandled = 0;\n\tswitch (msg->message) {\n\tcase WM_KEYDOWN:\n\tcase WM_SYSKEYDOWN:\n\t\thandled = SendMessageW(msg->hwnd, msgAreaKeyDown, msg->wParam, msg->lParam);\n\t\tbreak;\n\tcase WM_KEYUP:\n\tcase WM_SYSKEYUP:\n\t\thandled = SendMessageW(msg->hwnd, msgAreaKeyUp, msg->wParam, msg->lParam);\n\t\tbreak;\n\t// otherwise handled remains 0, as we didn't handle this\n\t}\n\treturn (BOOL) handled;\n}\n"
  },
  {
    "path": "windows/areascroll.cpp",
    "content": "// 8 september 2015\n#include \"uipriv_windows.hpp\"\n#include \"area.hpp\"\n\n// TODO\n// - move from pixels to points somehow\n// \t- add a function to offset points and rects by scrolling amounts; call it from doPaint() in areadraw.c\n// - recalculate scrolling after:\n// \t- creation?\n// \t- resize?\n// \t- recreating the render target? (after moving to points)\n// - error if these are called without scrollbars?\n\nstruct scrollParams {\n\tint *pos;\n\tint pagesize;\n\tint length;\n\tint *wheelCarry;\n\tUINT wheelSPIAction;\n};\n\nstatic void scrollto(uiArea *a, int which, struct scrollParams *p, int pos)\n{\n\tSCROLLINFO si;\n\n\t// note that the pos < 0 check is /after/ the p->length - p->pagesize check\n\t// it used to be /before/; this was actually a bug in Raymond Chen's original algorithm: if there are fewer than a page's worth of items, p->length - p->pagesize will be negative and our content draw at the bottom of the window\n\t// this SHOULD have the same effect with that bug fixed and no others introduced... (thanks to devin on irc.badnik.net for confirming this logic)\n\tif (pos > p->length - p->pagesize)\n\t\tpos = p->length - p->pagesize;\n\tif (pos < 0)\n\t\tpos = 0;\n\n\t// Direct2D doesn't have a method for scrolling the existing contents of a render target.\n\t// We'll have to just invalidate everything and hope for the best.\n\tinvalidateRect(a->hwnd, NULL, FALSE);\n\n\t*(p->pos) = pos;\n\n\t// now commit our new scrollbar setup...\n\tZeroMemory(&si, sizeof (SCROLLINFO));\n\tsi.cbSize = sizeof (SCROLLINFO);\n\tsi.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;\n\tsi.nPage = p->pagesize;\n\tsi.nMin = 0;\n\tsi.nMax = p->length - 1;\t\t// endpoint inclusive\n\tsi.nPos = *(p->pos);\n\tSetScrollInfo(a->hwnd, which, &si, TRUE);\n}\n\nstatic void scrollby(uiArea *a, int which, struct scrollParams *p, int delta)\n{\n\tscrollto(a, which, p, *(p->pos) + delta);\n}\n\nstatic void scroll(uiArea *a, int which, struct scrollParams *p, WPARAM wParam, LPARAM lParam)\n{\n\tint pos;\n\tSCROLLINFO si;\n\n\tpos = *(p->pos);\n\tswitch (LOWORD(wParam)) {\n\tcase SB_LEFT:\t\t\t// also SB_TOP\n\t\tpos = 0;\n\t\tbreak;\n\tcase SB_RIGHT:\t\t// also SB_BOTTOM\n\t\tpos = p->length - p->pagesize;\n\t\tbreak;\n\tcase SB_LINELEFT:\t\t// also SB_LINEUP\n\t\tpos--;\n\t\tbreak;\n\tcase SB_LINERIGHT:\t\t// also SB_LINEDOWN\n\t\tpos++;\n\t\tbreak;\n\tcase SB_PAGELEFT:\t\t// also SB_PAGEUP\n\t\tpos -= p->pagesize;\n\t\tbreak;\n\tcase SB_PAGERIGHT:\t// also SB_PAGEDOWN\n\t\tpos += p->pagesize;\n\t\tbreak;\n\tcase SB_THUMBPOSITION:\n\t\tZeroMemory(&si, sizeof (SCROLLINFO));\n\t\tsi.cbSize = sizeof (SCROLLINFO);\n\t\tsi.fMask = SIF_POS;\n\t\tif (GetScrollInfo(a->hwnd, which, &si) == 0)\n\t\t\tlogLastError(L\"error getting thumb position for area\");\n\t\tpos = si.nPos;\n\t\tbreak;\n\tcase SB_THUMBTRACK:\n\t\tZeroMemory(&si, sizeof (SCROLLINFO));\n\t\tsi.cbSize = sizeof (SCROLLINFO);\n\t\tsi.fMask = SIF_TRACKPOS;\n\t\tif (GetScrollInfo(a->hwnd, which, &si) == 0)\n\t\t\tlogLastError(L\"error getting thumb track position for area\");\n\t\tpos = si.nTrackPos;\n\t\tbreak;\n\t}\n\tscrollto(a, which, p, pos);\n}\n\nstatic void wheelscroll(uiArea *a, int which, struct scrollParams *p, WPARAM wParam, LPARAM lParam)\n{\n\tint delta;\n\tint lines;\n\tUINT scrollAmount;\n\n\tdelta = GET_WHEEL_DELTA_WPARAM(wParam);\n\tif (SystemParametersInfoW(p->wheelSPIAction, 0, &scrollAmount, 0) == 0)\n\t\t// TODO use scrollAmount == 3 (for both v and h) instead?\n\t\tlogLastError(L\"error getting area wheel scroll amount\");\n\tif (scrollAmount == WHEEL_PAGESCROLL)\n\t\tscrollAmount = p->pagesize;\n\tif (scrollAmount == 0)\t\t// no mouse wheel scrolling (or t->pagesize == 0)\n\t\treturn;\n\t// the rest of this is basically http://blogs.msdn.com/b/oldnewthing/archive/2003/08/07/54615.aspx and http://blogs.msdn.com/b/oldnewthing/archive/2003/08/11/54624.aspx\n\t// see those pages for information on subtleties\n\tdelta += *(p->wheelCarry);\n\tlines = delta * ((int) scrollAmount) / WHEEL_DELTA;\n\t*(p->wheelCarry) = delta - lines * WHEEL_DELTA / ((int) scrollAmount);\n\tscrollby(a, which, p, -lines);\n}\n\nstatic void hscrollParams(uiArea *a, struct scrollParams *p)\n{\n\tRECT r;\n\n\tZeroMemory(p, sizeof (struct scrollParams));\n\tp->pos = &(a->hscrollpos);\n\t// TODO get rid of these and replace with points\n\tuiWindowsEnsureGetClientRect(a->hwnd, &r);\n\tp->pagesize = r.right - r.left;\n\tp->length = a->scrollWidth;\n\tp->wheelCarry = &(a->hwheelCarry);\n\tp->wheelSPIAction = SPI_GETWHEELSCROLLCHARS;\n}\n\nstatic void hscrollto(uiArea *a, int pos)\n{\n\tstruct scrollParams p;\n\n\thscrollParams(a, &p);\n\tscrollto(a, SB_HORZ, &p, pos);\n}\n\nstatic void hscrollby(uiArea *a, int delta)\n{\n\tstruct scrollParams p;\n\n\thscrollParams(a, &p);\n\tscrollby(a, SB_HORZ, &p, delta);\n}\n\nstatic void hscroll(uiArea *a, WPARAM wParam, LPARAM lParam)\n{\n\tstruct scrollParams p;\n\n\thscrollParams(a, &p);\n\tscroll(a, SB_HORZ, &p, wParam, lParam);\n}\n\nstatic void hwheelscroll(uiArea *a, WPARAM wParam, LPARAM lParam)\n{\n\tstruct scrollParams p;\n\n\thscrollParams(a, &p);\n\twheelscroll(a, SB_HORZ, &p, wParam, lParam);\n}\n\nstatic void vscrollParams(uiArea *a, struct scrollParams *p)\n{\n\tRECT r;\n\n\tZeroMemory(p, sizeof (struct scrollParams));\n\tp->pos = &(a->vscrollpos);\n\tuiWindowsEnsureGetClientRect(a->hwnd, &r);\n\tp->pagesize = r.bottom - r.top;\n\tp->length = a->scrollHeight;\n\tp->wheelCarry = &(a->vwheelCarry);\n\tp->wheelSPIAction = SPI_GETWHEELSCROLLLINES;\n}\n\nstatic void vscrollto(uiArea *a, int pos)\n{\n\tstruct scrollParams p;\n\n\tvscrollParams(a, &p);\n\tscrollto(a, SB_VERT, &p, pos);\n}\n\nstatic void vscrollby(uiArea *a, int delta)\n{\n\tstruct scrollParams p;\n\n\tvscrollParams(a, &p);\n\tscrollby(a, SB_VERT, &p, delta);\n}\n\nstatic void vscroll(uiArea *a, WPARAM wParam, LPARAM lParam)\n{\n\tstruct scrollParams p;\n\n\tvscrollParams(a, &p);\n\tscroll(a, SB_VERT, &p, wParam, lParam);\n}\n\nstatic void vwheelscroll(uiArea *a, WPARAM wParam, LPARAM lParam)\n{\n\tstruct scrollParams p;\n\n\tvscrollParams(a, &p);\n\twheelscroll(a, SB_VERT, &p, wParam, lParam);\n}\n\nBOOL areaDoScroll(uiArea *a, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *lResult)\n{\n\tswitch (uMsg) {\n\tcase WM_HSCROLL:\n\t\thscroll(a, wParam, lParam);\n\t\t*lResult = 0;\n\t\treturn TRUE;\n\tcase WM_MOUSEHWHEEL:\n\t\thwheelscroll(a, wParam, lParam);\n\t\t*lResult = 0;\n\t\treturn TRUE;\n\tcase WM_VSCROLL:\n\t\tvscroll(a, wParam, lParam);\n\t\t*lResult = 0;\n\t\treturn TRUE;\n\tcase WM_MOUSEWHEEL:\n\t\tvwheelscroll(a, wParam, lParam);\n\t\t*lResult = 0;\n\t\treturn TRUE;\n\t}\n\treturn FALSE;\n}\n\nvoid areaScrollOnResize(uiArea *a, RECT *client)\n{\n\tareaUpdateScroll(a);\n}\n\nvoid areaUpdateScroll(uiArea *a)\n{\n\t// use a no-op scroll to simulate scrolling\n\thscrollby(a, 0);\n\tvscrollby(a, 0);\n}\n"
  },
  {
    "path": "windows/areautil.cpp",
    "content": "// 18 december 2015\n#include \"uipriv_windows.hpp\"\n#include \"area.hpp\"\n\nvoid loadAreaSize(uiArea *a, ID2D1RenderTarget *rt, double *width, double *height)\n{\n\tD2D1_SIZE_F size;\n\n\t*width = 0;\n\t*height = 0;\n\tif (!a->scrolling) {\n\t\tif (rt == NULL)\n\t\t\trt = a->rt;\n\t\tsize = realGetSize(rt);\n\t\t*width = size.width;\n\t\t*height = size.height;\n\t}\n}\n\nvoid pixelsToDIP(uiArea *a, double *x, double *y)\n{\n\tFLOAT dpix, dpiy;\n\n\ta->rt->GetDpi(&dpix, &dpiy);\n\t// see https://msdn.microsoft.com/en-us/library/windows/desktop/dd756649%28v=vs.85%29.aspx (and others; search \"direct2d mouse\")\n\t*x = (*x * 96) / dpix;\n\t*y = (*y * 96) / dpiy;\n}\n\nvoid dipToPixels(uiArea *a, double *x, double *y)\n{\n\tFLOAT dpix, dpiy;\n\n\ta->rt->GetDpi(&dpix, &dpiy);\n\t*x = (*x * dpix) / 96;\n\t*y = (*y * dpiy) / 96;\n}\n"
  },
  {
    "path": "windows/attrstr.cpp",
    "content": "// 12 february 2017\n#include \"uipriv_windows.hpp\"\n#include \"attrstr.hpp\"\n\n// TODO this whole file needs cleanup\n\n// yep, even when it supports C++11, it doesn't support C++11\n// we require MSVC 2013; this was added in MSVC 2015 (https://msdn.microsoft.com/en-us/library/wfa0edys.aspx)\n#ifdef _MSC_VER\n#if _MSC_VER < 1900\n#define noexcept\n#endif\n#endif\n\n// we need to collect all the background parameters and add them all at once\n// TODO consider having background parameters in the drawing effects\n// TODO contextual alternates override ligatures?\n// TODO rename this struct to something that isn't exclusively foreach-ing?\nstruct foreachParams {\n\tconst uint16_t *s;\n\tsize_t len;\n\tIDWriteTextLayout *layout;\n\tstd::vector<struct drawTextBackgroundParams *> *backgroundParams;\n};\n\nstatic std::hash<double> doubleHash;\n\n// we need to combine color and underline style into one unit for IDWriteLayout::SetDrawingEffect()\n// we also want to combine identical effects, which DirectWrite doesn't seem to provide a way to do\n// we can at least try to goad it into doing so if we can deduplicate effects once they're all computed\n// so what we do is use this class to store in-progress effects, much like uiprivCombinedFontAttr on the OS X code\n// we then deduplicate them later while converting them into a form suitable for drawing with; see applyEffectsAttributes() below\nclass combinedEffectsAttr : public IUnknown {\n\tULONG refcount;\n\tuiAttribute *colorAttr;\n\tuiAttribute *underlineAttr;\n\tuiAttribute *underlineColorAttr;\n\n\tvoid setAttribute(uiAttribute *a)\n\t{\n\t\tif (a == NULL)\n\t\t\treturn;\n\t\tswitch (uiAttributeGetType(a)) {\n\t\tcase uiAttributeTypeColor:\n\t\t\tif (this->colorAttr != NULL)\n\t\t\t\tuiprivAttributeRelease(this->colorAttr);\n\t\t\tthis->colorAttr = uiprivAttributeRetain(a);\n\t\t\tbreak;\n\t\tcase uiAttributeTypeUnderline:\n\t\t\tif (this->underlineAttr != NULL)\n\t\t\t\tuiprivAttributeRelease(this->underlineAttr);\n\t\t\tthis->underlineAttr = uiprivAttributeRetain(a);\n\t\t\tbreak;\n\t\tcase uiAttributeTypeUnderlineColor:\n\t\t\tif (this->underlineAttr != NULL)\n\t\t\t\tuiprivAttributeRelease(this->underlineAttr);\n\t\t\tthis->underlineColorAttr = uiprivAttributeRetain(a);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// this is needed by applyEffectsAttributes() below\n\t// 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\n\tstatic bool attrEqual(uiAttribute *a, uiAttribute *b)\n\t{\n\t\tif (a == NULL && b == NULL)\n\t\t\treturn true;\n\t\tif (a == NULL || b == NULL)\n\t\t\treturn false;\n\t\treturn uiprivAttributeEqual(a, b);\n\t}\npublic:\n\tcombinedEffectsAttr(uiAttribute *a)\n\t{\n\t\tthis->refcount = 1;\n\t\tthis->colorAttr = NULL;\n\t\tthis->underlineAttr = NULL;\n\t\tthis->underlineColorAttr = NULL;\n\t\tthis->setAttribute(a);\n\t}\n\n\t~combinedEffectsAttr()\n\t{\n\t\tif (this->colorAttr != NULL)\n\t\t\tuiprivAttributeRelease(this->colorAttr);\n\t\tif (this->underlineAttr != NULL)\n\t\t\tuiprivAttributeRelease(this->underlineAttr);\n\t\tif (this->underlineColorAttr != NULL)\n\t\t\tuiprivAttributeRelease(this->underlineColorAttr);\n\t}\n\n\t// IUnknown\n\tvirtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject)\n\t{\n\t\tif (ppvObject == NULL)\n\t\t\treturn E_POINTER;\n\t\tif (riid == IID_IUnknown) {\n\t\t\tthis->AddRef();\n\t\t\t*ppvObject = this;\n\t\t\treturn S_OK;\n\t\t}\n\t\t*ppvObject = NULL;\n\t\treturn E_NOINTERFACE;\n\t}\n\n\tvirtual ULONG STDMETHODCALLTYPE AddRef(void)\n\t{\n\t\tthis->refcount++;\n\t\treturn this->refcount;\n\t}\n\n\tvirtual ULONG STDMETHODCALLTYPE Release(void)\n\t{\n\t\tthis->refcount--;\n\t\tif (this->refcount == 0) {\n\t\t\tdelete this;\n\t\t\treturn 0;\n\t\t}\n\t\treturn this->refcount;\n\t}\n\n\tcombinedEffectsAttr *cloneWith(uiAttribute *a)\n\t{\n\t\tcombinedEffectsAttr *b;\n\n\t\tb = new combinedEffectsAttr(this->colorAttr);\n\t\tb->setAttribute(this->underlineAttr);\n\t\tb->setAttribute(this->underlineColorAttr);\n\t\tb->setAttribute(a);\n\t\treturn b;\n\t}\n\n\t// and these are also needed by applyEffectsAttributes() below\n\tsize_t hash(void) const noexcept\n\t{\n\t\tsize_t ret = 0;\n\t\tdouble r, g, b, a;\n\t\tuiUnderlineColor colorType;\n\n\t\tif (this->colorAttr != NULL) {\n\t\t\tuiAttributeColor(this->colorAttr, &r, &g, &b, &a);\n\t\t\tret ^= doubleHash(r);\n\t\t\tret ^= doubleHash(g);\n\t\t\tret ^= doubleHash(b);\n\t\t\tret ^= doubleHash(a);\n\t\t}\n\t\tif (this->underlineAttr != NULL)\n\t\t\tret ^= (size_t) uiAttributeUnderline(this->underlineAttr);\n\t\tif (this->underlineColorAttr != NULL) {\n\t\t\tuiAttributeUnderlineColor(this->underlineColorAttr, &colorType, &r, &g, &b, &a);\n\t\t\tret ^= (size_t) colorType;\n\t\t\tret ^= doubleHash(r);\n\t\t\tret ^= doubleHash(g);\n\t\t\tret ^= doubleHash(b);\n\t\t\tret ^= doubleHash(a);\n\t\t}\n\t\treturn ret;\n\t}\n\n\tbool equals(const combinedEffectsAttr *b) const\n\t{\n\t\tif (b == NULL)\n\t\t\treturn false;\n\t\treturn combinedEffectsAttr::attrEqual(this->colorAttr, b->colorAttr) &&\n\t\t\tcombinedEffectsAttr::attrEqual(this->underlineAttr, b->underlineAttr) &&\n\t\t\tcombinedEffectsAttr::attrEqual(this->underlineColorAttr, b->underlineColorAttr);\n\t}\n\n\tdrawingEffectsAttr *toDrawingEffectsAttr(void)\n\t{\n\t\tdrawingEffectsAttr *dea;\n\t\tdouble r, g, b, a;\n\t\tuiUnderlineColor colorType;\n\n\t\tdea = new drawingEffectsAttr;\n\t\tif (this->colorAttr != NULL) {\n\t\t\tuiAttributeColor(this->colorAttr, &r, &g, &b, &a);\n\t\t\tdea->setColor(r, g, b, a);\n\t\t}\n\t\tif (this->underlineAttr != NULL)\n\t\t\tdea->setUnderline(uiAttributeUnderline(this->underlineAttr));\n\t\tif (this->underlineColorAttr != NULL) {\n\t\t\tuiAttributeUnderlineColor(this->underlineColorAttr, &colorType, &r, &g, &b, &a);\n\t\t\t// TODO see if Microsoft has any standard colors for these\n\t\t\tswitch (colorType) {\n\t\t\tcase uiUnderlineColorSpelling:\n\t\t\t\t// TODO consider using the GtkTextView style property error-underline-color here if Microsoft has no preference\n\t\t\t\tr = 1.0;\n\t\t\t\tg = 0.0;\n\t\t\t\tb = 0.0;\n\t\t\t\ta = 1.0;\n\t\t\t\tbreak;\n\t\t\tcase uiUnderlineColorGrammar:\n\t\t\t\tr = 0.0;\n\t\t\t\tg = 1.0;\n\t\t\t\tb = 0.0;\n\t\t\t\ta = 1.0;\n\t\t\t\tbreak;\n\t\t\tcase uiUnderlineColorAuxiliary:\n\t\t\t\tr = 0.0;\n\t\t\t\tg = 0.0;\n\t\t\t\tb = 1.0;\n\t\t\t\ta = 1.0;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdea->setUnderlineColor(r, g, b, a);\n\t\t}\n\t\treturn dea;\n\t}\n};\n\n// also needed by applyEffectsAttributes() below\n// TODO provide all the fields of std::hash and std::equal_to?\nclass applyEffectsHash {\npublic:\n\ttypedef combinedEffectsAttr *ceaptr;\n\tsize_t operator()(applyEffectsHash::ceaptr const &cea) const noexcept\n\t{\n\t\treturn cea->hash();\n\t}\n};\n\nclass applyEffectsEqualTo {\npublic:\n\ttypedef combinedEffectsAttr *ceaptr;\n\tbool operator()(const applyEffectsEqualTo::ceaptr &a, const applyEffectsEqualTo::ceaptr &b) const\n\t{\n\t\treturn a->equals(b);\n\t}\n};\n\nstatic HRESULT addEffectAttributeToRange(struct foreachParams *p, size_t start, size_t end, uiAttribute *attr)\n{\n\tIUnknown *u;\n\tcombinedEffectsAttr *cea;\n\tDWRITE_TEXT_RANGE range;\n\tsize_t diff;\n\tHRESULT hr;\n\n\twhile (start < end) {\n\t\thr = p->layout->GetDrawingEffect(start, &u, &range);\n\t\tif (hr != S_OK)\n\t\t\treturn hr;\n\t\tcea = (combinedEffectsAttr *) u;\n\t\tif (cea == NULL)\n\t\t\tcea = new combinedEffectsAttr(attr);\n\t\telse\n\t\t\tcea = cea->cloneWith(attr);\n\t\t// clamp range within [start, end)\n\t\tif (range.startPosition < start) {\n\t\t\tdiff = start - range.startPosition;\n\t\t\trange.startPosition = start;\n\t\t\trange.length -= diff;\n\t\t}\n\t\tif ((range.startPosition + range.length) > end)\n\t\t\trange.length = end - range.startPosition;\n\t\thr = p->layout->SetDrawingEffect(cea, range);\n\t\t// SetDrawingEffect will AddRef(), so Release() our copy\n\t\t// (and we're abandoning early if that failed, so this will make sure things are cleaned up in that case)\n\t\tcea->Release();\n\t\tif (hr != S_OK)\n\t\t\treturn hr;\n\t\tstart += range.length;\n\t}\n\treturn S_OK;\n}\n\nstatic void addBackgroundParams(struct foreachParams *p, size_t start, size_t end, const uiAttribute *attr)\n{\n\tstruct drawTextBackgroundParams *params;\n\n\tparams = uiprivNew(struct drawTextBackgroundParams);\n\tparams->start = start;\n\tparams->end = end;\n\tuiAttributeColor(attr, &(params->r), &(params->g), &(params->b), &(params->a));\n\tp->backgroundParams->push_back(params);\n}\n\nstatic uiForEach processAttribute(const uiAttributedString *s, const uiAttribute *attr, size_t start, size_t end, void *data)\n{\n\tstruct foreachParams *p = (struct foreachParams *) data;\n\tDWRITE_TEXT_RANGE range;\n\tWCHAR *wfamily;\n\tBOOL hasUnderline;\n\tIDWriteTypography *dt;\n\tHRESULT hr;\n\n\tstart = uiprivAttributedStringUTF8ToUTF16(s, start);\n\tend = uiprivAttributedStringUTF8ToUTF16(s, end);\n\trange.startPosition = start;\n\trange.length = end - start;\n\tswitch (uiAttributeGetType(attr)) {\n\tcase uiAttributeTypeFamily:\n\t\twfamily = toUTF16(uiAttributeFamily(attr));\n\t\thr = p->layout->SetFontFamilyName(wfamily, range);\n\t\tif (hr != S_OK)\n\t\t\tlogHRESULT(L\"error applying family name attribute\", hr);\n\t\tuiprivFree(wfamily);\n\t\tbreak;\n\tcase uiAttributeTypeSize:\n\t\thr = p->layout->SetFontSize(\n// TODO unify with fontmatch.cpp and/or attrstr.hpp\n#define pointSizeToDWriteSize(size) (size * (96.0 / 72.0))\n\t\t\tpointSizeToDWriteSize(uiAttributeSize(attr)),\n\t\t\trange);\n\t\tif (hr != S_OK)\n\t\t\tlogHRESULT(L\"error applying size attribute\", hr);\n\t\tbreak;\n\tcase uiAttributeTypeWeight:\n\t\thr = p->layout->SetFontWeight(\n\t\t\tuiprivWeightToDWriteWeight(uiAttributeWeight(attr)),\n\t\t\trange);\n\t\tif (hr != S_OK)\n\t\t\tlogHRESULT(L\"error applying weight attribute\", hr);\n\t\tbreak;\n\tcase uiAttributeTypeItalic:\n\t\thr = p->layout->SetFontStyle(\n\t\t\tuiprivItalicToDWriteStyle(uiAttributeItalic(attr)),\n\t\t\trange);\n\t\tif (hr != S_OK)\n\t\t\tlogHRESULT(L\"error applying italic attribute\", hr);\n\t\tbreak;\n\tcase uiAttributeTypeStretch:\n\t\thr = p->layout->SetFontStretch(\n\t\t\tuiprivStretchToDWriteStretch(uiAttributeStretch(attr)),\n\t\t\trange);\n\t\tif (hr != S_OK)\n\t\t\tlogHRESULT(L\"error applying stretch attribute\", hr);\n\t\tbreak;\n\tcase uiAttributeTypeUnderline:\n\t\t// mark that we have an underline; otherwise, DirectWrite will never call our custom renderer's DrawUnderline() method\n\t\thasUnderline = FALSE;\n\t\tif (uiAttributeUnderline(attr) != uiUnderlineNone)\n\t\t\thasUnderline = TRUE;\n\t\thr = p->layout->SetUnderline(hasUnderline, range);\n\t\tif (hr != S_OK)\n\t\t\tlogHRESULT(L\"error applying underline attribute\", hr);\n\t\t// and fall through to set the underline style through the drawing effect\n\tcase uiAttributeTypeColor:\n\tcase uiAttributeTypeUnderlineColor:\n\t\t// TODO const-correct this properly\n\t\thr = addEffectAttributeToRange(p, start, end, (uiAttribute *) attr);\n\t\tif (hr != S_OK)\n\t\t\tlogHRESULT(L\"error applying effect (color, underline, or underline color) attribute\", hr);\n\t\tbreak;\n\tcase uiAttributeTypeBackground:\n\t\taddBackgroundParams(p, start, end, attr);\n\t\tbreak;\n\tcase uiAttributeTypeFeatures:\n\t\t// only generate an attribute if not NULL\n\t\t// TODO do we still need to do this or not...\n\t\tif (uiAttributeFeatures(attr) == NULL)\n\t\t\tbreak;\n\t\tdt = uiprivOpenTypeFeaturesToIDWriteTypography(uiAttributeFeatures(attr));\n\t\thr = p->layout->SetTypography(dt, range);\n\t\tif (hr != S_OK)\n\t\t\tlogHRESULT(L\"error applying features attribute\", hr);\n\t\tdt->Release();\n\t\tbreak;\n\t}\n\treturn uiForEachContinue;\n}\n\nstatic HRESULT applyEffectsAttributes(struct foreachParams *p)\n{\n\tIUnknown *u;\n\tcombinedEffectsAttr *cea;\n\tdrawingEffectsAttr *dea;\n\tDWRITE_TEXT_RANGE range;\n\t// here's the magic: this std::unordered_map will deduplicate all of our combinedEffectsAttrs, mapping all identical ones to a single drawingEffectsAttr\n\t// 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\n\t// note the use of our custom hash and equal_to implementations\n\tstd::unordered_map<combinedEffectsAttr *, drawingEffectsAttr *,\n\t\tapplyEffectsHash, applyEffectsEqualTo> effects;\n\tHRESULT hr;\n\n\t// go through, replacing every combinedEffectsAttr with the proper drawingEffectsAttr\n\trange.startPosition = 0;\n\t// and in case this while loop never runs, make hr valid to start with\n\thr = S_OK;\n\twhile (range.startPosition < p->len) {\n\t\thr = p->layout->GetDrawingEffect(range.startPosition, &u, &range);\n\t\tif (hr != S_OK)\n\t\t\t// note that we are breaking instead of returning; this allows us to clean up on failure\n\t\t\tbreak;\n\t\tcea = (combinedEffectsAttr *) u;\n\t\tif (cea != NULL) {\n\t\t\tauto diter = effects.find(cea);\n\t\t\tif (diter != effects.end())\n\t\t\t\tdea = diter->second;\n\t\t\telse {\n\t\t\t\tdea = cea->toDrawingEffectsAttr();\n\t\t\t\teffects.insert({cea, dea});\n\t\t\t}\n\t\t\thr = p->layout->SetDrawingEffect(dea, range);\n\t\t\t// don't release dea; we need the reference that's inside the map\n\t\t\t// (we don't take extra references on lookup, so this will be fine)\n\t\t\tif (hr != S_OK)\n\t\t\t\tbreak;\n\t\t}\n\t\trange.startPosition += range.length;\n\t}\n\n\t// and clean up, finally destroying the combinedEffectAttrs too\n\t// we do this in the case of failure as well, to make sure everything is properly cleaned up\n\tfor (auto iter = effects.begin(); iter != effects.end(); iter++) {\n\t\titer->first->Release();\n\t\titer->second->Release();\n\t}\n\treturn hr;\n}\n\nvoid uiprivAttributedStringApplyAttributesToDWriteTextLayout(uiDrawTextLayoutParams *p, IDWriteTextLayout *layout, std::vector<struct drawTextBackgroundParams *> **backgroundParams)\n{\n\tstruct foreachParams fep;\n\tHRESULT hr;\n\n\tfep.s = uiprivAttributedStringUTF16String(p->String);\n\tfep.len = uiprivAttributedStringUTF16Len(p->String);\n\tfep.layout = layout;\n\tfep.backgroundParams = new std::vector<struct drawTextBackgroundParams *>;\n\tuiAttributedStringForEachAttribute(p->String, processAttribute, &fep);\n\thr = applyEffectsAttributes(&fep);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error applying effects attributes\", hr);\n\t*backgroundParams = fep.backgroundParams;\n}\n"
  },
  {
    "path": "windows/attrstr.hpp",
    "content": "// 11 march 2018\n#include \"../common/attrstr.h\"\n\n// dwrite.cpp\nextern IDWriteFactory *dwfactory;\nextern HRESULT uiprivInitDrawText(void);\nextern void uiprivUninitDrawText(void);\nstruct fontCollection {\n\tIDWriteFontCollection *fonts;\n\tWCHAR userLocale[LOCALE_NAME_MAX_LENGTH];\n\tint userLocaleSuccess;\n};\nextern fontCollection *uiprivLoadFontCollection(void);\nextern WCHAR *uiprivFontCollectionFamilyName(fontCollection *fc, IDWriteFontFamily *family);\nextern void uiprivFontCollectionFree(fontCollection *fc);\nextern WCHAR *uiprivFontCollectionCorrectString(fontCollection *fc, IDWriteLocalizedStrings *names);\n\n// opentype.cpp\nextern IDWriteTypography *uiprivOpenTypeFeaturesToIDWriteTypography(const uiOpenTypeFeatures *otf);\n\n// fontmatch.cpp\nextern DWRITE_FONT_WEIGHT uiprivWeightToDWriteWeight(uiTextWeight w);\nextern DWRITE_FONT_STYLE uiprivItalicToDWriteStyle(uiTextItalic i);\nextern DWRITE_FONT_STRETCH uiprivStretchToDWriteStretch(uiTextStretch s);\nextern void uiprivFontDescriptorFromIDWriteFont(IDWriteFont *font, uiFontDescriptor *uidesc);\n\n// attrstr.cpp\n// TODO\nstruct drawTextBackgroundParams;\nextern void uiprivAttributedStringApplyAttributesToDWriteTextLayout(uiDrawTextLayoutParams *p, IDWriteTextLayout *layout, std::vector<struct drawTextBackgroundParams *> **backgroundFuncs);\n\n// drawtext.cpp\nclass drawingEffectsAttr : public IUnknown {\n\tULONG refcount;\n\n\tbool hasColor;\n\tdouble r;\n\tdouble g;\n\tdouble b;\n\tdouble a;\n\n\tbool hasUnderline;\n\tuiUnderline u;\n\n\tbool hasUnderlineColor;\n\tdouble ur;\n\tdouble ug;\n\tdouble ub;\n\tdouble ua;\npublic:\n\tdrawingEffectsAttr(void);\n\n\t// IUnknown\n\tvirtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject);\n\tvirtual ULONG STDMETHODCALLTYPE AddRef(void);\n\tvirtual ULONG STDMETHODCALLTYPE Release(void);\n\n\tvoid setColor(double r, double g, double b, double a);\n\tvoid setUnderline(uiUnderline u);\n\tvoid setUnderlineColor(double r, double g, double b, double a);\n\tHRESULT mkColorBrush(ID2D1RenderTarget *rt, ID2D1SolidColorBrush **b);\n\tHRESULT underline(uiUnderline *u);\n\tHRESULT mkUnderlineBrush(ID2D1RenderTarget *rt, ID2D1SolidColorBrush **b);\n};\n// TODO figure out where this type should *really* go in all the headers...\nstruct drawTextBackgroundParams {\n\tsize_t start;\n\tsize_t end;\n\tdouble r;\n\tdouble g;\n\tdouble b;\n\tdouble a;\n};\n\n// fontdialog.cpp\nstruct fontDialogParams {\n\tIDWriteFont *font;\n\tdouble size;\n\tWCHAR *familyName;\n\tWCHAR *styleName;\n};\nextern BOOL uiprivShowFontDialog(HWND parent, struct fontDialogParams *params);\nextern void uiprivLoadInitialFontDialogParams(struct fontDialogParams *params);\nextern void uiprivDestroyFontDialogParams(struct fontDialogParams *params);\nextern WCHAR *uiprivFontDialogParamsToString(struct fontDialogParams *params);\n"
  },
  {
    "path": "windows/box.cpp",
    "content": "// 7 april 2015\n#include \"uipriv_windows.hpp\"\n\nstruct boxChild {\n\tuiControl *c;\n\tint stretchy;\n\tint width;\n\tint height;\n};\n\nstruct uiBox {\n\tuiWindowsControl c;\n\tHWND hwnd;\n\tstd::vector<struct boxChild> *controls;\n\tint vertical;\n\tint padded;\n};\n\nstatic void boxPadding(uiBox *b, int *xpadding, int *ypadding)\n{\n\tuiWindowsSizing sizing;\n\n\t*xpadding = 0;\n\t*ypadding = 0;\n\tif (b->padded) {\n\t\tuiWindowsGetSizing(b->hwnd, &sizing);\n\t\tuiWindowsSizingStandardPadding(&sizing, xpadding, ypadding);\n\t}\n}\n\nstatic void boxRelayout(uiBox *b)\n{\n\tRECT r;\n\tint x, y, width, height;\n\tint xpadding, ypadding;\n\tint nStretchy;\n\tint stretchywid, stretchyht;\n\tint i;\n\tint minimumWidth, minimumHeight;\n\tint nVisible;\n\tuiWindowsSizing *d;\n\n\tif (b->controls->size() == 0)\n\t\treturn;\n\n\tuiWindowsEnsureGetClientRect(b->hwnd, &r);\n\tx = r.left;\n\ty = r.top;\n\twidth = r.right - r.left;\n\theight = r.bottom - r.top;\n\n\t// -1) get this Box's padding\n\tboxPadding(b, &xpadding, &ypadding);\n\n\t// 1) get width and height of non-stretchy controls\n\t// this will tell us how much space will be left for stretchy controls\n\tstretchywid = width;\n\tstretchyht = height;\n\tnStretchy = 0;\n\tnVisible = 0;\n\tfor (struct boxChild &bc : *(b->controls)) {\n\t\tif (!uiControlVisible(bc.c))\n\t\t\tcontinue;\n\t\tnVisible++;\n\t\tif (bc.stretchy) {\n\t\t\tnStretchy++;\n\t\t\tcontinue;\n\t\t}\n\t\tuiWindowsControlMinimumSize(uiWindowsControl(bc.c), &minimumWidth, &minimumHeight);\n\t\tif (b->vertical) {\t\t// all controls have same width\n\t\t\tbc.width = width;\n\t\t\tbc.height = minimumHeight;\n\t\t\tstretchyht -= minimumHeight;\n\t\t} else {\t\t\t\t// all controls have same height\n\t\t\tbc.width = minimumWidth;\n\t\t\tbc.height = height;\n\t\t\tstretchywid -= minimumWidth;\n\t\t}\n\t}\n\tif (nVisible == 0)\t\t\t// nothing to do\n\t\treturn;\n\n\t// 2) now inset the available rect by the needed padding\n\tif (b->vertical) {\n\t\theight -= (nVisible - 1) * ypadding;\n\t\tstretchyht -= (nVisible - 1) * ypadding;\n\t} else {\n\t\twidth -= (nVisible - 1) * xpadding;\n\t\tstretchywid -= (nVisible - 1) * xpadding;\n\t}\n\n\t// 3) now get the size of stretchy controls\n\tif (nStretchy != 0) {\n\t\tif (b->vertical)\n\t\t\tstretchyht /= nStretchy;\n\t\telse\n\t\t\tstretchywid /= nStretchy;\n\t\tfor (struct boxChild &bc : *(b->controls)) {\n\t\t\tif (!uiControlVisible(bc.c))\n\t\t\t\tcontinue;\n\t\t\tif (bc.stretchy) {\n\t\t\t\tbc.width = stretchywid;\n\t\t\t\tbc.height = stretchyht;\n\t\t\t}\n\t\t}\n\t}\n\n\t// 4) now we can position controls\n\t// first, make relative to the top-left corner of the container\n\tx = 0;\n\ty = 0;\n\tfor (const struct boxChild &bc : *(b->controls)) {\n\t\tif (!uiControlVisible(bc.c))\n\t\t\tcontinue;\n\t\tuiWindowsEnsureMoveWindowDuringResize((HWND) uiControlHandle(bc.c), x, y, bc.width, bc.height);\n\t\tif (b->vertical)\n\t\t\ty += bc.height + ypadding;\n\t\telse\n\t\t\tx += bc.width + xpadding;\n\t}\n}\n\nstatic void uiBoxDestroy(uiControl *c)\n{\n\tuiBox *b = uiBox(c);\n\n\tfor (const struct boxChild &bc : *(b->controls)) {\n\t\tuiControlSetParent(bc.c, NULL);\n\t\tuiControlDestroy(bc.c);\n\t}\n\tdelete b->controls;\n\tuiWindowsEnsureDestroyWindow(b->hwnd);\n\tuiFreeControl(uiControl(b));\n}\n\nuiWindowsControlDefaultHandle(uiBox)\nuiWindowsControlDefaultParent(uiBox)\nuiWindowsControlDefaultSetParent(uiBox)\nuiWindowsControlDefaultToplevel(uiBox)\nuiWindowsControlDefaultVisible(uiBox)\nuiWindowsControlDefaultShow(uiBox)\nuiWindowsControlDefaultHide(uiBox)\nuiWindowsControlDefaultEnabled(uiBox)\nuiWindowsControlDefaultEnable(uiBox)\nuiWindowsControlDefaultDisable(uiBox)\n\nstatic void uiBoxSyncEnableState(uiWindowsControl *c, int enabled)\n{\n\tuiBox *b = uiBox(c);\n\n\tif (uiWindowsShouldStopSyncEnableState(uiWindowsControl(b), enabled))\n\t\treturn;\n\tfor (const struct boxChild &bc : *(b->controls))\n\t\tuiWindowsControlSyncEnableState(uiWindowsControl(bc.c), enabled);\n}\n\nuiWindowsControlDefaultSetParentHWND(uiBox)\n\nstatic void uiBoxMinimumSize(uiWindowsControl *c, int *width, int *height)\n{\n\tuiBox *b = uiBox(c);\n\tint xpadding, ypadding;\n\tint nStretchy;\n\t// these two contain the largest minimum width and height of all stretchy controls in the box\n\t// all stretchy controls will use this value to determine the final minimum size\n\tint maxStretchyWidth, maxStretchyHeight;\n\tint i;\n\tint minimumWidth, minimumHeight;\n\tint nVisible;\n\tuiWindowsSizing sizing;\n\n\t*width = 0;\n\t*height = 0;\n\tif (b->controls->size() == 0)\n\t\treturn;\n\n\t// 0) get this Box's padding\n\tboxPadding(b, &xpadding, &ypadding);\n\n\t// 1) add in the size of non-stretchy controls and get (but not add in) the largest widths and heights of stretchy controls\n\t// we still add in like direction of stretchy controls\n\tnStretchy = 0;\n\tmaxStretchyWidth = 0;\n\tmaxStretchyHeight = 0;\n\tnVisible = 0;\n\tfor (const struct boxChild &bc : *(b->controls)) {\n\t\tif (!uiControlVisible(bc.c))\n\t\t\tcontinue;\n\t\tnVisible++;\n\t\tuiWindowsControlMinimumSize(uiWindowsControl(bc.c), &minimumWidth, &minimumHeight);\n\t\tif (bc.stretchy) {\n\t\t\tnStretchy++;\n\t\t\tif (maxStretchyWidth < minimumWidth)\n\t\t\t\tmaxStretchyWidth = minimumWidth;\n\t\t\tif (maxStretchyHeight < minimumHeight)\n\t\t\t\tmaxStretchyHeight = minimumHeight;\n\t\t}\n\t\tif (b->vertical) {\n\t\t\tif (*width < minimumWidth)\n\t\t\t\t*width = minimumWidth;\n\t\t\tif (!bc.stretchy)\n\t\t\t\t*height += minimumHeight;\n\t\t} else {\n\t\t\tif (!bc.stretchy)\n\t\t\t\t*width += minimumWidth;\n\t\t\tif (*height < minimumHeight)\n\t\t\t\t*height = minimumHeight;\n\t\t}\n\t}\n\tif (nVisible == 0)\t\t// just return 0x0\n\t\treturn;\n\n\t// 2) now outset the desired rect with the needed padding\n\tif (b->vertical)\n\t\t*height += (nVisible - 1) * ypadding;\n\telse\n\t\t*width += (nVisible - 1) * xpadding;\n\n\t// 3) and now we can add in stretchy controls\n\tif (b->vertical)\n\t\t*height += nStretchy * maxStretchyHeight;\n\telse\n\t\t*width += nStretchy * maxStretchyWidth;\n}\n\nstatic void uiBoxMinimumSizeChanged(uiWindowsControl *c)\n{\n\tuiBox *b = uiBox(c);\n\n\tif (uiWindowsControlTooSmall(uiWindowsControl(b))) {\n\t\tuiWindowsControlContinueMinimumSizeChanged(uiWindowsControl(b));\n\t\treturn;\n\t}\n\tboxRelayout(b);\n}\n\nuiWindowsControlDefaultLayoutRect(uiBox)\nuiWindowsControlDefaultAssignControlIDZOrder(uiBox)\n\nstatic void uiBoxChildVisibilityChanged(uiWindowsControl *c)\n{\n\t// TODO eliminate the redundancy\n\tuiWindowsControlMinimumSizeChanged(c);\n}\n\nstatic void boxArrangeChildren(uiBox *b)\n{\n\tLONG_PTR controlID;\n\tHWND insertAfter;\n\n\tcontrolID = 100;\n\tinsertAfter = NULL;\n\tfor (const struct boxChild &bc : *(b->controls))\n\t\tuiWindowsControlAssignControlIDZOrder(uiWindowsControl(bc.c), &controlID, &insertAfter);\n}\n\nvoid uiBoxAppend(uiBox *b, uiControl *c, int stretchy)\n{\n\tstruct boxChild bc;\n\n\tbc.c = c;\n\tbc.stretchy = stretchy;\n\tuiControlSetParent(bc.c, uiControl(b));\n\tuiWindowsControlSetParentHWND(uiWindowsControl(bc.c), b->hwnd);\n\tb->controls->push_back(bc);\n\tboxArrangeChildren(b);\n\tuiWindowsControlMinimumSizeChanged(uiWindowsControl(b));\n}\n\nvoid uiBoxDelete(uiBox *b, int index)\n{\n\tuiControl *c;\n\n\tc = (*(b->controls))[index].c;\n\tuiControlSetParent(c, NULL);\n\tuiWindowsControlSetParentHWND(uiWindowsControl(c), NULL);\n\tb->controls->erase(b->controls->begin() + index);\n\tboxArrangeChildren(b);\n\tuiWindowsControlMinimumSizeChanged(uiWindowsControl(b));\n}\n\nint uiBoxPadded(uiBox *b)\n{\n\treturn b->padded;\n}\n\nvoid uiBoxSetPadded(uiBox *b, int padded)\n{\n\tb->padded = padded;\n\tuiWindowsControlMinimumSizeChanged(uiWindowsControl(b));\n}\n\nstatic void onResize(uiWindowsControl *c)\n{\n\tboxRelayout(uiBox(c));\n}\n\nstatic uiBox *finishNewBox(int vertical)\n{\n\tuiBox *b;\n\n\tuiWindowsNewControl(uiBox, b);\n\n\tb->hwnd = uiWindowsMakeContainer(uiWindowsControl(b), onResize);\n\n\tb->vertical = vertical;\n\tb->controls = new std::vector<struct boxChild>;\n\n\treturn b;\n}\n\nuiBox *uiNewHorizontalBox(void)\n{\n\treturn finishNewBox(0);\n}\n\nuiBox *uiNewVerticalBox(void)\n{\n\treturn finishNewBox(1);\n}\n"
  },
  {
    "path": "windows/button.cpp",
    "content": "// 7 april 2015\n#include \"uipriv_windows.hpp\"\n\nstruct uiButton {\n\tuiWindowsControl c;\n\tHWND hwnd;\n\tvoid (*onClicked)(uiButton *, void *);\n\tvoid *onClickedData;\n};\n\nstatic BOOL onWM_COMMAND(uiControl *c, HWND hwnd, WORD code, LRESULT *lResult)\n{\n\tuiButton *b = uiButton(c);\n\n\tif (code != BN_CLICKED)\n\t\treturn FALSE;\n\t(*(b->onClicked))(b, b->onClickedData);\n\t*lResult = 0;\n\treturn TRUE;\n}\n\nstatic void uiButtonDestroy(uiControl *c)\n{\n\tuiButton *b = uiButton(c);\n\n\tuiWindowsUnregisterWM_COMMANDHandler(b->hwnd);\n\tuiWindowsEnsureDestroyWindow(b->hwnd);\n\tuiFreeControl(uiControl(b));\n}\n\nuiWindowsControlAllDefaultsExceptDestroy(uiButton)\n\n// from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing\n#define buttonHeight 14\n\nstatic void uiButtonMinimumSize(uiWindowsControl *c, int *width, int *height)\n{\n\tuiButton *b = uiButton(c);\n\tSIZE size;\n\tuiWindowsSizing sizing;\n\tint y;\n\n\t// try the comctl32 version 6 way\n\tsize.cx = 0;\t\t// explicitly ask for ideal size\n\tsize.cy = 0;\n\tif (SendMessageW(b->hwnd, BCM_GETIDEALSIZE, 0, (LPARAM) (&size)) != FALSE) {\n\t\t*width = size.cx;\n\t\t*height = size.cy;\n\t\treturn;\n\t}\n\n\t// that didn't work; fall back to using Microsoft's metrics\n\t// Microsoft says to use a fixed width for all buttons; this isn't good enough\n\t// use the text width instead, with some edge padding\n\t*width = uiWindowsWindowTextWidth(b->hwnd) + (2 * GetSystemMetrics(SM_CXEDGE));\n\ty = buttonHeight;\n\tuiWindowsGetSizing(b->hwnd, &sizing);\n\tuiWindowsSizingDlgUnitsToPixels(&sizing, NULL, &y);\n\t*height = y;\n}\n\nstatic void defaultOnClicked(uiButton *b, void *data)\n{\n\t// do nothing\n}\n\nchar *uiButtonText(uiButton *b)\n{\n\treturn uiWindowsWindowText(b->hwnd);\n}\n\nvoid uiButtonSetText(uiButton *b, const char *text)\n{\n\tuiWindowsSetWindowText(b->hwnd, text);\n\t// changing the text might necessitate a change in the button's size\n\tuiWindowsControlMinimumSizeChanged(uiWindowsControl(b));\n}\n\nvoid uiButtonOnClicked(uiButton *b, void (*f)(uiButton *, void *), void *data)\n{\n\tb->onClicked = f;\n\tb->onClickedData = data;\n}\n\nuiButton *uiNewButton(const char *text)\n{\n\tuiButton *b;\n\tWCHAR *wtext;\n\n\tuiWindowsNewControl(uiButton, b);\n\n\twtext = toUTF16(text);\n\tb->hwnd = uiWindowsEnsureCreateControlHWND(0,\n\t\tL\"button\", wtext,\n\t\tBS_PUSHBUTTON | WS_TABSTOP,\n\t\thInstance, NULL,\n\t\tTRUE);\n\tuiprivFree(wtext);\n\n\tuiWindowsRegisterWM_COMMANDHandler(b->hwnd, onWM_COMMAND, uiControl(b));\n\tuiButtonOnClicked(b, defaultOnClicked, NULL);\n\n\treturn b;\n}\n"
  },
  {
    "path": "windows/checkbox.cpp",
    "content": "// 7 april 2015\n#include \"uipriv_windows.hpp\"\n\nstruct uiCheckbox {\n\tuiWindowsControl c;\n\tHWND hwnd;\n\tvoid (*onToggled)(uiCheckbox *, void *);\n\tvoid *onToggledData;\n};\n\nstatic BOOL onWM_COMMAND(uiControl *cc, HWND hwnd, WORD code, LRESULT *lResult)\n{\n\tuiCheckbox *c = uiCheckbox(cc);\n\tWPARAM check;\n\n\tif (code != BN_CLICKED)\n\t\treturn FALSE;\n\n\t// we didn't use BS_AUTOCHECKBOX (http://blogs.msdn.com/b/oldnewthing/archive/2014/05/22/10527522.aspx) so we have to manage the check state ourselves\n\tcheck = BST_CHECKED;\n\tif (SendMessage(c->hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED)\n\t\tcheck = BST_UNCHECKED;\n\tSendMessage(c->hwnd, BM_SETCHECK, check, 0);\n\n\t(*(c->onToggled))(c, c->onToggledData);\n\t*lResult = 0;\n\treturn TRUE;\n}\n\nstatic void uiCheckboxDestroy(uiControl *cc)\n{\n\tuiCheckbox *c = uiCheckbox(cc);\n\n\tuiWindowsUnregisterWM_COMMANDHandler(c->hwnd);\n\tuiWindowsEnsureDestroyWindow(c->hwnd);\n\tuiFreeControl(uiControl(c));\n}\n\nuiWindowsControlAllDefaultsExceptDestroy(uiCheckbox)\n\n// from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing\n#define checkboxHeight 10\n// from http://msdn.microsoft.com/en-us/library/windows/desktop/bb226818%28v=vs.85%29.aspx\n#define checkboxXFromLeftOfBoxToLeftOfLabel 12\n\nstatic void uiCheckboxMinimumSize(uiWindowsControl *cc, int *width, int *height)\n{\n\tuiCheckbox *c = uiCheckbox(cc);\n\tuiWindowsSizing sizing;\n\tint x, y;\n\n\tx = checkboxXFromLeftOfBoxToLeftOfLabel;\n\ty = checkboxHeight;\n\tuiWindowsGetSizing(c->hwnd, &sizing);\n\tuiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y);\n\t*width = x + uiWindowsWindowTextWidth(c->hwnd);\n\t*height = y;\n}\n\nstatic void defaultOnToggled(uiCheckbox *c, void *data)\n{\n\t// do nothing\n}\n\nchar *uiCheckboxText(uiCheckbox *c)\n{\n\treturn uiWindowsWindowText(c->hwnd);\n}\n\nvoid uiCheckboxSetText(uiCheckbox *c, const char *text)\n{\n\tuiWindowsSetWindowText(c->hwnd, text);\n\t// changing the text might necessitate a change in the checkbox's size\n\tuiWindowsControlMinimumSizeChanged(uiWindowsControl(c));\n}\n\nvoid uiCheckboxOnToggled(uiCheckbox *c, void (*f)(uiCheckbox *, void *), void *data)\n{\n\tc->onToggled = f;\n\tc->onToggledData = data;\n}\n\nint uiCheckboxChecked(uiCheckbox *c)\n{\n\treturn SendMessage(c->hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED;\n}\n\nvoid uiCheckboxSetChecked(uiCheckbox *c, int checked)\n{\n\tWPARAM check;\n\n\tcheck = BST_CHECKED;\n\tif (!checked)\n\t\tcheck = BST_UNCHECKED;\n\tSendMessage(c->hwnd, BM_SETCHECK, check, 0);\n}\n\nuiCheckbox *uiNewCheckbox(const char *text)\n{\n\tuiCheckbox *c;\n\tWCHAR *wtext;\n\n\tuiWindowsNewControl(uiCheckbox, c);\n\n\twtext = toUTF16(text);\n\tc->hwnd = uiWindowsEnsureCreateControlHWND(0,\n\t\tL\"button\", wtext,\n\t\tBS_CHECKBOX | WS_TABSTOP,\n\t\thInstance, NULL,\n\t\tTRUE);\n\tuiprivFree(wtext);\n\n\tuiWindowsRegisterWM_COMMANDHandler(c->hwnd, onWM_COMMAND, uiControl(c));\n\tuiCheckboxOnToggled(c, defaultOnToggled, NULL);\n\n\treturn c;\n}\n"
  },
  {
    "path": "windows/colorbutton.cpp",
    "content": "// 16 may 2016\n#include \"uipriv_windows.hpp\"\n\nstruct uiColorButton {\n\tuiWindowsControl c;\n\tHWND hwnd;\n\tdouble r;\n\tdouble g;\n\tdouble b;\n\tdouble a;\n\tvoid (*onChanged)(uiColorButton *, void *);\n\tvoid *onChangedData;\n};\n\nstatic void uiColorButtonDestroy(uiControl *c)\n{\n\tuiColorButton *b = uiColorButton(c);\n\n\tuiWindowsUnregisterWM_COMMANDHandler(b->hwnd);\n\tuiWindowsUnregisterWM_NOTIFYHandler(b->hwnd);\n\tuiWindowsEnsureDestroyWindow(b->hwnd);\n\tuiFreeControl(uiControl(b));\n}\n\nstatic BOOL onWM_COMMAND(uiControl *c, HWND hwnd, WORD code, LRESULT *lResult)\n{\n\tuiColorButton *b = uiColorButton(c);\n\tHWND parent;\n\tstruct colorDialogRGBA rgba;\n\n\tif (code != BN_CLICKED)\n\t\treturn FALSE;\n\n\tparent = parentToplevel(b->hwnd);\n\trgba.r = b->r;\n\trgba.g = b->g;\n\trgba.b = b->b;\n\trgba.a = b->a;\n\tif (showColorDialog(parent, &rgba)) {\n\t\tb->r = rgba.r;\n\t\tb->g = rgba.g;\n\t\tb->b = rgba.b;\n\t\tb->a = rgba.a;\n\t\tinvalidateRect(b->hwnd, NULL, TRUE);\n\t\t(*(b->onChanged))(b, b->onChangedData);\n\t}\n\n\t*lResult = 0;\n\treturn TRUE;\n}\n\nstatic BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult)\n{\n\tuiColorButton *b = uiColorButton(c);\n\tNMCUSTOMDRAW *nm = (NMCUSTOMDRAW *) nmhdr;\n\tRECT client;\n\tID2D1DCRenderTarget *rt;\n\tD2D1_RECT_F r;\n\tD2D1_COLOR_F color;\n\tD2D1_BRUSH_PROPERTIES bprop;\n\tID2D1SolidColorBrush *brush;\n\tuiWindowsSizing sizing;\n\tint x, y;\n\tHRESULT hr;\n\n\tif (nmhdr->code != NM_CUSTOMDRAW)\n\t\treturn FALSE;\n\t// and allow the button to draw its background\n\tif (nm->dwDrawStage != CDDS_PREPAINT)\n\t\treturn FALSE;\n\n\tuiWindowsEnsureGetClientRect(b->hwnd, &client);\n\trt = makeHDCRenderTarget(nm->hdc, &client);\n\trt->BeginDraw();\n\n\tuiWindowsGetSizing(b->hwnd, &sizing);\n\tx = 3;\t\t// should be enough\n\ty = 3;\n\tuiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y);\n\tr.left = client.left + x;\n\tr.top = client.top + y;\n\tr.right = client.right - x;\n\tr.bottom = client.bottom - y;\n\n\tcolor.r = b->r;\n\tcolor.g = b->g;\n\tcolor.b = b->b;\n\tcolor.a = b->a;\n\tZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES));\n\tbprop.opacity = 1.0;\n\tbprop.transform._11 = 1;\n\tbprop.transform._22 = 1;\n\thr = rt->CreateSolidColorBrush(&color, &bprop, &brush);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating brush for color button\", hr);\n\trt->FillRectangle(&r, brush);\n\tbrush->Release();\n\n\thr = rt->EndDraw(NULL, NULL);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error drawing color on color button\", hr);\n\trt->Release();\n\n\t// skip default processing (don't draw text)\n\t*lResult = CDRF_SKIPDEFAULT;\n\treturn TRUE;\n}\n\nuiWindowsControlAllDefaultsExceptDestroy(uiColorButton)\n\n// from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing\n#define buttonHeight 14\n\n// TODO check widths\nstatic void uiColorButtonMinimumSize(uiWindowsControl *c, int *width, int *height)\n{\n\tuiColorButton *b = uiColorButton(c);\n\tSIZE size;\n\tuiWindowsSizing sizing;\n\tint y;\n\n\t// try the comctl32 version 6 way\n\tsize.cx = 0;\t\t// explicitly ask for ideal size\n\tsize.cy = 0;\n\tif (SendMessageW(b->hwnd, BCM_GETIDEALSIZE, 0, (LPARAM) (&size)) != FALSE) {\n\t\t*width = size.cx;\n\t\t*height = size.cy;\n\t\treturn;\n\t}\n\n\t// that didn't work; fall back to using Microsoft's metrics\n\t// Microsoft says to use a fixed width for all buttons; this isn't good enough\n\t// use the text width instead, with some edge padding\n\t*width = uiWindowsWindowTextWidth(b->hwnd) + (2 * GetSystemMetrics(SM_CXEDGE));\n\ty = buttonHeight;\n\tuiWindowsGetSizing(b->hwnd, &sizing);\n\tuiWindowsSizingDlgUnitsToPixels(&sizing, NULL, &y);\n\t*height = y;\n}\n\nstatic void defaultOnChanged(uiColorButton *b, void *data)\n{\n\t// do nothing\n}\n\nvoid uiColorButtonColor(uiColorButton *b, double *r, double *g, double *bl, double *a)\n{\n\t*r = b->r;\n\t*g = b->g;\n\t*bl = b->b;\n\t*a = b->a;\n}\n\nvoid uiColorButtonSetColor(uiColorButton *b, double r, double g, double bl, double a)\n{\n\tb->r = r;\n\tb->g = g;\n\tb->b = bl;\n\tb->a = a;\n\tinvalidateRect(b->hwnd, NULL, TRUE);\n}\n\nvoid uiColorButtonOnChanged(uiColorButton *b, void (*f)(uiColorButton *, void *), void *data)\n{\n\tb->onChanged = f;\n\tb->onChangedData = data;\n}\n\nuiColorButton *uiNewColorButton(void)\n{\n\tuiColorButton *b;\n\n\tuiWindowsNewControl(uiColorButton, b);\n\n\t// initial color is black\n\tb->r = 0.0;\n\tb->g = 0.0;\n\tb->b = 0.0;\n\tb->a = 1.0;\n\n\tb->hwnd = uiWindowsEnsureCreateControlHWND(0,\n\t\tL\"button\", L\" \",\t\t// TODO; can't use \"\" TODO\n\t\tBS_PUSHBUTTON | WS_TABSTOP,\n\t\thInstance, NULL,\n\t\tTRUE);\n\n\tuiWindowsRegisterWM_COMMANDHandler(b->hwnd, onWM_COMMAND, uiControl(b));\n\tuiWindowsRegisterWM_NOTIFYHandler(b->hwnd, onWM_NOTIFY, uiControl(b));\n\tuiColorButtonOnChanged(b, defaultOnChanged, NULL);\n\n\treturn b;\n}\n"
  },
  {
    "path": "windows/colordialog.cpp",
    "content": "// 16 may 2016\n#include \"uipriv_windows.hpp\"\n\n// TODO should the d2dscratch programs capture mouse?\n\nstruct colorDialog {\n\tHWND hwnd;\n\n\tHWND svChooser;\n\tHWND hSlider;\n\tHWND preview;\n\tHWND opacitySlider;\n\tHWND editH;\n\tHWND editS;\n\tHWND editV;\n\tHWND editRDouble, editRInt;\n\tHWND editGDouble, editGInt;\n\tHWND editBDouble, editBInt;\n\tHWND editADouble, editAInt;\n\tHWND editHex;\n\n\tdouble h;\n\tdouble s;\n\tdouble v;\n\tdouble a;\n\tstruct colorDialogRGBA *out;\n\n\tBOOL updating;\n};\n\n// both of these are from the wikipedia page on HSV\n// TODO what to do about negative h?\nstatic void rgb2HSV(double r, double g, double b, double *h, double *s, double *v)\n{\n\tdouble M, m;\n\tint whichmax;\n\tdouble c;\n\n\tM = r;\n\twhichmax = 0;\n\tif (M < g) {\n\t\tM = g;\n\t\twhichmax = 1;\n\t}\n\tif (M < b) {\n\t\tM = b;\n\t\twhichmax = 2;\n\t}\n\tm = r;\n\tif (m > g)\n\t\tm = g;\n\tif (m > b)\n\t\tm = b;\n\tc = M - m;\n\n\tif (c == 0)\n\t\t*h = 0;\n\telse {\n\t\tswitch (whichmax) {\n\t\tcase 0:\n\t\t\t*h = ((g - b) / c);\n\t\t\t*h = fmod(*h, 6);\n\t\t\tbreak;\n\t\tcase 1:\n\t\t\t*h = ((b - r) / c) + 2;\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\t*h = ((r - g) / c) + 4;\n\t\t\tbreak;\n\t\t}\n\t\t*h /= 6;\t\t// put in range [0,1)\n\t}\n\n\t*v = M;\n\n\tif (c == 0)\n\t\t*s = 0;\n\telse\n\t\t*s = c / *v;\n}\n\n// TODO negative R values?\nstatic void hsv2RGB(double h, double s, double v, double *r, double *g, double *b)\n{\n\tdouble c;\n\tdouble hPrime;\n\tint h60;\n\tdouble x;\n\tdouble m;\n\tdouble c1, c2;\n\n\tc = v * s;\n\thPrime = h * 6;\n\th60 = (int) hPrime;\t\t// equivalent to splitting into 60° chunks\n\tx = c * (1.0 - fabs(fmod(hPrime, 2) - 1.0));\n\tm = v - c;\n\tswitch (h60) {\n\tcase 0:\n\t\t*r = c + m;\n\t\t*g = x + m;\n\t\t*b = m;\n\t\treturn;\n\tcase 1:\n\t\t*r = x + m;\n\t\t*g = c + m;\n\t\t*b = m;\n\t\treturn;\n\tcase 2:\n\t\t*r = m;\n\t\t*g = c + m;\n\t\t*b = x + m;\n\t\treturn;\n\tcase 3:\n\t\t*r = m;\n\t\t*g = x + m;\n\t\t*b = c + m;\n\t\treturn;\n\tcase 4:\n\t\t*r = x + m;\n\t\t*g = m;\n\t\t*b = c + m;\n\t\treturn;\n\tcase 5:\n\t\t*r = c + m;\n\t\t*g = m;\n\t\t*b = x + m;\n\t\treturn;\n\t}\n\t// TODO\n}\n\n#define hexd L\"0123456789ABCDEF\"\n\nstatic void rgba2Hex(uint8_t r, uint8_t g, uint8_t b, uint8_t a, WCHAR *buf)\n{\n\tbuf[0] = L'#';\n\tbuf[1] = hexd[(a >> 4) & 0xF];\n\tbuf[2] = hexd[a & 0xF];\n\tbuf[3] = hexd[(r >> 4) & 0xF];\n\tbuf[4] = hexd[r & 0xF];\n\tbuf[5] = hexd[(g >> 4) & 0xF];\n\tbuf[6] = hexd[g & 0xF];\n\tbuf[7] = hexd[(b >> 4) & 0xF];\n\tbuf[8] = hexd[b & 0xF];\n\tbuf[9] = L'\\0';\n}\n\nstatic int convHexDigit(WCHAR c)\n{\n\tif (c >= L'0' && c <= L'9')\n\t\treturn c - L'0';\n\tif (c >= L'A' && c <= L'F')\n\t\treturn c - L'A' + 0xA;\n\tif (c >= L'a' && c <= L'f')\n\t\treturn c - L'a' + 0xA;\n\treturn -1;\n}\n\n// TODO allow #NNN shorthand\nstatic BOOL hex2RGBA(WCHAR *buf, double *r, double *g, double *b, double *a)\n{\n\tuint8_t component;\n\tint i;\n\n\tif (*buf == L'#')\n\t\tbuf++;\n\n\tcomponent = 0;\n\ti = convHexDigit(*buf++);\n\tif (i < 0)\n\t\treturn FALSE;\n\tcomponent |= ((uint8_t) i) << 4;\n\ti = convHexDigit(*buf++);\n\tif (i < 0)\n\t\treturn FALSE;\n\tcomponent |= ((uint8_t) i);\n\t*a = ((double) component) / 255;\n\n\tcomponent = 0;\n\ti = convHexDigit(*buf++);\n\tif (i < 0)\n\t\treturn FALSE;\n\tcomponent |= ((uint8_t) i) << 4;\n\ti = convHexDigit(*buf++);\n\tif (i < 0)\n\t\treturn FALSE;\n\tcomponent |= ((uint8_t) i);\n\t*r = ((double) component) / 255;\n\n\tcomponent = 0;\n\ti = convHexDigit(*buf++);\n\tif (i < 0)\n\t\treturn FALSE;\n\tcomponent |= ((uint8_t) i) << 4;\n\ti = convHexDigit(*buf++);\n\tif (i < 0)\n\t\treturn FALSE;\n\tcomponent |= ((uint8_t) i);\n\t*g = ((double) component) / 255;\n\n\tif (*buf == L'\\0') {\t\t// #NNNNNN syntax\n\t\t*b = *g;\n\t\t*g = *r;\n\t\t*r = *a;\n\t\t*a = 1;\n\t\treturn TRUE;\n\t}\n\n\tcomponent = 0;\n\ti = convHexDigit(*buf++);\n\tif (i < 0)\n\t\treturn FALSE;\n\tcomponent |= ((uint8_t) i) << 4;\n\ti = convHexDigit(*buf++);\n\tif (i < 0)\n\t\treturn FALSE;\n\tcomponent |= ((uint8_t) i);\n\t*b = ((double) component) / 255;\n\n\treturn *buf == L'\\0';\n}\n\nstatic void updateDouble(HWND hwnd, double d, HWND whichChanged)\n{\n\tWCHAR *str;\n\n\tif (whichChanged == hwnd)\n\t\treturn;\n\tstr = ftoutf16(d);\n\tsetWindowText(hwnd, str);\n\tuiprivFree(str);\n}\n\nstatic void updateDialog(struct colorDialog *c, HWND whichChanged)\n{\n\tdouble r, g, b;\n\tuint8_t rb, gb, bb, ab;\n\tWCHAR *str;\n\tWCHAR hexbuf[16];\t\t// more than enough\n\n\tc->updating = TRUE;\n\n\tupdateDouble(c->editH, c->h, whichChanged);\n\tupdateDouble(c->editS, c->s, whichChanged);\n\tupdateDouble(c->editV, c->v, whichChanged);\n\n\thsv2RGB(c->h, c->s, c->v, &r, &g, &b);\n\n\tupdateDouble(c->editRDouble, r, whichChanged);\n\tupdateDouble(c->editGDouble, g, whichChanged);\n\tupdateDouble(c->editBDouble, b, whichChanged);\n\tupdateDouble(c->editADouble, c->a, whichChanged);\n\n\trb = (uint8_t) (r * 255);\n\tgb = (uint8_t) (g * 255);\n\tbb = (uint8_t) (b * 255);\n\tab = (uint8_t) (c->a * 255);\n\n\tif (whichChanged != c->editRInt) {\n\t\tstr = itoutf16(rb);\n\t\tsetWindowText(c->editRInt, str);\n\t\tuiprivFree(str);\n\t}\n\tif (whichChanged != c->editGInt) {\n\t\tstr = itoutf16(gb);\n\t\tsetWindowText(c->editGInt, str);\n\t\tuiprivFree(str);\n\t}\n\tif (whichChanged != c->editBInt) {\n\t\tstr = itoutf16(bb);\n\t\tsetWindowText(c->editBInt, str);\n\t\tuiprivFree(str);\n\t}\n\tif (whichChanged != c->editAInt) {\n\t\tstr = itoutf16(ab);\n\t\tsetWindowText(c->editAInt, str);\n\t\tuiprivFree(str);\n\t}\n\n\tif (whichChanged != c->editHex) {\n\t\trgba2Hex(rb, gb, bb, ab, hexbuf);\n\t\tsetWindowText(c->editHex, hexbuf);\n\t}\n\n\t// TODO TRUE?\n\tinvalidateRect(c->svChooser, NULL, TRUE);\n\tinvalidateRect(c->hSlider, NULL, TRUE);\n\tinvalidateRect(c->preview, NULL, TRUE);\n\tinvalidateRect(c->opacitySlider, NULL, TRUE);\n\n\tc->updating = FALSE;\n}\n\n// this imitates http://blogs.msdn.com/b/wpfsdk/archive/2006/10/26/uncommon-dialogs--font-chooser-and-color-picker-dialogs.aspx\nstatic void drawGrid(ID2D1RenderTarget *rt, D2D1_RECT_F *fillRect)\n{\n\tD2D1_SIZE_F size;\n\tD2D1_PIXEL_FORMAT pformat;\n\tID2D1BitmapRenderTarget *brt;\n\tD2D1_COLOR_F color;\n\tD2D1_BRUSH_PROPERTIES bprop;\n\tID2D1SolidColorBrush *brush;\n\tD2D1_RECT_F rect;\n\tID2D1Bitmap *bitmap;\n\tD2D1_BITMAP_BRUSH_PROPERTIES bbp;\n\tID2D1BitmapBrush *bb;\n\tHRESULT hr;\n\n\t// mind the divisions; they represent the fact the original uses a viewport\n\tsize.width = 100 / 10;\n\tsize.height = 100 / 10;\n\t// yay more ABI bugs\n#ifdef _MSC_VER\n\tpformat = rt->GetPixelFormat();\n#else\n\t{\n\t\ttypedef D2D1_PIXEL_FORMAT *(__stdcall ID2D1RenderTarget::* GetPixelFormatF)(D2D1_PIXEL_FORMAT *) const;\n\t\tGetPixelFormatF gpf;\n\n\t\tgpf = (GetPixelFormatF) (&(rt->GetPixelFormat));\n\t\t(rt->*gpf)(&pformat);\n\t}\n#endif\n\thr = rt->CreateCompatibleRenderTarget(&size, NULL,\n\t\t&pformat, D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE,\n\t\t&brt);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating render target for grid\", hr);\n\n\tbrt->BeginDraw();\n\n\tcolor.r = 1.0;\n\tcolor.g = 1.0;\n\tcolor.b = 1.0;\n\tcolor.a = 1.0;\n\tbrt->Clear(&color);\n\n\tcolor = D2D1::ColorF(D2D1::ColorF::LightGray, 1.0);\n\tZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES));\n\tbprop.opacity = 1.0;\n\tbprop.transform._11 = 1;\n\tbprop.transform._22 = 1;\n\thr = brt->CreateSolidColorBrush(&color, &bprop, &brush);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating brush for grid\", hr);\n\trect.left = 0;\n\trect.top = 0;\n\trect.right = 50 / 10;\n\trect.bottom = 50 / 10;\n\tbrt->FillRectangle(&rect, brush);\n\trect.left = 50 / 10;\n\trect.top = 50 / 10;\n\trect.right = 100 / 10;\n\trect.bottom = 100 / 10;\n\tbrt->FillRectangle(&rect, brush);\n\tbrush->Release();\n\n\thr = brt->EndDraw(NULL, NULL);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error finalizing render target for grid\", hr);\n\thr = brt->GetBitmap(&bitmap);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error getting bitmap for grid\", hr);\n\tbrt->Release();\n\n\tZeroMemory(&bbp, sizeof (D2D1_BITMAP_BRUSH_PROPERTIES));\n\tbbp.extendModeX = D2D1_EXTEND_MODE_WRAP;\n\tbbp.extendModeY = D2D1_EXTEND_MODE_WRAP;\n\tbbp.interpolationMode = D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR;\n\thr = rt->CreateBitmapBrush(bitmap, &bbp, &bprop, &bb);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating bitmap brush for grid\", hr);\n\trt->FillRectangle(fillRect, bb);\n\tbb->Release();\n\tbitmap->Release();\n}\n\n// this interesting approach comes from http://blogs.msdn.com/b/wpfsdk/archive/2006/10/26/uncommon-dialogs--font-chooser-and-color-picker-dialogs.aspx\nstatic void drawSVChooser(struct colorDialog *c, ID2D1RenderTarget *rt)\n{\n\tD2D1_SIZE_F size;\n\tD2D1_RECT_F rect;\n\tdouble rTop, gTop, bTop;\n\tD2D1_GRADIENT_STOP stops[2];\n\tID2D1GradientStopCollection *collection;\n\tD2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES lprop;\n\tD2D1_BRUSH_PROPERTIES bprop;\n\tID2D1LinearGradientBrush *brush;\n\tID2D1LinearGradientBrush *opacity;\n\tID2D1Layer *layer;\n\tD2D1_LAYER_PARAMETERS layerparams;\n\tD2D1_ELLIPSE mparam;\n\tD2D1_COLOR_F mcolor;\n\tID2D1SolidColorBrush *markerBrush;\n\tHRESULT hr;\n\n\tsize = realGetSize(rt);\n\trect.left = 0;\n\trect.top = 0;\n\trect.right = size.width;\n\trect.bottom = size.height;\n\n\tdrawGrid(rt, &rect);\n\n\t// first, draw a vertical gradient from the current hue at max S/V to black\n\t// the source example draws it upside down; let's do so too just to be safe\n\thsv2RGB(c->h, 1.0, 1.0, &rTop, &gTop, &bTop);\n\tstops[0].position = 0;\n\tstops[0].color.r = 0.0;\n\tstops[0].color.g = 0.0;\n\tstops[0].color.b = 0.0;\n\tstops[0].color.a = 1.0;\n\tstops[1].position = 1;\n\tstops[1].color.r = rTop;\n\tstops[1].color.g = gTop;\n\tstops[1].color.b = bTop;\n\tstops[1].color.a = 1.0;\n\thr = rt->CreateGradientStopCollection(stops, 2,\n\t\tD2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP,\n\t\t&collection);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error making gradient stop collection for first gradient in SV chooser\", hr);\n\tZeroMemory(&lprop, sizeof (D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES));\n\tlprop.startPoint.x = size.width / 2;\n\tlprop.startPoint.y = size.height;\n\tlprop.endPoint.x = size.width / 2;\n\tlprop.endPoint.y = 0;\n\t// TODO decide what to do about the duplication of this\n\tZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES));\n\tbprop.opacity = c->a;\t\t// note this part; we also use it below for the layer\n\tbprop.transform._11 = 1;\n\tbprop.transform._22 = 1;\n\thr = rt->CreateLinearGradientBrush(&lprop, &bprop,\n\t\tcollection, &brush);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error making gradient brush for first gradient in SV chooser\", hr);\n\trt->FillRectangle(&rect, brush);\n\tbrush->Release();\n\tcollection->Release();\n\n\t// second, create an opacity mask for the third step: a horizontal gradientthat goes from opaque to translucent\n\tstops[0].position = 0;\n\tstops[0].color.r = 0.0;\n\tstops[0].color.g = 0.0;\n\tstops[0].color.b = 0.0;\n\tstops[0].color.a = 1.0;\n\tstops[1].position = 1;\n\tstops[1].color.r = 0.0;\n\tstops[1].color.g = 0.0;\n\tstops[1].color.b = 0.0;\n\tstops[1].color.a = 0.0;\n\thr = rt->CreateGradientStopCollection(stops, 2,\n\t\tD2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP,\n\t\t&collection);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error making gradient stop collection for opacity mask gradient in SV chooser\", hr);\n\tZeroMemory(&lprop, sizeof (D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES));\n\tlprop.startPoint.x = 0;\n\tlprop.startPoint.y = size.height / 2;\n\tlprop.endPoint.x = size.width;\n\tlprop.endPoint.y = size.height / 2;\n\tZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES));\n\tbprop.opacity = 1.0;\n\tbprop.transform._11 = 1;\n\tbprop.transform._22 = 1;\n\thr = rt->CreateLinearGradientBrush(&lprop, &bprop,\n\t\tcollection, &opacity);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error making gradient brush for opacity mask gradient in SV chooser\", hr);\n\tcollection->Release();\n\n\t// 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\n\tstops[0].position = 0;\n\tstops[0].color.r = 1.0;\n\tstops[0].color.g = 1.0;\n\tstops[0].color.b = 1.0;\n\tstops[0].color.a = 1.0;\n\tstops[1].position = 1;\n\tstops[1].color.r = 0.0;\n\tstops[1].color.g = 0.0;\n\tstops[1].color.b = 0.0;\n\tstops[1].color.a = 1.0;\n\thr = rt->CreateGradientStopCollection(stops, 2,\n\t\tD2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP,\n\t\t&collection);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error making gradient stop collection for second gradient in SV chooser\", hr);\n\tZeroMemory(&lprop, sizeof (D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES));\n\tlprop.startPoint.x = size.width / 2;\n\tlprop.startPoint.y = 0;\n\tlprop.endPoint.x = size.width / 2;\n\tlprop.endPoint.y = size.height;\n\tZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES));\n\tbprop.opacity = 1.0;\n\tbprop.transform._11 = 1;\n\tbprop.transform._22 = 1;\n\thr = rt->CreateLinearGradientBrush(&lprop, &bprop,\n\t\tcollection, &brush);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error making gradient brush for second gradient in SV chooser\", hr);\n\t// oh but wait we can't use FillRectangle() with an opacity mask\n\t// and we can't use FillGeometry() with both an opacity mask and a non-bitmap\n\t// layers it is!\n\thr = rt->CreateLayer(&size, &layer);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error making layer for second gradient in SV chooser\", hr);\n\tZeroMemory(&layerparams, sizeof (D2D1_LAYER_PARAMETERS));\n\tlayerparams.contentBounds = rect;\n\t// TODO make sure these are right\n\tlayerparams.geometricMask = NULL;\n\tlayerparams.maskAntialiasMode = D2D1_ANTIALIAS_MODE_PER_PRIMITIVE;\n\tlayerparams.maskTransform._11 = 1;\n\tlayerparams.maskTransform._22 = 1;\n\tlayerparams.opacity = c->a;\t\t\t// here's the other use of c->a to note\n\tlayerparams.opacityBrush = opacity;\n\tlayerparams.layerOptions = D2D1_LAYER_OPTIONS_NONE;\n\trt->PushLayer(&layerparams, layer);\n\trt->FillRectangle(&rect, brush);\n\trt->PopLayer();\n\tlayer->Release();\n\tbrush->Release();\n\tcollection->Release();\n\topacity->Release();\n\n\t// and now we just draw the marker\n\tZeroMemory(&mparam, sizeof (D2D1_ELLIPSE));\n\tmparam.point.x = c->s * size.width;\n\tmparam.point.y = (1 - c->v) * size.height;\n\tmparam.radiusX = 7;\n\tmparam.radiusY = 7;\n\t// TODO make the color contrast?\n\tmcolor.r = 1.0;\n\tmcolor.g = 1.0;\n\tmcolor.b = 1.0;\n\tmcolor.a = 1.0;\n\tbprop.opacity = 1.0;\t\t// the marker should always be opaque\n\thr = rt->CreateSolidColorBrush(&mcolor, &bprop, &markerBrush);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating brush for SV chooser marker\", hr);\n\trt->DrawEllipse(&mparam, markerBrush, 2, NULL);\n\tmarkerBrush->Release();\n}\n\nstatic LRESULT CALLBACK svChooserSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)\n{\n\tID2D1RenderTarget *rt;\n\tstruct colorDialog *c;\n\tD2D1_POINT_2F *pos;\n\tD2D1_SIZE_F *size;\n\n\tc = (struct colorDialog *) dwRefData;\n\tswitch (uMsg) {\n\tcase msgD2DScratchPaint:\n\t\trt = (ID2D1RenderTarget *) lParam;\n\t\tdrawSVChooser(c, rt);\n\t\treturn 0;\n\tcase msgD2DScratchLButtonDown:\n\t\tpos = (D2D1_POINT_2F *) wParam;\n\t\tsize = (D2D1_SIZE_F *) lParam;\n\t\tc->s = pos->x / size->width;\n\t\tc->v = 1 - (pos->y / size->height);\n\t\tupdateDialog(c, NULL);\n\t\treturn 0;\n\tcase WM_NCDESTROY:\n\t\tif (RemoveWindowSubclass(hwnd, svChooserSubProc, uIdSubclass) == FALSE)\n\t\t\tlogLastError(L\"error removing color dialog SV chooser subclass\");\n\t\tbreak;\n\t}\n\treturn DefSubclassProc(hwnd, uMsg, wParam, lParam);\n}\n\nstatic void drawArrow(ID2D1RenderTarget *rt, D2D1_POINT_2F center, double hypot)\n{\n\tdouble leg;\n\tD2D1_RECT_F rect;\n\tD2D1_MATRIX_3X2_F oldtf, rotate;\n\tD2D1_COLOR_F color;\n\tD2D1_BRUSH_PROPERTIES bprop;\n\tID2D1SolidColorBrush *brush;\n\tHRESULT hr;\n\n\t// to avoid needing a geometry, this will just be a rotated square\n\t// compute the length of each side; the diagonal of the square is 2 * offset to gradient\n\t// a^2 + a^2 = c^2 -> 2a^2 = c^2\n\t// a = sqrt(c^2/2)\n\thypot *= hypot;\n\thypot /= 2;\n\tleg = sqrt(hypot);\n\trect.left = center.x - leg;\n\trect.top = center.y - leg;\n\trect.right = center.x + leg;\n\trect.bottom = center.y + leg;\n\n\t// now we need to rotate the render target 45° (either way works) about the center point\n\trt->GetTransform(&oldtf);\n\trotate = oldtf * D2D1::Matrix3x2F::Rotation(45, center);\n\trt->SetTransform(&rotate);\n\n\t// and draw\n\tcolor.r = 0.0;\n\tcolor.g = 0.0;\n\tcolor.b = 0.0;\n\tcolor.a = 1.0;\n\tZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES));\n\tbprop.opacity = 1.0;\n\tbprop.transform._11 = 1;\n\tbprop.transform._22 = 1;\n\thr = rt->CreateSolidColorBrush(&color, &bprop, &brush);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating brush for arrow\", hr);\n\trt->FillRectangle(&rect, brush);\n\tbrush->Release();\n\n\t// clean up\n\trt->SetTransform(&oldtf);\n}\n\n// 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\n#define nStops (30)\n#define degPerStop (360 / nStops)\n#define stopIncr (1.0 / ((double) nStops))\n\nstatic void drawHSlider(struct colorDialog *c, ID2D1RenderTarget *rt)\n{\n\tD2D1_SIZE_F size;\n\tD2D1_RECT_F rect;\n\tD2D1_GRADIENT_STOP stops[nStops];\n\tdouble r, g, b;\n\tint i;\n\tdouble h;\n\tID2D1GradientStopCollection *collection;\n\tD2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES lprop;\n\tD2D1_BRUSH_PROPERTIES bprop;\n\tID2D1LinearGradientBrush *brush;\n\tdouble hypot;\n\tD2D1_POINT_2F center;\n\tHRESULT hr;\n\n\tsize = realGetSize(rt);\n\trect.left = size.width / 6;\t\t// leftmost sixth for arrow\n\trect.top = 0;\n\trect.right = size.width;\n\trect.bottom = size.height;\n\n\tfor (i = 0; i < nStops; i++) {\n\t\th = ((double) (i * degPerStop)) / 360.0;\n\t\tif (i == (nStops - 1))\n\t\t\th = 0;\n\t\thsv2RGB(h, 1.0, 1.0, &r, &g, &b);\n\t\tstops[i].position = ((double) i) * stopIncr;\n\t\tstops[i].color.r = r;\n\t\tstops[i].color.g = g;\n\t\tstops[i].color.b = b;\n\t\tstops[i].color.a = 1.0;\n\t}\n\t// and pin the last one\n\tstops[i - 1].position = 1.0;\n\n\thr = rt->CreateGradientStopCollection(stops, nStops,\n\t\t// note that in this case this gamma is explicitly specified by the original\n\t\tD2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP,\n\t\t&collection);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating stop collection for H slider gradient\", hr);\n\tZeroMemory(&lprop, sizeof (D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES));\n\tlprop.startPoint.x = (rect.right - rect.left) / 2;\n\tlprop.startPoint.y = 0;\n\tlprop.endPoint.x = (rect.right - rect.left) / 2;\n\tlprop.endPoint.y = size.height;\n\tZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES));\n\tbprop.opacity = 1.0;\n\tbprop.transform._11 = 1;\n\tbprop.transform._22 = 1;\n\thr = rt->CreateLinearGradientBrush(&lprop, &bprop,\n\t\tcollection, &brush);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating gradient brush for H slider\", hr);\n\trt->FillRectangle(&rect, brush);\n\tbrush->Release();\n\tcollection->Release();\n\n\t// now draw a black arrow\n\tcenter.x = 0;\n\tcenter.y = c->h * size.height;\n\thypot = rect.left;\n\tdrawArrow(rt, center, hypot);\n}\n\nstatic LRESULT CALLBACK hSliderSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)\n{\n\tID2D1RenderTarget *rt;\n\tstruct colorDialog *c;\n\tD2D1_POINT_2F *pos;\n\tD2D1_SIZE_F *size;\n\n\tc = (struct colorDialog *) dwRefData;\n\tswitch (uMsg) {\n\tcase msgD2DScratchPaint:\n\t\trt = (ID2D1RenderTarget *) lParam;\n\t\tdrawHSlider(c, rt);\n\t\treturn 0;\n\tcase msgD2DScratchLButtonDown:\n\t\tpos = (D2D1_POINT_2F *) wParam;\n\t\tsize = (D2D1_SIZE_F *) lParam;\n\t\tc->h = pos->y / size->height;\n\t\tupdateDialog(c, NULL);\n\t\treturn 0;\n\tcase WM_NCDESTROY:\n\t\tif (RemoveWindowSubclass(hwnd, hSliderSubProc, uIdSubclass) == FALSE)\n\t\t\tlogLastError(L\"error removing color dialog H slider subclass\");\n\t\tbreak;\n\t}\n\treturn DefSubclassProc(hwnd, uMsg, wParam, lParam);\n}\n\nstatic void drawPreview(struct colorDialog *c, ID2D1RenderTarget *rt)\n{\n\tD2D1_SIZE_F size;\n\tD2D1_RECT_F rect;\n\tdouble r, g, b;\n\tD2D1_COLOR_F color;\n\tD2D1_BRUSH_PROPERTIES bprop;\n\tID2D1SolidColorBrush *brush;\n\tHRESULT hr;\n\n\tsize = realGetSize(rt);\n\trect.left = 0;\n\trect.top = 0;\n\trect.right = size.width;\n\trect.bottom = size.height;\n\n\tdrawGrid(rt, &rect);\n\n\thsv2RGB(c->h, c->s, c->v, &r, &g, &b);\n\tcolor.r = r;\n\tcolor.g = g;\n\tcolor.b = b;\n\tcolor.a = c->a;\n\tZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES));\n\tbprop.opacity = 1.0;\n\tbprop.transform._11 = 1;\n\tbprop.transform._22 = 1;\n\thr = rt->CreateSolidColorBrush(&color, &bprop, &brush);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating brush for preview\", hr);\n\trt->FillRectangle(&rect, brush);\n\tbrush->Release();\n}\n\nstatic LRESULT CALLBACK previewSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)\n{\n\tID2D1RenderTarget *rt;\n\tstruct colorDialog *c;\n\n\tc = (struct colorDialog *) dwRefData;\n\tswitch (uMsg) {\n\tcase msgD2DScratchPaint:\n\t\trt = (ID2D1RenderTarget *) lParam;\n\t\tdrawPreview(c, rt);\n\t\treturn 0;\n\tcase WM_NCDESTROY:\n\t\tif (RemoveWindowSubclass(hwnd, previewSubProc, uIdSubclass) == FALSE)\n\t\t\tlogLastError(L\"error removing color dialog previewer subclass\");\n\t\tbreak;\n\t}\n\treturn DefSubclassProc(hwnd, uMsg, wParam, lParam);\n}\n\n// once again, this is based on the Microsoft sample above\nstatic void drawOpacitySlider(struct colorDialog *c, ID2D1RenderTarget *rt)\n{\n\tD2D1_SIZE_F size;\n\tD2D1_RECT_F rect;\n\tD2D1_GRADIENT_STOP stops[2];\n\tID2D1GradientStopCollection *collection;\n\tD2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES lprop;\n\tD2D1_BRUSH_PROPERTIES bprop;\n\tID2D1LinearGradientBrush *brush;\n\tdouble hypot;\n\tD2D1_POINT_2F center;\n\tHRESULT hr;\n\n\tsize = realGetSize(rt);\n\trect.left = 0;\n\trect.top = 0;\n\trect.right = size.width;\n\trect.bottom = size.height * (5.0 / 6.0);\t\t// bottommost sixth for arrow\n\n\tdrawGrid(rt, &rect);\n\n\tstops[0].position = 0.0;\n\tstops[0].color.r = 0.0;\n\tstops[0].color.g = 0.0;\n\tstops[0].color.b = 0.0;\n\tstops[0].color.a = 1.0;\n\tstops[1].position = 1.0;\n\tstops[1].color.r = 1.0;\t\t// this is the XAML color Transparent, as in the source\n\tstops[1].color.g = 1.0;\n\tstops[1].color.b = 1.0;\n\tstops[1].color.a = 0.0;\n\thr = rt->CreateGradientStopCollection(stops, 2,\n\t\t// note that in this case this gamma is explicitly specified by the original\n\t\tD2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP,\n\t\t&collection);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating stop collection for opacity slider gradient\", hr);\n\tZeroMemory(&lprop, sizeof (D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES));\n\tlprop.startPoint.x = 0;\n\tlprop.startPoint.y = (rect.bottom - rect.top) / 2;\n\tlprop.endPoint.x = size.width;\n\tlprop.endPoint.y = (rect.bottom - rect.top) / 2;\n\tZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES));\n\tbprop.opacity = 1.0;\n\tbprop.transform._11 = 1;\n\tbprop.transform._22 = 1;\n\thr = rt->CreateLinearGradientBrush(&lprop, &bprop,\n\t\tcollection, &brush);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating gradient brush for opacity slider\", hr);\n\trt->FillRectangle(&rect, brush);\n\tbrush->Release();\n\tcollection->Release();\n\n\t// now draw a black arrow\n\tcenter.x = (1 - c->a) * size.width;\n\tcenter.y = size.height;\n\thypot = size.height - rect.bottom;\n\tdrawArrow(rt, center, hypot);\n}\n\nstatic LRESULT CALLBACK opacitySliderSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)\n{\n\tID2D1RenderTarget *rt;\n\tstruct colorDialog *c;\n\tD2D1_POINT_2F *pos;\n\tD2D1_SIZE_F *size;\n\n\tc = (struct colorDialog *) dwRefData;\n\tswitch (uMsg) {\n\tcase msgD2DScratchPaint:\n\t\trt = (ID2D1RenderTarget *) lParam;\n\t\tdrawOpacitySlider(c, rt);\n\t\treturn 0;\n\tcase msgD2DScratchLButtonDown:\n\t\tpos = (D2D1_POINT_2F *) wParam;\n\t\tsize = (D2D1_SIZE_F *) lParam;\n\t\tc->a = 1 - (pos->x / size->width);\n\t\tupdateDialog(c, NULL);\n\t\treturn 0;\n\tcase WM_NCDESTROY:\n\t\tif (RemoveWindowSubclass(hwnd, opacitySliderSubProc, uIdSubclass) == FALSE)\n\t\t\tlogLastError(L\"error removing color dialog opacity slider subclass\");\n\t\tbreak;\n\t}\n\treturn DefSubclassProc(hwnd, uMsg, wParam, lParam);\n}\n\n// TODO extract into d2dscratch.cpp, use in font dialog\nHWND replaceWithD2DScratch(HWND parent, int id, SUBCLASSPROC subproc, void *data)\n{\n\tHWND replace;\n\tRECT r;\n\n\treplace = getDlgItem(parent, id);\n\tuiWindowsEnsureGetWindowRect(replace, &r);\n\tmapWindowRect(NULL, parent, &r);\n\tuiWindowsEnsureDestroyWindow(replace);\n\treturn newD2DScratch(parent, &r, (HMENU) id, subproc, (DWORD_PTR) data);\n\t// TODO preserve Z-order\n}\n\n// a few issues:\n// - 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\n// - labels are too low; need to adjust them by the font's internal leading\n// fixupControlPositions() and the following helper routines fix that for us\n\nstatic LONG offsetTo(HWND a, HWND b)\n{\n\tRECT ra, rb;\n\n\tuiWindowsEnsureGetWindowRect(a, &ra);\n\tuiWindowsEnsureGetWindowRect(b, &rb);\n\treturn rb.top - ra.bottom;\n}\n\nstatic void moveWindowsUp(struct colorDialog *c, LONG by, ...)\n{\n\tva_list ap;\n\tHWND cur;\n\tRECT r;\n\n\tva_start(ap, by);\n\tfor (;;) {\n\t\tcur = va_arg(ap, HWND);\n\t\tif (cur == NULL)\n\t\t\tbreak;\n\t\tuiWindowsEnsureGetWindowRect(cur, &r);\n\t\tmapWindowRect(NULL, c->hwnd, &r);\n\t\tr.top -= by;\n\t\tr.bottom -= by;\n\t\t// TODO this isn't technically during a resize\n\t\tuiWindowsEnsureMoveWindowDuringResize(cur,\n\t\t\tr.left, r.top,\n\t\t\tr.right - r.left, r.bottom - r.top);\n\t}\n\tva_end(ap);\n}\n\nstatic void fixupControlPositions(struct colorDialog *c)\n{\n\tHWND labelH;\n\tHWND labelS;\n\tHWND labelV;\n\tHWND labelR;\n\tHWND labelG;\n\tHWND labelB;\n\tHWND labelA;\n\tHWND labelHex;\n\tLONG offset;\n\tuiWindowsSizing sizing;\n\n\tlabelH = getDlgItem(c->hwnd, rcHLabel);\n\tlabelS = getDlgItem(c->hwnd, rcSLabel);\n\tlabelV = getDlgItem(c->hwnd, rcVLabel);\n\tlabelR = getDlgItem(c->hwnd, rcRLabel);\n\tlabelG = getDlgItem(c->hwnd, rcGLabel);\n\tlabelB = getDlgItem(c->hwnd, rcBLabel);\n\tlabelA = getDlgItem(c->hwnd, rcALabel);\n\tlabelHex = getDlgItem(c->hwnd, rcHexLabel);\n\n\toffset = offsetTo(c->editH, c->editS);\n\tmoveWindowsUp(c, offset,\n\t\tlabelS, c->editS,\n\t\tlabelG, c->editGDouble, c->editGInt,\n\t\tNULL);\n\toffset = offsetTo(c->editS, c->editV);\n\tmoveWindowsUp(c, offset,\n\t\tlabelV, c->editV,\n\t\tlabelB, c->editBDouble, c->editBInt,\n\t\tNULL);\n\toffset = offsetTo(c->editBDouble, c->editADouble);\n\tmoveWindowsUp(c, offset,\n\t\tlabelA, c->editADouble, c->editAInt,\n\t\tNULL);\n\n\tgetSizing(c->hwnd, &sizing, (HFONT) SendMessageW(labelH, WM_GETFONT, 0, 0));\n\toffset = sizing.InternalLeading;\n\tmoveWindowsUp(c, offset,\n\t\tlabelH, labelS, labelV,\n\t\tlabelR, labelG, labelB, labelA,\n\t\tlabelHex,\n\t\tNULL);\n}\n\nstatic struct colorDialog *beginColorDialog(HWND hwnd, LPARAM lParam)\n{\n\tstruct colorDialog *c;\n\n\tc = uiprivNew(struct colorDialog);\n\tc->hwnd = hwnd;\n\tc->out = (struct colorDialogRGBA *) lParam;\n\t// load initial values now\n\trgb2HSV(c->out->r, c->out->g, c->out->b, &(c->h), &(c->s), &(c->v));\n\tc->a = c->out->a;\n\n\t// TODO set up d2dscratches\n\n\t// TODO prefix all these with rcColor instead of just rc\n\tc->editH = getDlgItem(c->hwnd, rcH);\n\tc->editS = getDlgItem(c->hwnd, rcS);\n\tc->editV = getDlgItem(c->hwnd, rcV);\n\tc->editRDouble = getDlgItem(c->hwnd, rcRDouble);\n\tc->editRInt = getDlgItem(c->hwnd, rcRInt);\n\tc->editGDouble = getDlgItem(c->hwnd, rcGDouble);\n\tc->editGInt = getDlgItem(c->hwnd, rcGInt);\n\tc->editBDouble = getDlgItem(c->hwnd, rcBDouble);\n\tc->editBInt = getDlgItem(c->hwnd, rcBInt);\n\tc->editADouble = getDlgItem(c->hwnd, rcADouble);\n\tc->editAInt = getDlgItem(c->hwnd, rcAInt);\n\tc->editHex = getDlgItem(c->hwnd, rcHex);\n\n\tc->svChooser = replaceWithD2DScratch(c->hwnd, rcColorSVChooser, svChooserSubProc, c);\n\tc->hSlider = replaceWithD2DScratch(c->hwnd, rcColorHSlider, hSliderSubProc, c);\n\tc->preview = replaceWithD2DScratch(c->hwnd, rcPreview, previewSubProc, c);\n\tc->opacitySlider = replaceWithD2DScratch(c->hwnd, rcOpacitySlider, opacitySliderSubProc, c);\n\n\tfixupControlPositions(c);\n\n\t// and get the ball rolling\n\tupdateDialog(c, NULL);\n\treturn c;\n}\n\nstatic void endColorDialog(struct colorDialog *c, INT_PTR code)\n{\n\tif (EndDialog(c->hwnd, code) == 0)\n\t\tlogLastError(L\"error ending color dialog\");\n\tuiprivFree(c);\n}\n\n// TODO make this void on the font dialog too\nstatic void tryFinishDialog(struct colorDialog *c, WPARAM wParam)\n{\n\t// cancelling\n\tif (LOWORD(wParam) != IDOK) {\n\t\tendColorDialog(c, 1);\n\t\treturn;\n\t}\n\n\t// OK\n\thsv2RGB(c->h, c->s, c->v, &(c->out->r), &(c->out->g), &(c->out->b));\n\tc->out->a = c->a;\n\tendColorDialog(c, 2);\n}\n\nstatic double editDouble(HWND hwnd)\n{\n\tWCHAR *s;\n\tdouble d;\n\n\ts = windowText(hwnd);\n\td = _wtof(s);\n\tuiprivFree(s);\n\treturn d;\n}\n\nstatic void hChanged(struct colorDialog *c)\n{\n\tdouble h;\n\n\th = editDouble(c->editH);\n\tif (h < 0 || h >= 1.0)\t\t// note the >=\n\t\treturn;\n\tc->h = h;\n\tupdateDialog(c, c->editH);\n}\n\nstatic void sChanged(struct colorDialog *c)\n{\n\tdouble s;\n\n\ts = editDouble(c->editS);\n\tif (s < 0 || s > 1)\n\t\treturn;\n\tc->s = s;\n\tupdateDialog(c, c->editS);\n}\n\nstatic void vChanged(struct colorDialog *c)\n{\n\tdouble v;\n\n\tv = editDouble(c->editV);\n\tif (v < 0 || v > 1)\n\t\treturn;\n\tc->v = v;\n\tupdateDialog(c, c->editV);\n}\n\nstatic void rDoubleChanged(struct colorDialog *c)\n{\n\tdouble r, g, b;\n\n\thsv2RGB(c->h, c->s, c->v, &r, &g, &b);\n\tr = editDouble(c->editRDouble);\n\tif (r < 0 || r > 1)\n\t\treturn;\n\trgb2HSV(r, g, b, &(c->h), &(c->s), &(c->v));\n\tupdateDialog(c, c->editRDouble);\n}\n\nstatic void gDoubleChanged(struct colorDialog *c)\n{\n\tdouble r, g, b;\n\n\thsv2RGB(c->h, c->s, c->v, &r, &g, &b);\n\tg = editDouble(c->editGDouble);\n\tif (g < 0 || g > 1)\n\t\treturn;\n\trgb2HSV(r, g, b, &(c->h), &(c->s), &(c->v));\n\tupdateDialog(c, c->editGDouble);\n}\n\nstatic void bDoubleChanged(struct colorDialog *c)\n{\n\tdouble r, g, b;\n\n\thsv2RGB(c->h, c->s, c->v, &r, &g, &b);\n\tb = editDouble(c->editBDouble);\n\tif (b < 0 || b > 1)\n\t\treturn;\n\trgb2HSV(r, g, b, &(c->h), &(c->s), &(c->v));\n\tupdateDialog(c, c->editBDouble);\n}\n\nstatic void aDoubleChanged(struct colorDialog *c)\n{\n\tdouble a;\n\n\ta = editDouble(c->editADouble);\n\tif (a < 0 || a > 1)\n\t\treturn;\n\tc->a = a;\n\tupdateDialog(c, c->editADouble);\n}\n\nstatic int editInt(HWND hwnd)\n{\n\tWCHAR *s;\n\tint i;\n\n\ts = windowText(hwnd);\n\ti = _wtoi(s);\n\tuiprivFree(s);\n\treturn i;\n}\n\nstatic void rIntChanged(struct colorDialog *c)\n{\n\tdouble r, g, b;\n\tint i;\n\n\thsv2RGB(c->h, c->s, c->v, &r, &g, &b);\n\ti = editInt(c->editRInt);\n\tif (i < 0 || i > 255)\n\t\treturn;\n\tr = ((double) i) / 255.0;\n\trgb2HSV(r, g, b, &(c->h), &(c->s), &(c->v));\n\tupdateDialog(c, c->editRInt);\n}\n\nstatic void gIntChanged(struct colorDialog *c)\n{\n\tdouble r, g, b;\n\tint i;\n\n\thsv2RGB(c->h, c->s, c->v, &r, &g, &b);\n\ti = editInt(c->editGInt);\n\tif (i < 0 || i > 255)\n\t\treturn;\n\tg = ((double) i) / 255.0;\n\trgb2HSV(r, g, b, &(c->h), &(c->s), &(c->v));\n\tupdateDialog(c, c->editGInt);\n}\n\nstatic void bIntChanged(struct colorDialog *c)\n{\n\tdouble r, g, b;\n\tint i;\n\n\thsv2RGB(c->h, c->s, c->v, &r, &g, &b);\n\ti = editInt(c->editBInt);\n\tif (i < 0 || i > 255)\n\t\treturn;\n\tb = ((double) i) / 255.0;\n\trgb2HSV(r, g, b, &(c->h), &(c->s), &(c->v));\n\tupdateDialog(c, c->editBInt);\n}\n\nstatic void aIntChanged(struct colorDialog *c)\n{\n\tint a;\n\n\ta = editInt(c->editAInt);\n\tif (a < 0 || a > 255)\n\t\treturn;\n\tc->a = ((double) a) / 255;\n\tupdateDialog(c, c->editAInt);\n}\n\nstatic void hexChanged(struct colorDialog *c)\n{\n\tWCHAR *buf;\n\tdouble r, g, b, a;\n\tBOOL is;\n\n\tbuf = windowText(c->editHex);\n\tis = hex2RGBA(buf, &r, &g, &b, &a);\n\tuiprivFree(buf);\n\tif (!is)\n\t\treturn;\n\trgb2HSV(r, g, b, &(c->h), &(c->s), &(c->v));\n\tc->a = a;\n\tupdateDialog(c, c->editHex);\n}\n\n// TODO change fontdialog to use this\n// note that if we make this const, we get lots of weird compiler errors\nstatic std::map<int, void (*)(struct colorDialog *)> changed = {\n\t{ rcH, hChanged },\n\t{ rcS, sChanged },\n\t{ rcV, vChanged },\n\t{ rcRDouble, rDoubleChanged },\n\t{ rcGDouble, gDoubleChanged },\n\t{ rcBDouble, bDoubleChanged },\n\t{ rcADouble, aDoubleChanged },\n\t{ rcRInt, rIntChanged },\n\t{ rcGInt, gIntChanged },\n\t{ rcBInt, bIntChanged },\n\t{ rcAInt, aIntChanged },\n\t{ rcHex, hexChanged },\n};\n\nstatic INT_PTR CALLBACK colorDialogDlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)\n{\n\tstruct colorDialog *c;\n\n\tc = (struct colorDialog *) GetWindowLongPtrW(hwnd, DWLP_USER);\n\tif (c == NULL) {\n\t\tif (uMsg == WM_INITDIALOG) {\n\t\t\tc = beginColorDialog(hwnd, lParam);\n\t\t\tSetWindowLongPtrW(hwnd, DWLP_USER, (LONG_PTR) c);\n\t\t\treturn TRUE;\n\t\t}\n\t\treturn FALSE;\n\t}\n\n\tswitch (uMsg) {\n\tcase WM_COMMAND:\n\t\tSetWindowLongPtrW(c->hwnd, DWLP_MSGRESULT, 0);\t\t// just in case\n\t\tswitch (LOWORD(wParam)) {\n\t\tcase IDOK:\n\t\tcase IDCANCEL:\n\t\t\tif (HIWORD(wParam) != BN_CLICKED)\n\t\t\t\treturn FALSE;\n\t\t\ttryFinishDialog(c, wParam);\n\t\t\treturn TRUE;\n\t\tcase rcH:\n\t\tcase rcS:\n\t\tcase rcV:\n\t\tcase rcRDouble:\n\t\tcase rcGDouble:\n\t\tcase rcBDouble:\n\t\tcase rcADouble:\n\t\tcase rcRInt:\n\t\tcase rcGInt:\n\t\tcase rcBInt:\n\t\tcase rcAInt:\n\t\tcase rcHex:\n\t\t\tif (HIWORD(wParam) != EN_CHANGE)\n\t\t\t\treturn FALSE;\n\t\t\tif (c->updating)\t\t// prevent infinite recursion during an update\n\t\t\t\treturn FALSE;\n\t\t\t(*(changed[LOWORD(wParam)]))(c);\n\t\t\treturn TRUE;\n\t\t}\n\t\treturn FALSE;\n\t}\n\treturn FALSE;\n}\n\n// because Windows doesn't really support resources in static libraries, we have to embed this directly; oh well\n/*\nrcColorDialog DIALOGEX 13, 54, 344, 209\nSTYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_3DLOOK\nCAPTION \"Color\"\nFONT 9, \"Segoe UI\"\nBEGIN\n\t// this size should be big enough to get at least 256x256 on font sizes >= 8 pt\n\tCTEXT\t\t\"AaBbYyZz\", rcColorSVChooser, 7, 7, 195, 195, SS_NOPREFIX | SS_BLACKRECT\n\n\t// width is the suggested slider height since this is vertical\n\tCTEXT\t\t\"AaBbYyZz\", rcColorHSlider, 206, 7, 15, 195, SS_NOPREFIX | SS_BLACKRECT\n\n\tLTEXT\t\t\"Preview:\", -1, 230, 7, 107, 9, SS_NOPREFIX\n\tCTEXT\t\t\"AaBbYyZz\", rcPreview, 230, 16, 107, 20, SS_NOPREFIX | SS_BLACKRECT\n\n\tLTEXT\t\t\"Opacity:\", -1, 230, 45, 107, 9, SS_NOPREFIX\n\tCTEXT\t\t\"AaBbYyZz\", rcOpacitySlider, 230, 54, 107, 15, SS_NOPREFIX | SS_BLACKRECT\n\n\tLTEXT\t\t\"&H:\", rcHLabel, 230, 81, 8, 8\n\tEDITTEXT\t\trcH, 238, 78, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE\n\tLTEXT\t\t\"&S:\", rcSLabel, 230, 95, 8, 8\n\tEDITTEXT\t\trcS, 238, 92, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE\n\tLTEXT\t\t\"&V:\", rcVLabel, 230, 109, 8, 8\n\tEDITTEXT\t\trcV, 238, 106, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE\n\n\tLTEXT\t\t\"&R:\", rcRLabel, 277, 81, 8, 8\n\tEDITTEXT\t\trcRDouble, 285, 78, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE\n\tEDITTEXT\t\trcRInt, 315, 78, 20, 14, ES_LEFT | ES_AUTOHSCROLL | ES_NUMBER | WS_TABSTOP, WS_EX_CLIENTEDGE\n\tLTEXT\t\t\"&G:\", rcGLabel, 277, 95, 8, 8\n\tEDITTEXT\t\trcGDouble, 285, 92, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE\n\tEDITTEXT\t\trcGInt, 315, 92, 20, 14, ES_LEFT | ES_AUTOHSCROLL | ES_NUMBER | WS_TABSTOP, WS_EX_CLIENTEDGE\n\tLTEXT\t\t\"&B:\", rcBLabel, 277, 109, 8, 8\n\tEDITTEXT\t\trcBDouble, 285, 106, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE\n\tEDITTEXT\t\trcBInt, 315, 106, 20, 14, ES_LEFT | ES_AUTOHSCROLL | ES_NUMBER | WS_TABSTOP, WS_EX_CLIENTEDGE\n\tLTEXT\t\t\"&A:\", rcALabel, 277, 123, 8, 8\n\tEDITTEXT\t\trcADouble, 285, 120, 30, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE\n\tEDITTEXT\t\trcAInt, 315, 120, 20, 14, ES_LEFT | ES_AUTOHSCROLL | ES_NUMBER | WS_TABSTOP, WS_EX_CLIENTEDGE\n\n\tLTEXT\t\t\"He&x:\", rcHexLabel, 269, 146, 16, 8\n\tEDITTEXT\t\trcHex, 285, 143, 50, 14, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP, WS_EX_CLIENTEDGE\n\n\tDEFPUSHBUTTON\t\"OK\", IDOK, 243, 188, 45, 14, WS_GROUP\n\tPUSHBUTTON\t\t\"Cancel\", IDCANCEL, 292, 188, 45, 14, WS_GROUP\nEND\n*/\nstatic const uint8_t data_rcColorDialog[] = {\n\t0x01, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0xC4, 0x00, 0xC8, 0x80,\n\t0x1C, 0x00, 0x0D, 0x00, 0x36, 0x00, 0x58, 0x01,\n\t0xD1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x00,\n\t0x6F, 0x00, 0x6C, 0x00, 0x6F, 0x00, 0x72, 0x00,\n\t0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x01,\n\t0x53, 0x00, 0x65, 0x00, 0x67, 0x00, 0x6F, 0x00,\n\t0x65, 0x00, 0x20, 0x00, 0x55, 0x00, 0x49, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x85, 0x00, 0x02, 0x50,\n\t0x07, 0x00, 0x07, 0x00, 0xC3, 0x00, 0xC3, 0x00,\n\t0x4C, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00,\n\t0x41, 0x00, 0x61, 0x00, 0x42, 0x00, 0x62, 0x00,\n\t0x59, 0x00, 0x79, 0x00, 0x5A, 0x00, 0x7A, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x85, 0x00, 0x02, 0x50,\n\t0xCE, 0x00, 0x07, 0x00, 0x0F, 0x00, 0xC3, 0x00,\n\t0x4D, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00,\n\t0x41, 0x00, 0x61, 0x00, 0x42, 0x00, 0x62, 0x00,\n\t0x59, 0x00, 0x79, 0x00, 0x5A, 0x00, 0x7A, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x02, 0x50,\n\t0xE6, 0x00, 0x07, 0x00, 0x6B, 0x00, 0x09, 0x00,\n\t0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x82, 0x00,\n\t0x50, 0x00, 0x72, 0x00, 0x65, 0x00, 0x76, 0x00,\n\t0x69, 0x00, 0x65, 0x00, 0x77, 0x00, 0x3A, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x85, 0x00, 0x02, 0x50,\n\t0xE6, 0x00, 0x10, 0x00, 0x6B, 0x00, 0x14, 0x00,\n\t0x4E, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00,\n\t0x41, 0x00, 0x61, 0x00, 0x42, 0x00, 0x62, 0x00,\n\t0x59, 0x00, 0x79, 0x00, 0x5A, 0x00, 0x7A, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x02, 0x50,\n\t0xE6, 0x00, 0x2D, 0x00, 0x6B, 0x00, 0x09, 0x00,\n\t0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x82, 0x00,\n\t0x4F, 0x00, 0x70, 0x00, 0x61, 0x00, 0x63, 0x00,\n\t0x69, 0x00, 0x74, 0x00, 0x79, 0x00, 0x3A, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x85, 0x00, 0x02, 0x50,\n\t0xE6, 0x00, 0x36, 0x00, 0x6B, 0x00, 0x0F, 0x00,\n\t0x4F, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00,\n\t0x41, 0x00, 0x61, 0x00, 0x42, 0x00, 0x62, 0x00,\n\t0x59, 0x00, 0x79, 0x00, 0x5A, 0x00, 0x7A, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50,\n\t0xE6, 0x00, 0x51, 0x00, 0x08, 0x00, 0x08, 0x00,\n\t0x5C, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00,\n\t0x26, 0x00, 0x48, 0x00, 0x3A, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x02, 0x00, 0x00, 0x80, 0x00, 0x81, 0x50,\n\t0xEE, 0x00, 0x4E, 0x00, 0x1E, 0x00, 0x0E, 0x00,\n\t0x50, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x81, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50,\n\t0xE6, 0x00, 0x5F, 0x00, 0x08, 0x00, 0x08, 0x00,\n\t0x5D, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00,\n\t0x26, 0x00, 0x53, 0x00, 0x3A, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x02, 0x00, 0x00, 0x80, 0x00, 0x81, 0x50,\n\t0xEE, 0x00, 0x5C, 0x00, 0x1E, 0x00, 0x0E, 0x00,\n\t0x51, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x81, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50,\n\t0xE6, 0x00, 0x6D, 0x00, 0x08, 0x00, 0x08, 0x00,\n\t0x5E, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00,\n\t0x26, 0x00, 0x56, 0x00, 0x3A, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x02, 0x00, 0x00, 0x80, 0x00, 0x81, 0x50,\n\t0xEE, 0x00, 0x6A, 0x00, 0x1E, 0x00, 0x0E, 0x00,\n\t0x52, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x81, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50,\n\t0x15, 0x01, 0x51, 0x00, 0x08, 0x00, 0x08, 0x00,\n\t0x5F, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00,\n\t0x26, 0x00, 0x52, 0x00, 0x3A, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x02, 0x00, 0x00, 0x80, 0x00, 0x81, 0x50,\n\t0x1D, 0x01, 0x4E, 0x00, 0x1E, 0x00, 0x0E, 0x00,\n\t0x53, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x81, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x02, 0x00, 0x00, 0x80, 0x20, 0x81, 0x50,\n\t0x3B, 0x01, 0x4E, 0x00, 0x14, 0x00, 0x0E, 0x00,\n\t0x54, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x81, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50,\n\t0x15, 0x01, 0x5F, 0x00, 0x08, 0x00, 0x08, 0x00,\n\t0x60, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00,\n\t0x26, 0x00, 0x47, 0x00, 0x3A, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x02, 0x00, 0x00, 0x80, 0x00, 0x81, 0x50,\n\t0x1D, 0x01, 0x5C, 0x00, 0x1E, 0x00, 0x0E, 0x00,\n\t0x55, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x81, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x02, 0x00, 0x00, 0x80, 0x20, 0x81, 0x50,\n\t0x3B, 0x01, 0x5C, 0x00, 0x14, 0x00, 0x0E, 0x00,\n\t0x56, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x81, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50,\n\t0x15, 0x01, 0x6D, 0x00, 0x08, 0x00, 0x08, 0x00,\n\t0x61, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00,\n\t0x26, 0x00, 0x42, 0x00, 0x3A, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x02, 0x00, 0x00, 0x80, 0x00, 0x81, 0x50,\n\t0x1D, 0x01, 0x6A, 0x00, 0x1E, 0x00, 0x0E, 0x00,\n\t0x57, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x81, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x02, 0x00, 0x00, 0x80, 0x20, 0x81, 0x50,\n\t0x3B, 0x01, 0x6A, 0x00, 0x14, 0x00, 0x0E, 0x00,\n\t0x58, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x81, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50,\n\t0x15, 0x01, 0x7B, 0x00, 0x08, 0x00, 0x08, 0x00,\n\t0x62, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00,\n\t0x26, 0x00, 0x41, 0x00, 0x3A, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x02, 0x00, 0x00, 0x80, 0x00, 0x81, 0x50,\n\t0x1D, 0x01, 0x78, 0x00, 0x1E, 0x00, 0x0E, 0x00,\n\t0x59, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x81, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x02, 0x00, 0x00, 0x80, 0x20, 0x81, 0x50,\n\t0x3B, 0x01, 0x78, 0x00, 0x14, 0x00, 0x0E, 0x00,\n\t0x5A, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x81, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50,\n\t0x0D, 0x01, 0x92, 0x00, 0x10, 0x00, 0x08, 0x00,\n\t0x63, 0x04, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00,\n\t0x48, 0x00, 0x65, 0x00, 0x26, 0x00, 0x78, 0x00,\n\t0x3A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00,\n\t0x80, 0x00, 0x81, 0x50, 0x1D, 0x01, 0x8F, 0x00,\n\t0x32, 0x00, 0x0E, 0x00, 0x5B, 0x04, 0x00, 0x00,\n\t0xFF, 0xFF, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x01, 0x00, 0x03, 0x50, 0xF3, 0x00, 0xBC, 0x00,\n\t0x2D, 0x00, 0x0E, 0x00, 0x01, 0x00, 0x00, 0x00,\n\t0xFF, 0xFF, 0x80, 0x00, 0x4F, 0x00, 0x4B, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x50,\n\t0x24, 0x01, 0xBC, 0x00, 0x2D, 0x00, 0x0E, 0x00,\n\t0x02, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00,\n\t0x43, 0x00, 0x61, 0x00, 0x6E, 0x00, 0x63, 0x00,\n\t0x65, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, \n};\nstatic_assert(ARRAYSIZE(data_rcColorDialog) == 1144, \"wrong size for resource rcColorDialog\");\n\nBOOL showColorDialog(HWND parent, struct colorDialogRGBA *c)\n{\n\tswitch (DialogBoxIndirectParamW(hInstance, (const DLGTEMPLATE *) data_rcColorDialog, parent, colorDialogDlgProc, (LPARAM) c)) {\n\tcase 1:\t\t\t// cancel\n\t\treturn FALSE;\n\tcase 2:\t\t\t// ok\n\t\t// make the compiler happy by putting the return after the switch\n\t\tbreak;\n\tdefault:\n\t\tlogLastError(L\"error running color dialog\");\n\t}\n\treturn TRUE;\n}\n"
  },
  {
    "path": "windows/combobox.cpp",
    "content": "// 20 may 2015\n#include \"uipriv_windows.hpp\"\n\n// 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\n\nstruct uiCombobox {\n\tuiWindowsControl c;\n\tHWND hwnd;\n\tvoid (*onSelected)(uiCombobox *, void *);\n\tvoid *onSelectedData;\n};\n\nstatic BOOL onWM_COMMAND(uiControl *cc, HWND hwnd, WORD code, LRESULT *lResult)\n{\n\tuiCombobox *c = uiCombobox(cc);\n\n\tif (code != CBN_SELCHANGE)\n\t\treturn FALSE;\n\t(*(c->onSelected))(c, c->onSelectedData);\n\t*lResult = 0;\n\treturn TRUE;\n}\n\nvoid uiComboboxDestroy(uiControl *cc)\n{\n\tuiCombobox *c = uiCombobox(cc);\n\n\tuiWindowsUnregisterWM_COMMANDHandler(c->hwnd);\n\tuiWindowsEnsureDestroyWindow(c->hwnd);\n\tuiFreeControl(uiControl(c));\n}\n\nuiWindowsControlAllDefaultsExceptDestroy(uiCombobox)\n\n// from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing\n#define comboboxWidth 107\t/* this is actually the shorter progress bar width, but Microsoft only indicates as wide as necessary; LONGTERM */\n#define comboboxHeight 14\t/* LONGTERM: is this too high? */\n\nstatic void uiComboboxMinimumSize(uiWindowsControl *cc, int *width, int *height)\n{\n\tuiCombobox *c = uiCombobox(cc);\n\tuiWindowsSizing sizing;\n\tint x, y;\n\n\tx = comboboxWidth;\n\ty = comboboxHeight;\n\tuiWindowsGetSizing(c->hwnd, &sizing);\n\tuiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y);\n\t*width = x;\n\t*height = y;\n}\n\nstatic void defaultOnSelected(uiCombobox *c, void *data)\n{\n\t// do nothing\n}\n\nvoid uiComboboxAppend(uiCombobox *c, const char *text)\n{\n\tWCHAR *wtext;\n\tLRESULT res;\n\n\twtext = toUTF16(text);\n\tres = SendMessageW(c->hwnd, CB_ADDSTRING, 0, (LPARAM) wtext);\n\tif (res == (LRESULT) CB_ERR)\n\t\tlogLastError(L\"error appending item to uiCombobox\");\n\telse if (res == (LRESULT) CB_ERRSPACE)\n\t\tlogLastError(L\"memory exhausted appending item to uiCombobox\");\n\tuiprivFree(wtext);\n}\n\nint uiComboboxSelected(uiCombobox *c)\n{\n\tLRESULT n;\n\n\tn = SendMessage(c->hwnd, CB_GETCURSEL, 0, 0);\n\tif (n == (LRESULT) CB_ERR)\n\t\treturn -1;\n\treturn n;\n}\n\nvoid uiComboboxSetSelected(uiCombobox *c, int n)\n{\n\t// TODO error check\n\tSendMessageW(c->hwnd, CB_SETCURSEL, (WPARAM) n, 0);\n}\n\nvoid uiComboboxOnSelected(uiCombobox *c, void (*f)(uiCombobox *c, void *data), void *data)\n{\n\tc->onSelected = f;\n\tc->onSelectedData = data;\n}\n\nuiCombobox *uiNewCombobox(void)\n{\n\tuiCombobox *c;\n\n\tuiWindowsNewControl(uiCombobox, c);\n\n\tc->hwnd = uiWindowsEnsureCreateControlHWND(WS_EX_CLIENTEDGE,\n\t\tL\"combobox\", L\"\",\n\t\tCBS_DROPDOWNLIST | WS_TABSTOP,\n\t\thInstance, NULL,\n\t\tTRUE);\n\n\tuiWindowsRegisterWM_COMMANDHandler(c->hwnd, onWM_COMMAND, uiControl(c));\n\tuiComboboxOnSelected(c, defaultOnSelected, NULL);\n\n\treturn c;\n}\n"
  },
  {
    "path": "windows/compilerver.hpp",
    "content": "// 9 june 2015\n\n// Visual Studio (Microsoft's compilers)\n// VS2013 is needed for va_copy().\n#ifdef _MSC_VER\n#if _MSC_VER < 1800\n#error Visual Studio 2013 or higher is required to build libui.\n#endif\n#endif\n\n// LONGTERM MinGW\n\n// other compilers can be added here as necessary\n\n/* TODO this should not be necessary, but I don't know\n\nhere's @bcampbell's original comment:\n// sanity check - make sure wchar_t is 16 bits (the assumption on windows)\n// (MinGW-w64 gcc does seem to define a 16bit wchar_t, but you never know. Other windows gcc ports might not)\n\nhere's what I got when I tried to investigate on irc.oftc.net/#mingw-w64:\n{\n[08:45:20]  <lh_mouse>\tandlabs, 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.\n[09:22:16]  <andlabs>\tlh_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\n[09:22:28]  <andlabs>\tand I couldn't find any concrete information other than the msvc docs\n[09:23:04]  <lh_mouse>\tSince Windows 2000 the NT kernel uses UTF-16.\n[09:23:50]  <lh_mouse>\tIf you don't care about Windows 9x you can just pretend non-UTF-16 APIs didn't exist.\n[09:24:04]  <andlabs>\tthat's not what I meant\n[09:24:06]  <lh_mouse>\tActually long long long ago Windows used UCS2.\n[09:24:15]  <andlabs>\tI meant whether sizeof(wchar_t) must necessarily equal sizeof(uint16_t)\n[09:24:18]  <andlabs>\tand likewise for alignof\n[09:24:27]  <andlabs>\tfor all windows compilers\n[09:24:29]  <andlabs>\tanyway afk\n[09:24:31]  <lh_mouse>\tYes. That is what the ABI says.\n[09:24:40]  <andlabs>\tis there a source for that I can point at other people\n[09:24:45]  <lh_mouse>\tthe ABI != on Windows\n[09:26:00]  <andlabs>\tokay I really need to afk now but I was about to ask what you meant\n[09:26:06]  <andlabs>\tand by source I meant URL\n[09:49:18]  <m2bot>\tandlabs: Sent 17 minutes ago: <lh_mouse> Here is what Microsoft people describe `wchar_t`: https://docs.microsoft.com/en-us/cpp/cpp/char-wchar-t-char16-t-char32-t\n[09:49:19]  <m2bot>\tandlabs: Sent 17 minutes ago: <lh_mouse> 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.'\n[09:50:08]  <lh_mouse>\tandlabs, If you build for cygwin then `wchar_t` is probably `int`, just like what it is on Linux.\n[09:51:00]  <andlabs>\tyes 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\n[09:52:13]  <lh_mouse>\tThis is already the ABI documentation:   https://docs.microsoft.com/en-us/cpp/cpp/char-wchar-t-char16-t-char32-t    \n[09:52:15]  <m2bot>\tTitle: char, wchar_t, char16_t, char32_t | Microsoft Docs (at docs.microsoft.com)\n[09:52:19]  <lh_mouse>\tIt describes C++ types,\n[09:53:09]  <andlabs>\toh, ok\n[09:54:47]  <andlabs>\tI assume by the != statement you mean code that doesn't make any windows API calls can theoretically be compiled any which way, right\n[09:55:05]  <lh_mouse>\tyes. think about MSYS and Cygwin.\n[09:55:21]  <lh_mouse>\tThey have 8-byte `long` and 4-byte `wchar_t`.\n[09:57:37]  <andlabs>\tright, except the code I'm trying to compile does use the Windows API, so that wouldn't apply to me\n[09:57:43]  <andlabs>\tI assume\n[09:57:53]  <lh_mouse>\tit wouldn't.\n[09:59:12]  <lh_mouse>\tOn 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.\n[09:59:32]  <lh_mouse>\tThis is guaranteed to work on Windows. Linux will say otherwise.\n[10:00:41]  <lh_mouse>\tWe all know `#include <windows.h>` in a public header lets the genie out of the bottle, doesn't it?\n[10:04:24]  <andlabs>\tthe zombie of win32_lean_and_mean lives forever\n[10:04:53]  <andlabs>\tof course now we have stdint.h and cstdint (which took longer because lolC++03) which helps stuff\n[10:06:19]  <lh_mouse>\tno `uint32_t` is `unsigned int` while `DWORD` is `unsigned long` hence they are incompatible. :(\n[10:06:39]  <andlabs>\tin what sense\n[10:06:55]  <lh_mouse>\ta `unsigned int *` cannot be converted to `unsigned long *` implicitly.\n[10:07:41]  <lh_mouse>\tthe 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.\n[10:08:04]  <andlabs>\toh\n[10:08:22]  <lh_mouse>\tcasting would indeed make code compile, but I tend to keep away from them unless necessary.\n[10:08:24]  <andlabs>\twel yeah, but we haven't left the world of windows-specific code yet\n[10:08:38]  <andlabs>\tmy point was more we don't need to use names like DWORD anymore\n[10:08:51]  <andlabs>\tof course it's easier to do so\n[10:09:04]  <lh_mouse>\tjust use `uint32_t`.\n[10:09:44]  <lh_mouse>\tI just tested GCC 8 today and noticed they had added a warning for casting between incompatible function pointer types.\n[10:10:10]  <lh_mouse>\tSo casting from `unsigned (*)(void)` to `unsigned long (*)(void)` now results in a warning.\n[10:10:43]  <lh_mouse>\tWith `-Werror` it is a hard error. This can be worked around by casting the operand to an intermediate result of `intptr_t`.\n[10:10:59]  <lh_mouse>\t... not so serious.\n[10:11:42]  <andlabs>\toh good I wonder what else will break :D\n[10:12:19]  <andlabs>\tthough 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**)\n[10:13:23]  <lh_mouse>\tPOSIX 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.\n[10:13:45]  <andlabs>\tyes that's what I just said =P it avoids the problem entirely\n[10:13:49]  <lh_mouse>\tC++ says this is 'conditionally supported' and it is not a warning or error there.\n[10:13:50]  <andlabs>\tdlsym already returns void*\n[10:14:13]  <andlabs>\tsomething like dlsym would require an ABI guarantee on the matter anyway\n[10:14:16]  <andlabs>\tby definition\n[10:14:32]  <lh_mouse>\tCasting is evil. Double casting is double evil. So I keep myself away from them.\n[10:15:03]  <andlabs>\tsadly this is C (and C++) =P\n[10:15:25]  <lh_mouse>\tfor `*-w64-mingw32` targets it is safe to cast between `unsigned short`, `wchar_t` and `char16_t`.\n[10:15:33]  <lh_mouse>\tas well as pointers to them.\n[10:16:30]  <lh_mouse>\tyou just need to `static_assert` it, so something naughty will not compile.\n[12:36:14]  <andlabs>\tactually I didn't notice that last message until just now\n[12:36:23]  <andlabs>\tI was asking because I was sitting here thinking such a static_assert was unnecessary\n}\nclang 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\n\nso I'm not sure what is correct, but I do need to find out\n*/\n#include <limits.h>\n#if WCHAR_MAX > 0xFFFF\n#error unexpected: wchar_t larger than 16-bit on a Windows ABI build; contact andlabs with your build setup information\n#endif\n"
  },
  {
    "path": "windows/container.cpp",
    "content": "// 26 april 2015\n#include \"uipriv_windows.hpp\"\n\n// Code for the HWND of the following uiControls:\n// - uiBox\n// - uiRadioButtons\n// - uiSpinbox\n// - uiTab\n// - uiForm\n// - uiGrid\n\nstruct containerInit {\n\tuiWindowsControl *c;\n\tvoid (*onResize)(uiWindowsControl *);\n};\n\nstatic LRESULT CALLBACK containerWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)\n{\n\tRECT r;\n\tHDC dc;\n\tPAINTSTRUCT ps;\n\tCREATESTRUCTW *cs = (CREATESTRUCTW *) lParam;\n\tWINDOWPOS *wp = (WINDOWPOS *) lParam;\n\tMINMAXINFO *mmi = (MINMAXINFO *) lParam;\n\tstruct containerInit *init;\n\tuiWindowsControl *c;\n\tvoid (*onResize)(uiWindowsControl *);\n\tint minwid, minht;\n\tLRESULT lResult;\n\n\tif (handleParentMessages(hwnd, uMsg, wParam, lParam, &lResult) != FALSE)\n\t\treturn lResult;\n\tswitch (uMsg) {\n\tcase WM_CREATE:\n\t\tinit = (struct containerInit *) (cs->lpCreateParams);\n\t\tSetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR) (init->onResize));\n\t\tSetWindowLongPtrW(hwnd, 0, (LONG_PTR) (init->c));\n\t\tbreak;\t\t// defer to DefWindowProc()\n\tcase WM_WINDOWPOSCHANGED:\n\t\tif ((wp->flags & SWP_NOSIZE) != 0)\n\t\t\tbreak;\t// defer to DefWindowProc();\n\t\tonResize = (void (*)(uiWindowsControl *)) GetWindowLongPtrW(hwnd, GWLP_USERDATA);\n\t\tc = (uiWindowsControl *) GetWindowLongPtrW(hwnd, 0);\n\t\t(*(onResize))(c);\n\t\treturn 0;\n\tcase WM_GETMINMAXINFO:\n\t\tlResult = DefWindowProcW(hwnd, uMsg, wParam, lParam);\n\t\tc = (uiWindowsControl *) GetWindowLongPtrW(hwnd, 0);\n\t\tuiWindowsControlMinimumSize(c, &minwid, &minht);\n\t\tmmi->ptMinTrackSize.x = minwid;\n\t\tmmi->ptMinTrackSize.y = minht;\n\t\treturn lResult;\n\tcase WM_PAINT:\n\t\tdc = BeginPaint(hwnd, &ps);\n\t\tif (dc == NULL) {\n\t\t\tlogLastError(L\"error beginning container paint\");\n\t\t\t// bail out; hope DefWindowProc() catches us\n\t\t\tbreak;\n\t\t}\n\t\tr = ps.rcPaint;\n\t\tpaintContainerBackground(hwnd, dc, &r);\n\t\tEndPaint(hwnd, &ps);\n\t\treturn 0;\n\t// tab controls use this to draw the background of the tab area\n\tcase WM_PRINTCLIENT:\n\t\tuiWindowsEnsureGetClientRect(hwnd, &r);\n\t\tpaintContainerBackground(hwnd, (HDC) wParam, &r);\n\t\treturn 0;\n\tcase WM_ERASEBKGND:\n\t\t// avoid some flicker\n\t\t// we draw the whole update area anyway\n\t\treturn 1;\n\t}\n\treturn DefWindowProcW(hwnd, uMsg, wParam, lParam);\n}\n\nATOM initContainer(HICON hDefaultIcon, HCURSOR hDefaultCursor)\n{\n\tWNDCLASSW wc;\n\n\tZeroMemory(&wc, sizeof (WNDCLASSW));\n\twc.lpszClassName = containerClass;\n\twc.lpfnWndProc = containerWndProc;\n\twc.hInstance = hInstance;\n\twc.hIcon = hDefaultIcon;\n\twc.hCursor = hDefaultCursor;\n\twc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);\n\twc.cbWndExtra = sizeof (void *);\n\treturn RegisterClassW(&wc);\n}\n\nvoid uninitContainer(void)\n{\n\tif (UnregisterClassW(containerClass, hInstance) == 0)\n\t\tlogLastError(L\"error unregistering container window class\");\n}\n\nHWND uiWindowsMakeContainer(uiWindowsControl *c, void (*onResize)(uiWindowsControl *))\n{\n\tstruct containerInit init;\n\n\t// TODO onResize cannot be NULL\n\tinit.c = c;\n\tinit.onResize = onResize;\n\treturn uiWindowsEnsureCreateControlHWND(WS_EX_CONTROLPARENT,\n\t\tcontainerClass, L\"\",\n\t\t0,\n\t\thInstance, (LPVOID) (&init),\n\t\tFALSE);\n}\n"
  },
  {
    "path": "windows/control.cpp",
    "content": "// 16 august 2015\n#include \"uipriv_windows.hpp\"\n\nvoid uiWindowsControlSyncEnableState(uiWindowsControl *c, int enabled)\n{\n\t(*(c->SyncEnableState))(c, enabled);\n}\n\nvoid uiWindowsControlSetParentHWND(uiWindowsControl *c, HWND parent)\n{\n\t(*(c->SetParentHWND))(c, parent);\n}\n\nvoid uiWindowsControlMinimumSize(uiWindowsControl *c, int *width, int *height)\n{\n\t(*(c->MinimumSize))(c, width, height);\n}\n\nvoid uiWindowsControlMinimumSizeChanged(uiWindowsControl *c)\n{\n\t(*(c->MinimumSizeChanged))(c);\n}\n\n// TODO get rid of this\nvoid uiWindowsControlLayoutRect(uiWindowsControl *c, RECT *r)\n{\n\t(*(c->LayoutRect))(c, r);\n}\n\nvoid uiWindowsControlAssignControlIDZOrder(uiWindowsControl *c, LONG_PTR *controlID, HWND *insertAfter)\n{\n\t(*(c->AssignControlIDZOrder))(c, controlID, insertAfter);\n}\n\nvoid uiWindowsControlChildVisibilityChanged(uiWindowsControl *c)\n{\n\t(*(c->ChildVisibilityChanged))(c);\n}\n\nHWND uiWindowsEnsureCreateControlHWND(DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle, HINSTANCE hInstance, LPVOID lpParam, BOOL useStandardControlFont)\n{\n\tHWND hwnd;\n\n\t// don't let using the arrow keys in a uiRadioButtons leave the radio buttons\n\tif ((dwStyle & WS_TABSTOP) != 0)\n\t\tdwStyle |= WS_GROUP;\n\thwnd = CreateWindowExW(dwExStyle,\n\t\tlpClassName, lpWindowName,\n\t\tdwStyle | WS_CHILD | WS_VISIBLE,\n\t\t0, 0,\n\t\t// use a nonzero initial size just in case some control breaks with a zero initial size\n\t\t100, 100,\n\t\tutilWindow, NULL, hInstance, lpParam);\n\tif (hwnd == NULL) {\n\t\tlogLastError(L\"error creating window\");\n\t\t// TODO return a decoy window\n\t}\n\tif (useStandardControlFont)\n\t\tSendMessageW(hwnd, WM_SETFONT, (WPARAM) hMessageFont, (LPARAM) TRUE);\n\treturn hwnd;\n}\n\n// choose a value distinct from uiWindowSignature\n#define uiWindowsControlSignature 0x4D53576E\n\nuiWindowsControl *uiWindowsAllocControl(size_t n, uint32_t typesig, const char *typenamestr)\n{\n\treturn uiWindowsControl(uiAllocControl(n, uiWindowsControlSignature, typesig, typenamestr));\n}\n\nBOOL uiWindowsShouldStopSyncEnableState(uiWindowsControl *c, BOOL enabled)\n{\n\tint ce;\n\n\tce = uiControlEnabled(uiControl(c));\n\t// only stop if we're going from disabled back to enabled; don't stop under any other condition\n\t// (if we stop when going from enabled to disabled then enabled children of a disabled control won't get disabled at the OS level)\n\tif (!ce && enabled)\n\t\treturn TRUE;\n\treturn FALSE;\n}\n\nvoid uiWindowsControlAssignSoleControlIDZOrder(uiWindowsControl *c)\n{\n\tLONG_PTR controlID;\n\tHWND insertAfter;\n\n\tcontrolID = 100;\n\tinsertAfter = NULL;\n\tuiWindowsControlAssignControlIDZOrder(c, &controlID, &insertAfter);\n}\n\nBOOL uiWindowsControlTooSmall(uiWindowsControl *c)\n{\n\tRECT r;\n\tint width, height;\n\n\tuiWindowsControlLayoutRect(c, &r);\n\tuiWindowsControlMinimumSize(c, &width, &height);\n\tif ((r.right - r.left) < width)\n\t\treturn TRUE;\n\tif ((r.bottom - r.top) < height)\n\t\treturn TRUE;\n\treturn FALSE;\n}\n\nvoid uiWindowsControlContinueMinimumSizeChanged(uiWindowsControl *c)\n{\n\tuiControl *parent;\n\n\tparent = uiControlParent(uiControl(c));\n\tif (parent != NULL)\n\t\tuiWindowsControlMinimumSizeChanged(uiWindowsControl(parent));\n}\n\n// TODO rename this nad the OS X this and hugging ones to NotifyChild\nvoid uiWindowsControlNotifyVisibilityChanged(uiWindowsControl *c)\n{\n\t// TODO we really need to figure this out; the duplication is a mess\n\tuiWindowsControlContinueMinimumSizeChanged(c);\n}\n"
  },
  {
    "path": "windows/d2dscratch.cpp",
    "content": "// 17 april 2016\n#include \"uipriv_windows.hpp\"\n\n// The Direct2D scratch window is a utility for libui internal use to do quick things with Direct2D.\n// To use, call newD2DScratch() passing in a subclass procedure. This subclass procedure should handle the msgD2DScratchPaint message, which has the following usage:\n// - wParam - 0\n// - lParam - ID2D1RenderTarget *\n// - lResult - 0\n// 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.\n// - wParam - position in DIPs, as D2D1_POINT_2F *\n// - lParam - size of render target in DIPs, as D2D1_SIZE_F *\n// - lResult - 0\n// Other messages can also be handled here.\n\n// TODO allow resize\n\n#define d2dScratchClass L\"libui_d2dScratchClass\"\n\n// TODO clip rect\nstatic HRESULT d2dScratchDoPaint(HWND hwnd, ID2D1RenderTarget *rt)\n{\n\tCOLORREF bgcolorref;\n\tD2D1_COLOR_F bgcolor;\n\n\trt->BeginDraw();\n\n\t// TODO only clear the clip area\n\t// TODO clear with actual background brush\n\tbgcolorref = GetSysColor(COLOR_BTNFACE);\n\tbgcolor.r = ((float) GetRValue(bgcolorref)) / 255.0;\n\t// due to utter apathy on Microsoft's part, GetGValue() does not work with MSVC's Run-Time Error Checks\n\t// it has not worked since 2008 and they have *never* fixed it\n\t// TODO now that -RTCc has just been deprecated entirely, should we switch back?\n\tbgcolor.g = ((float) ((BYTE) ((bgcolorref & 0xFF00) >> 8))) / 255.0;\n\tbgcolor.b = ((float) GetBValue(bgcolorref)) / 255.0;\n\tbgcolor.a = 1.0;\n\trt->Clear(&bgcolor);\n\n\tSendMessageW(hwnd, msgD2DScratchPaint, 0, (LPARAM) rt);\n\n\treturn rt->EndDraw(NULL, NULL);\n}\n\nstatic void d2dScratchDoLButtonDown(HWND hwnd, ID2D1RenderTarget *rt, LPARAM lParam)\n{\n\tdouble xpix, ypix;\n\tFLOAT dpix, dpiy;\n\tD2D1_POINT_2F pos;\n\tD2D1_SIZE_F size;\n\n\txpix = (double) GET_X_LPARAM(lParam);\n\typix = (double) GET_Y_LPARAM(lParam);\n\t// these are in pixels; we need points\n\t// TODO separate the function from areautil.cpp?\n\trt->GetDpi(&dpix, &dpiy);\n\tpos.x = (xpix * 96) / dpix;\n\tpos.y = (ypix * 96) / dpiy;\n\n\tsize = realGetSize(rt);\n\n\tSendMessageW(hwnd, msgD2DScratchLButtonDown, (WPARAM) (&pos), (LPARAM) (&size));\n}\n\nstatic LRESULT CALLBACK d2dScratchWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)\n{\n\tLONG_PTR init;\n\tID2D1HwndRenderTarget *rt;\n\tID2D1DCRenderTarget *dcrt;\n\tRECT client;\n\tHRESULT hr;\n\n\tinit = GetWindowLongPtrW(hwnd, 0);\n\tif (!init) {\n\t\tif (uMsg == WM_CREATE)\n\t\t\tSetWindowLongPtrW(hwnd, 0, (LONG_PTR) TRUE);\n\t\treturn DefWindowProcW(hwnd, uMsg, wParam, lParam);\n\t}\n\n\trt = (ID2D1HwndRenderTarget *) GetWindowLongPtrW(hwnd, GWLP_USERDATA);\n\tif (rt == NULL) {\n\t\trt = makeHWNDRenderTarget(hwnd);\n\t\tSetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR) rt);\n\t}\n\n\tswitch (uMsg) {\n\tcase WM_DESTROY:\n\t\trt->Release();\n\t\tSetWindowLongPtrW(hwnd, 0, (LONG_PTR) FALSE);\n\t\tbreak;\n\tcase WM_PAINT:\n\t\thr = d2dScratchDoPaint(hwnd, rt);\n\t\tswitch (hr) {\n\t\tcase S_OK:\n\t\t\tif (ValidateRect(hwnd, NULL) == 0)\n\t\t\t\tlogLastError(L\"error validating D2D scratch control rect\");\n\t\t\tbreak;\n\t\tcase D2DERR_RECREATE_TARGET:\n\t\t\t// DON'T validate the rect\n\t\t\t// instead, simply drop the render target\n\t\t\t// we'll get another WM_PAINT and make the render target again\n\t\t\t// TODO would this require us to invalidate the entire client area?\n\t\t\trt->Release();\n\t\t\tSetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR) NULL);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tlogHRESULT(L\"error drawing D2D scratch window\", hr);\n\t\t}\n\t\treturn 0;\n\tcase WM_PRINTCLIENT:\n\t\tuiWindowsEnsureGetClientRect(hwnd, &client);\n\t\tdcrt = makeHDCRenderTarget((HDC) wParam, &client);\n\t\thr = d2dScratchDoPaint(hwnd, dcrt);\n\t\tif (hr != S_OK)\n\t\t\tlogHRESULT(L\"error printing D2D scratch window client area\", hr);\n\t\tdcrt->Release();\n\t\treturn 0;\n\tcase WM_LBUTTONDOWN:\n\t\td2dScratchDoLButtonDown(hwnd, rt, lParam);\n\t\treturn 0;\n\tcase WM_MOUSEMOVE:\n\t\t// also send LButtonDowns when dragging\n\t\tif ((wParam & MK_LBUTTON) != 0)\n\t\t\td2dScratchDoLButtonDown(hwnd, rt, lParam);\n\t\treturn 0;\n\t}\n\treturn DefWindowProcW(hwnd, uMsg, wParam, lParam);\n}\n\nATOM registerD2DScratchClass(HICON hDefaultIcon, HCURSOR hDefaultCursor)\n{\n\tWNDCLASSW wc;\n\n\tZeroMemory(&wc, sizeof (WNDCLASSW));\n\twc.lpszClassName = d2dScratchClass;\n\twc.lpfnWndProc = d2dScratchWndProc;\n\twc.hInstance = hInstance;\n\twc.hIcon = hDefaultIcon;\n\twc.hCursor = hDefaultCursor;\n\twc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);\n\twc.cbWndExtra = sizeof (LONG_PTR);\t\t\t// for the init status\n\treturn RegisterClassW(&wc);\n}\n\nvoid unregisterD2DScratchClass(void)\n{\n\tif (UnregisterClassW(d2dScratchClass, hInstance) == 0)\n\t\tlogLastError(L\"error unregistering D2D scratch window class\");\n}\n\nHWND newD2DScratch(HWND parent, RECT *rect, HMENU controlID, SUBCLASSPROC subclass, DWORD_PTR subclassData)\n{\n\tHWND hwnd;\n\n\thwnd = CreateWindowExW(0,\n\t\td2dScratchClass, L\"\",\n\t\tWS_CHILD | WS_VISIBLE,\n\t\trect->left, rect->top,\n\t\trect->right - rect->left, rect->bottom - rect->top,\n\t\tparent, controlID, hInstance, NULL);\n\tif (hwnd == NULL)\n\t\t// TODO return decoy window\n\t\tlogLastError(L\"error creating D2D scratch window\");\n\tif (SetWindowSubclass(hwnd, subclass, 0, subclassData) == FALSE)\n\t\tlogLastError(L\"error subclassing D2D scratch window\");\n\treturn hwnd;\n}\n"
  },
  {
    "path": "windows/datetimepicker.cpp",
    "content": "// 22 may 2015\n#include \"uipriv_windows.hpp\"\n\nstruct uiDateTimePicker {\n\tuiWindowsControl c;\n\tHWND hwnd;\n\tvoid (*onChanged)(uiDateTimePicker *, void *);\n\tvoid *onChangedData;\n};\n\n// utility functions\n\n#define GLI(what, buf, n) GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT, what, buf, n)\n\n// The real date/time picker does a manual replacement of \"yy\" with \"yyyy\" for DTS_SHORTDATECENTURYFORMAT.\n// Because we're also duplicating its functionality (see below), we have to do it too.\nstatic WCHAR *expandYear(WCHAR *dts, int n)\n{\n\tWCHAR *out;\n\tWCHAR *p, *q;\n\tint ny = 0;\n\n\t// allocate more than we need to be safe\n\tout = (WCHAR *) uiprivAlloc((n * 3) * sizeof (WCHAR), \"WCHAR[]\");\n\tq = out;\n\tfor (p = dts; *p != L'\\0'; p++) {\n\t\t// first, if the current character is a y, increment the number of consecutive ys\n\t\t// otherwise, stop counting, and if there were only two, add two more to make four\n\t\tif (*p != L'y') {\n\t\t\tif (ny == 2) {\n\t\t\t\t*q++ = L'y';\n\t\t\t\t*q++ = L'y';\n\t\t\t}\n\t\t\tny = 0;\n\t\t} else\n\t\t\tny++;\n\t\t// next, handle quoted blocks\n\t\t// we do this AFTER the above so yy'abc' becomes yyyy'abc' and not yy'abc'yy\n\t\t// this handles the case of 'a''b' elegantly as well\n\t\tif (*p == L'\\'') {\n\t\t\t// copy the opening quote\n\t\t\t*q++ = *p;\n\t\t\t// copy the contents\n\t\t\tfor (;;) {\n\t\t\t\tp++;\n\t\t\t\tif (*p == L'\\'')\n\t\t\t\t\tbreak;\n\t\t\t\tif (*p == L'\\0')\n\t\t\t\t\tuiprivImplBug(\"unterminated quote in system-provided locale date string in expandYear()\");\n\t\t\t\t*q++ = *p;\n\t\t\t}\n\t\t\t// and fall through to copy the closing quote\n\t\t}\n\t\t// copy the current character\n\t\t*q++ = *p;\n\t}\n\t// handle trailing yy\n\tif (ny == 2) {\n\t\t*q++ = L'y';\n\t\t*q++ = L'y';\n\t}\n\t*q++ = L'\\0';\n\treturn out;\n}\n\n// Windows has no combined date/time prebuilt constant; we have to build the format string ourselves\n// TODO use a default format if one fails\nstatic void setDateTimeFormat(HWND hwnd)\n{\n\tWCHAR *unexpandedDate, *date;\n\tWCHAR *time;\n\tWCHAR *datetime;\n\tint ndate, ntime;\n\n\tndate = GLI(LOCALE_SSHORTDATE, NULL, 0);\n\tif (ndate == 0)\n\t\tlogLastError(L\"error getting date string length\");\n\tdate = (WCHAR *) uiprivAlloc(ndate * sizeof (WCHAR), \"WCHAR[]\");\n\tif (GLI(LOCALE_SSHORTDATE, date, ndate) == 0)\n\t\tlogLastError(L\"error geting date string\");\n\tunexpandedDate = date;\t\t// so we can free it\n\tdate = expandYear(unexpandedDate, ndate);\n\tuiprivFree(unexpandedDate);\n\n\tntime = GLI(LOCALE_STIMEFORMAT, NULL, 0);\n\tif (ndate == 0)\n\t\tlogLastError(L\"error getting time string length\");\n\ttime = (WCHAR *) uiprivAlloc(ntime * sizeof (WCHAR), \"WCHAR[]\");\n\tif (GLI(LOCALE_STIMEFORMAT, time, ntime) == 0)\n\t\tlogLastError(L\"error geting time string\");\n\n\tdatetime = strf(L\"%s %s\", date, time);\n\tif (SendMessageW(hwnd, DTM_SETFORMAT, 0, (LPARAM) datetime) == 0)\n\t\tlogLastError(L\"error applying format string to date/time picker\");\n\n\tuiprivFree(datetime);\n\tuiprivFree(time);\n\tuiprivFree(date);\n}\n\n// control implementation\n\nstatic void uiDateTimePickerDestroy(uiControl *c)\n{\n\tuiDateTimePicker *d = uiDateTimePicker(c);\n\n\tuiWindowsUnregisterReceiveWM_WININICHANGE(d->hwnd);\n\tuiWindowsUnregisterWM_NOTIFYHandler(d->hwnd);\n\tuiWindowsEnsureDestroyWindow(d->hwnd);\n\tuiFreeControl(uiControl(d));\n}\n\nuiWindowsControlAllDefaultsExceptDestroy(uiDateTimePicker)\n\n// the height returned from DTM_GETIDEALSIZE is unreliable; see http://stackoverflow.com/questions/30626549/what-is-the-proper-use-of-dtm-getidealsize-treating-the-returned-size-as-pixels\n// from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing\n#define entryHeight 14\n\nstatic void uiDateTimePickerMinimumSize(uiWindowsControl *c, int *width, int *height)\n{\n\tuiDateTimePicker *d = uiDateTimePicker(c);\n\tSIZE s;\n\tuiWindowsSizing sizing;\n\tint y;\n\n\ts.cx = 0;\n\ts.cy = 0;\n\tSendMessageW(d->hwnd, DTM_GETIDEALSIZE, 0, (LPARAM) (&s));\n\t*width = s.cx;\n\n\ty = entryHeight;\n\tuiWindowsGetSizing(d->hwnd, &sizing);\n\tuiWindowsSizingDlgUnitsToPixels(&sizing, NULL, &y);\n\t*height = y;\n}\n\nstatic BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult)\n{\n\tuiDateTimePicker *d = uiDateTimePicker(c);\n\n\tif (nmhdr->code != DTN_DATETIMECHANGE)\n\t\treturn FALSE;\n\t(*(d->onChanged))(d, d->onChangedData);\n\t*lResult = 0;\n\treturn TRUE;\n}\n\nstatic void fromSystemTime(SYSTEMTIME *systime, struct tm *time)\n{\n\tZeroMemory(time, sizeof (struct tm));\n\ttime->tm_sec = systime->wSecond;\n\ttime->tm_min = systime->wMinute;\n\ttime->tm_hour = systime->wHour;\n\ttime->tm_mday = systime->wDay;\n\ttime->tm_mon = systime->wMonth - 1;\n\ttime->tm_year = systime->wYear - 1900;\n\ttime->tm_wday = systime->wDayOfWeek;\n\ttime->tm_isdst = -1;\n}\n\nstatic void toSystemTime(const struct tm *time, SYSTEMTIME *systime)\n{\n\tZeroMemory(systime, sizeof (SYSTEMTIME));\n\tsystime->wYear = time->tm_year + 1900;\n\tsystime->wMonth = time->tm_mon + 1;\n\tsystime->wDayOfWeek = time->tm_wday;\n\tsystime->wDay = time->tm_mday;\n\tsystime->wHour = time->tm_hour;\n\tsystime->wMinute = time->tm_min;\n\tsystime->wSecond = time->tm_sec;\n}\n\nstatic void defaultOnChanged(uiDateTimePicker *d, void *data)\n{\n\t// do nothing\n}\n\nvoid uiDateTimePickerTime(uiDateTimePicker *d, struct tm *time)\n{\n\tSYSTEMTIME systime;\n\n\tif (SendMessageW(d->hwnd, DTM_GETSYSTEMTIME, 0, (LPARAM) (&systime)) != GDT_VALID)\n\t\tlogLastError(L\"error getting date and time\");\n\tfromSystemTime(&systime, time);\n}\n\nvoid uiDateTimePickerSetTime(uiDateTimePicker *d, const struct tm *time)\n{\n\tSYSTEMTIME systime;\n\n\ttoSystemTime(time, &systime);\n\tif (SendMessageW(d->hwnd, DTM_SETSYSTEMTIME, GDT_VALID, (LPARAM) (&systime)) == 0)\n\t\tlogLastError(L\"error setting date and time\");\n}\n\nvoid uiDateTimePickerOnChanged(uiDateTimePicker *d, void (*f)(uiDateTimePicker *, void *), void *data)\n{\n\td->onChanged = f;\n\td->onChangedData = data;\n}\n\nstatic uiDateTimePicker *finishNewDateTimePicker(DWORD style)\n{\n\tuiDateTimePicker *d;\n\n\tuiWindowsNewControl(uiDateTimePicker, d);\n\n\td->hwnd = uiWindowsEnsureCreateControlHWND(WS_EX_CLIENTEDGE,\n\t\tDATETIMEPICK_CLASSW, L\"\",\n\t\tstyle | WS_TABSTOP,\n\t\thInstance, NULL,\n\t\tTRUE);\n\n\t// automatically update date/time format when user changes locale settings\n\t// for the standard styles, this is in the date-time picker itself\n\t// for our date/time mode, we do it in a subclass assigned in uiNewDateTimePicker()\n\tuiWindowsRegisterReceiveWM_WININICHANGE(d->hwnd);\n\tuiWindowsRegisterWM_NOTIFYHandler(d->hwnd, onWM_NOTIFY, uiControl(d));\n\tuiDateTimePickerOnChanged(d, defaultOnChanged, NULL);\n\n\treturn d;\n}\n\nstatic LRESULT CALLBACK datetimepickerSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)\n{\n\tswitch (uMsg) {\n\tcase WM_WININICHANGE:\n\t\t// we can optimize this by only doing it when the real date/time picker does it\n\t\t// unfortunately, I don't know when that is :/\n\t\t// hopefully this won't hurt\n\t\tsetDateTimeFormat(hwnd);\n\t\treturn 0;\n\tcase WM_NCDESTROY:\n\t\tif (RemoveWindowSubclass(hwnd, datetimepickerSubProc, uIdSubclass) == FALSE)\n\t\t\tlogLastError(L\"error removing date-time picker locale change handling subclass\");\n\t\tbreak;\n\t}\n\treturn DefSubclassProc(hwnd, uMsg, wParam, lParam);\n}\n\nuiDateTimePicker *uiNewDateTimePicker(void)\n{\n\tuiDateTimePicker *d;\n\n\td = finishNewDateTimePicker(0);\n\tsetDateTimeFormat(d->hwnd);\n\tif (SetWindowSubclass(d->hwnd, datetimepickerSubProc, 0, (DWORD_PTR) d) == FALSE)\n\t\tlogLastError(L\"error subclassing date-time-picker to assist in locale change handling\");\n\t\t// TODO set a suitable default in this case\n\treturn d;\n}\n\nuiDateTimePicker *uiNewDatePicker(void)\n{\n\treturn finishNewDateTimePicker(DTS_SHORTDATECENTURYFORMAT);\n}\n\nuiDateTimePicker *uiNewTimePicker(void)\n{\n\treturn finishNewDateTimePicker(DTS_TIMEFORMAT);\n}\n"
  },
  {
    "path": "windows/debug.cpp",
    "content": "// 25 february 2015\n#include \"uipriv_windows.hpp\"\n\n// LONGTERM disable logging and stopping on no-debug builds\n\nstatic void printDebug(const WCHAR *msg)\n{\n\tOutputDebugStringW(msg);\n}\n\nHRESULT _logLastError(debugargs, const WCHAR *s)\n{\n\tDWORD le;\n\tWCHAR *msg;\n\tWCHAR *formatted;\n\tBOOL useFormatted;\n\n\tle = GetLastError();\n\n\tuseFormatted = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, le, 0, (LPWSTR) (&formatted), 0, NULL) != 0;\n\tif (!useFormatted)\n\t\tformatted = (WCHAR *) L\"\\n\";\t\t// TODO\n\tmsg = strf(L\"[libui] %s:%s:%s() %s: GetLastError() == %I32u %s\",\n\t\tfile, line, func,\n\t\ts, le, formatted);\n\tif (useFormatted)\n\t\tLocalFree(formatted);\t\t// ignore error\n\tprintDebug(msg);\n\tuiprivFree(msg);\n\tDebugBreak();\n\n\tSetLastError(le);\n\t// a function does not have to set a last error\n\t// if the last error we get is actually 0, then HRESULT_FROM_WIN32(0) will return S_OK (0 cast to an HRESULT, since 0 <= 0), which we don't want\n\t// prevent this by returning E_FAIL\n\tif (le == 0)\n\t\treturn E_FAIL;\n\treturn HRESULT_FROM_WIN32(le);\n}\n\nHRESULT _logHRESULT(debugargs, const WCHAR *s, HRESULT hr)\n{\n\tWCHAR *msg;\n\tWCHAR *formatted;\n\tBOOL useFormatted;\n\n\tuseFormatted = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, hr, 0, (LPWSTR) (&formatted), 0, NULL) != 0;\n\tif (!useFormatted)\n\t\tformatted = (WCHAR *) L\"\\n\";\t\t\t// TODO\n\tmsg = strf(L\"[libui] %s:%s:%s() %s: HRESULT == 0x%08I32X %s\",\n\t\tfile, line, func,\n\t\ts, hr, formatted);\n\tif (useFormatted)\n\t\tLocalFree(formatted);\t\t// ignore error\n\tprintDebug(msg);\n\tuiprivFree(msg);\n\tDebugBreak();\n\n\treturn hr;\n}\n\nvoid uiprivRealBug(const char *file, const char *line, const char *func, const char *prefix, const char *format, va_list ap)\n{\n\tva_list ap2;\n\tchar *msg;\n\tsize_t n;\n\tWCHAR *final;\n\n\tva_copy(ap2, ap);\n\tn = _vscprintf(format, ap2);\n\tva_end(ap2);\n\tn++;\t\t// terminating '\\0'\n\n\tmsg = (char *) uiprivAlloc(n * sizeof (char), \"char[]\");\n\t// includes terminating '\\0' according to example in https://msdn.microsoft.com/en-us/library/xa1a1a6z.aspx\n\tvsprintf_s(msg, n, format, ap);\n\n\tfinal = strf(L\"[libui] %hs:%hs:%hs() %hs%hs\\n\", file, line, func, prefix, msg);\n\tuiprivFree(msg);\n\tprintDebug(final);\n\tuiprivFree(final);\n\n\tDebugBreak();\n}\n"
  },
  {
    "path": "windows/draw.cpp",
    "content": "// 7 september 2015\n#include \"uipriv_windows.hpp\"\n#include \"draw.hpp\"\n\nID2D1Factory *d2dfactory = NULL;\n\nHRESULT initDraw(void)\n{\n\tD2D1_FACTORY_OPTIONS opts;\n\n\tZeroMemory(&opts, sizeof (D2D1_FACTORY_OPTIONS));\n\t// TODO make this an option\n\topts.debugLevel = D2D1_DEBUG_LEVEL_NONE;\n\treturn D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,\n\t\tIID_ID2D1Factory,\n\t\t&opts,\n\t\t(void **) (&d2dfactory));\n}\n\nvoid uninitDraw(void)\n{\n\td2dfactory->Release();\n}\n\nID2D1HwndRenderTarget *makeHWNDRenderTarget(HWND hwnd)\n{\n\tD2D1_RENDER_TARGET_PROPERTIES props;\n\tD2D1_HWND_RENDER_TARGET_PROPERTIES hprops;\n\tHDC dc;\n\tRECT r;\n\tID2D1HwndRenderTarget *rt;\n\tHRESULT hr;\n\n\t// we need a DC for the DPI\n\t// we *could* just use the screen DPI but why when we have a window handle and its DC has a DPI\n\tdc = GetDC(hwnd);\n\tif (dc == NULL)\n\t\tlogLastError(L\"error getting DC to find DPI\");\n\n\tZeroMemory(&props, sizeof (D2D1_RENDER_TARGET_PROPERTIES));\n\tprops.type = D2D1_RENDER_TARGET_TYPE_DEFAULT;\n\tprops.pixelFormat.format = DXGI_FORMAT_UNKNOWN;\n\tprops.pixelFormat.alphaMode = D2D1_ALPHA_MODE_UNKNOWN;\n\tprops.dpiX = GetDeviceCaps(dc, LOGPIXELSX);\n\tprops.dpiY = GetDeviceCaps(dc, LOGPIXELSY);\n\tprops.usage = D2D1_RENDER_TARGET_USAGE_NONE;\n\tprops.minLevel = D2D1_FEATURE_LEVEL_DEFAULT;\n\n\tif (ReleaseDC(hwnd, dc) == 0)\n\t\tlogLastError(L\"error releasing DC for finding DPI\");\n\n\tuiWindowsEnsureGetClientRect(hwnd, &r);\n\n\tZeroMemory(&hprops, sizeof (D2D1_HWND_RENDER_TARGET_PROPERTIES));\n\thprops.hwnd = hwnd;\n\thprops.pixelSize.width = r.right - r.left;\n\thprops.pixelSize.height = r.bottom - r.top;\n\t// according to Rick Brewster, some drivers will misbehave if we don't specify this (see http://stackoverflow.com/a/33222983/3408572)\n\thprops.presentOptions = D2D1_PRESENT_OPTIONS_RETAIN_CONTENTS;\n\n\thr = d2dfactory->CreateHwndRenderTarget(\n\t\t&props,\n\t\t&hprops,\n\t\t&rt);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating HWND render target\", hr);\n\treturn rt;\n}\n\nID2D1DCRenderTarget *makeHDCRenderTarget(HDC dc, RECT *r)\n{\n\tD2D1_RENDER_TARGET_PROPERTIES props;\n\tID2D1DCRenderTarget *rt;\n\tHRESULT hr;\n\n\tZeroMemory(&props, sizeof (D2D1_RENDER_TARGET_PROPERTIES));\n\tprops.type = D2D1_RENDER_TARGET_TYPE_DEFAULT;\n\tprops.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM;\n\tprops.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;\n\tprops.dpiX = GetDeviceCaps(dc, LOGPIXELSX);\n\tprops.dpiY = GetDeviceCaps(dc, LOGPIXELSY);\n\tprops.usage = D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE;\n\tprops.minLevel = D2D1_FEATURE_LEVEL_DEFAULT;\n\n\thr = d2dfactory->CreateDCRenderTarget(&props, &rt);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating DC render target\", hr);\n\thr = rt->BindDC(dc, r);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error binding DC to DC render target\", hr);\n\treturn rt;\n}\n\nstatic void resetTarget(ID2D1RenderTarget *rt)\n{\n\tD2D1_MATRIX_3X2_F dm;\n\n\t// transformations persist\n\t// reset to the identity matrix\n\tZeroMemory(&dm, sizeof (D2D1_MATRIX_3X2_F));\n\tdm._11 = 1;\n\tdm._22 = 1;\n\trt->SetTransform(&dm);\n}\n\nuiDrawContext *newContext(ID2D1RenderTarget *rt)\n{\n\tuiDrawContext *c;\n\n\tc = uiprivNew(uiDrawContext);\n\tc->rt = rt;\n\tc->states = new std::vector<struct drawState>;\n\tresetTarget(c->rt);\n\treturn c;\n}\n\nvoid freeContext(uiDrawContext *c)\n{\n\tif (c->currentClip != NULL)\n\t\tc->currentClip->Release();\n\tif (c->states->size() != 0)\n\t\t// TODO do this on other platforms\n\t\tuiprivUserBug(\"You did not balance uiDrawSave() and uiDrawRestore() calls.\");\n\tdelete c->states;\n\tuiprivFree(c);\n}\n\nstatic ID2D1Brush *makeSolidBrush(uiDrawBrush *b, ID2D1RenderTarget *rt, D2D1_BRUSH_PROPERTIES *props)\n{\n\tD2D1_COLOR_F color;\n\tID2D1SolidColorBrush *brush;\n\tHRESULT hr;\n\n\tcolor.r = b->R;\n\tcolor.g = b->G;\n\tcolor.b = b->B;\n\tcolor.a = b->A;\n\n\thr = rt->CreateSolidColorBrush(\n\t\t&color,\n\t\tprops,\n\t\t&brush);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating solid brush\", hr);\n\treturn brush;\n}\n\nstatic ID2D1GradientStopCollection *mkstops(uiDrawBrush *b, ID2D1RenderTarget *rt)\n{\n\tID2D1GradientStopCollection *s;\n\tD2D1_GRADIENT_STOP *stops;\n\tsize_t i;\n\tHRESULT hr;\n\n\tstops = (D2D1_GRADIENT_STOP *) uiprivAlloc(b->NumStops * sizeof (D2D1_GRADIENT_STOP), \"D2D1_GRADIENT_STOP[]\");\n\tfor (i = 0; i < b->NumStops; i++) {\n\t\tstops[i].position = b->Stops[i].Pos;\n\t\tstops[i].color.r = b->Stops[i].R;\n\t\tstops[i].color.g = b->Stops[i].G;\n\t\tstops[i].color.b = b->Stops[i].B;\n\t\tstops[i].color.a = b->Stops[i].A;\n\t}\n\n\thr = rt->CreateGradientStopCollection(\n\t\tstops,\n\t\tb->NumStops,\n\t\tD2D1_GAMMA_2_2,\t\t\t// this is the default for the C++-only overload of ID2D1RenderTarget::GradientStopCollection()\n\t\tD2D1_EXTEND_MODE_CLAMP,\n\t\t&s);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating stop collection\", hr);\n\n\tuiprivFree(stops);\n\treturn s;\n}\n\nstatic ID2D1Brush *makeLinearBrush(uiDrawBrush *b, ID2D1RenderTarget *rt, D2D1_BRUSH_PROPERTIES *props)\n{\n\tID2D1LinearGradientBrush *brush;\n\tD2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES gprops;\n\tID2D1GradientStopCollection *stops;\n\tHRESULT hr;\n\n\tZeroMemory(&gprops, sizeof (D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES));\n\tgprops.startPoint.x = b->X0;\n\tgprops.startPoint.y = b->Y0;\n\tgprops.endPoint.x = b->X1;\n\tgprops.endPoint.y = b->Y1;\n\n\tstops = mkstops(b, rt);\n\n\thr = rt->CreateLinearGradientBrush(\n\t\t&gprops,\n\t\tprops,\n\t\tstops,\n\t\t&brush);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating gradient brush\", hr);\n\n\t// the example at https://msdn.microsoft.com/en-us/library/windows/desktop/dd756682%28v=vs.85%29.aspx says this is safe to do now\n\tstops->Release();\n\treturn brush;\n}\n\nstatic ID2D1Brush *makeRadialBrush(uiDrawBrush *b, ID2D1RenderTarget *rt, D2D1_BRUSH_PROPERTIES *props)\n{\n\tID2D1RadialGradientBrush *brush;\n\tD2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES gprops;\n\tID2D1GradientStopCollection *stops;\n\tHRESULT hr;\n\n\tZeroMemory(&gprops, sizeof (D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES));\n\tgprops.gradientOriginOffset.x = b->X0 - b->X1;\n\tgprops.gradientOriginOffset.y = b->Y0 - b->Y1;\n\tgprops.center.x = b->X1;\n\tgprops.center.y = b->Y1;\n\tgprops.radiusX = b->OuterRadius;\n\tgprops.radiusY = b->OuterRadius;\n\n\tstops = mkstops(b, rt);\n\n\thr = rt->CreateRadialGradientBrush(\n\t\t&gprops,\n\t\tprops,\n\t\tstops,\n\t\t&brush);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating gradient brush\", hr);\n\n\tstops->Release();\n\treturn brush;\n}\n\nstatic ID2D1Brush *makeBrush(uiDrawBrush *b, ID2D1RenderTarget *rt)\n{\n\tD2D1_BRUSH_PROPERTIES props;\n\n\tZeroMemory(&props, sizeof (D2D1_BRUSH_PROPERTIES));\n\tprops.opacity = 1.0;\n\t// identity matrix\n\tprops.transform._11 = 1;\n\tprops.transform._22 = 1;\n\n\tswitch (b->Type) {\n\tcase uiDrawBrushTypeSolid:\n\t\treturn makeSolidBrush(b, rt, &props);\n\tcase uiDrawBrushTypeLinearGradient:\n\t\treturn makeLinearBrush(b, rt, &props);\n\tcase uiDrawBrushTypeRadialGradient:\n\t\treturn makeRadialBrush(b, rt, &props);\n//\tcase uiDrawBrushTypeImage:\n//\t\tTODO\n\t}\n\n\t// TODO do this on all platforms\n\tuiprivUserBug(\"Invalid brush type %d given to drawing operation.\", b->Type);\n\t// TODO dummy brush?\n\treturn NULL;\t\t// make compiler happy\n}\n\n// how clipping works:\n// every fill and stroke is done on a temporary layer with the clip geometry applied to it\n// this is really the only way to clip in Direct2D that doesn't involve opacity images\n// reference counting:\n// - initially the clip is NULL, which means do not use a layer\n// - the first time uiDrawClip() is called, we take a reference on the path passed in (this is also why uiPathEnd() is needed)\n// - every successive time, we create a new PathGeometry and merge the current clip with the new path, releasing the reference we took earlier and taking a reference to the new one\n// - in Save, we take another reference; in Restore we drop the refernece to the existing path geometry and transfer that saved ref to the new path geometry over to the context\n// uiDrawFreePath() doesn't destroy the path geometry, it just drops the reference count, so a clip can exist independent of its path\n\nstatic ID2D1Layer *applyClip(uiDrawContext *c)\n{\n\tID2D1Layer *layer;\n\tD2D1_LAYER_PARAMETERS params;\n\tHRESULT hr;\n\n\t// if no clip, don't do anything\n\tif (c->currentClip == NULL)\n\t\treturn NULL;\n\n\t// create a layer for clipping\n\t// we have to explicitly make the layer because we're still targeting Windows 7\n\thr = c->rt->CreateLayer(NULL, &layer);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating clip layer\", hr);\n\n\t// apply it as the clip\n\tZeroMemory(&params, sizeof (D2D1_LAYER_PARAMETERS));\n\t// this is the equivalent of InfiniteRect() in d2d1helper.h\n\tparams.contentBounds.left = -FLT_MAX;\n\tparams.contentBounds.top = -FLT_MAX;\n\tparams.contentBounds.right = FLT_MAX;\n\tparams.contentBounds.bottom = FLT_MAX;\n\tparams.geometricMask = (ID2D1Geometry *) (c->currentClip);\n\t// TODO is this correct?\n\tparams.maskAntialiasMode = c->rt->GetAntialiasMode();\n\t// identity matrix\n\tparams.maskTransform._11 = 1;\n\tparams.maskTransform._22 = 1;\n\tparams.opacity = 1.0;\n\tparams.opacityBrush = NULL;\n\tparams.layerOptions = D2D1_LAYER_OPTIONS_NONE;\n\t// TODO is this correct?\n\tif (c->rt->GetTextAntialiasMode() == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE)\n\t\tparams.layerOptions = D2D1_LAYER_OPTIONS_INITIALIZE_FOR_CLEARTYPE;\n\tc->rt->PushLayer(&params, layer);\n\n\t// return the layer so it can be freed later\n\treturn layer;\n}\n\nstatic void unapplyClip(uiDrawContext *c, ID2D1Layer *layer)\n{\n\tif (layer == NULL)\n\t\treturn;\n\tc->rt->PopLayer();\n\tlayer->Release();\n}\n\nvoid uiDrawStroke(uiDrawContext *c, uiDrawPath *p, uiDrawBrush *b, uiDrawStrokeParams *sp)\n{\n\tID2D1Brush *brush;\n\tID2D1StrokeStyle *style;\n\tD2D1_STROKE_STYLE_PROPERTIES dsp;\n\tFLOAT *dashes;\n\tsize_t i;\n\tID2D1Layer *cliplayer;\n\tHRESULT hr;\n\n\tbrush = makeBrush(b, c->rt);\n\n\tZeroMemory(&dsp, sizeof (D2D1_STROKE_STYLE_PROPERTIES));\n\tswitch (sp->Cap) {\n\tcase uiDrawLineCapFlat:\n\t\tdsp.startCap = D2D1_CAP_STYLE_FLAT;\n\t\tdsp.endCap = D2D1_CAP_STYLE_FLAT;\n\t\tdsp.dashCap = D2D1_CAP_STYLE_FLAT;\n\t\tbreak;\n\tcase uiDrawLineCapRound:\n\t\tdsp.startCap = D2D1_CAP_STYLE_ROUND;\n\t\tdsp.endCap = D2D1_CAP_STYLE_ROUND;\n\t\tdsp.dashCap = D2D1_CAP_STYLE_ROUND;\n\t\tbreak;\n\tcase uiDrawLineCapSquare:\n\t\tdsp.startCap = D2D1_CAP_STYLE_SQUARE;\n\t\tdsp.endCap = D2D1_CAP_STYLE_SQUARE;\n\t\tdsp.dashCap = D2D1_CAP_STYLE_SQUARE;\n\t\tbreak;\n\t}\n\tswitch (sp->Join) {\n\tcase uiDrawLineJoinMiter:\n\t\tdsp.lineJoin = D2D1_LINE_JOIN_MITER_OR_BEVEL;\n\t\tdsp.miterLimit = sp->MiterLimit;\n\t\tbreak;\n\tcase uiDrawLineJoinRound:\n\t\tdsp.lineJoin = D2D1_LINE_JOIN_ROUND;\n\t\tbreak;\n\tcase uiDrawLineJoinBevel:\n\t\tdsp.lineJoin = D2D1_LINE_JOIN_BEVEL;\n\t\tbreak;\n\t}\n\tdsp.dashStyle = D2D1_DASH_STYLE_SOLID;\n\tdashes = NULL;\n\t// note that dash widths and the dash phase are scaled up by the thickness by Direct2D\n\t// TODO be sure to formally document this\n\tif (sp->NumDashes != 0) {\n\t\tdsp.dashStyle = D2D1_DASH_STYLE_CUSTOM;\n\t\tdashes = (FLOAT *) uiprivAlloc(sp->NumDashes * sizeof (FLOAT), \"FLOAT[]\");\n\t\tfor (i = 0; i < sp->NumDashes; i++)\n\t\t\tdashes[i] = sp->Dashes[i] / sp->Thickness;\n\t}\n\tdsp.dashOffset = sp->DashPhase / sp->Thickness;\n\thr = d2dfactory->CreateStrokeStyle(\n\t\t&dsp,\n\t\tdashes,\n\t\tsp->NumDashes,\n\t\t&style);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating stroke style\", hr);\n\tif (sp->NumDashes != 0)\n\t\tuiprivFree(dashes);\n\n\tcliplayer = applyClip(c);\n\tc->rt->DrawGeometry(\n\t\tpathGeometry(p),\n\t\tbrush,\n\t\tsp->Thickness,\n\t\tstyle);\n\tunapplyClip(c, cliplayer);\n\n\tstyle->Release();\n\tbrush->Release();\n}\n\nvoid uiDrawFill(uiDrawContext *c, uiDrawPath *p, uiDrawBrush *b)\n{\n\tID2D1Brush *brush;\n\tID2D1Layer *cliplayer;\n\n\tbrush = makeBrush(b, c->rt);\n\tcliplayer = applyClip(c);\n\tc->rt->FillGeometry(\n\t\tpathGeometry(p),\n\t\tbrush,\n\t\tNULL);\n\tunapplyClip(c, cliplayer);\n\tbrush->Release();\n}\n\nvoid uiDrawTransform(uiDrawContext *c, uiDrawMatrix *m)\n{\n\tD2D1_MATRIX_3X2_F dm, cur;\n\n\tc->rt->GetTransform(&cur);\n\tm2d(m, &dm);\n\t// you would think we have to do already * m, right?\n\t// WRONG! we have to do m * already\n\t// why? a few reasons\n\t// a) this lovely comment in cairo's source - http://cgit.freedesktop.org/cairo/tree/src/cairo-matrix.c?id=0537479bd1d4c5a3bc0f6f41dec4deb98481f34a#n330\n\t// \tDirect2D uses column vectors and I don't know if this is even documented\n\t// b) that's what Core Graphics does\n\t// TODO see if Microsoft says to do this\n\tdm = dm * cur;\t\t// for whatever reason operator * is defined but not operator *=\n\tc->rt->SetTransform(&dm);\n}\n\nvoid uiDrawClip(uiDrawContext *c, uiDrawPath *path)\n{\n\tID2D1PathGeometry *newPath;\n\tID2D1GeometrySink *newSink;\n\tHRESULT hr;\n\n\t// if there's no current clip, borrow the path\n\tif (c->currentClip == NULL) {\n\t\tc->currentClip = pathGeometry(path);\n\t\t// we have to take our own reference to that clip\n\t\tc->currentClip->AddRef();\n\t\treturn;\n\t}\n\n\t// otherwise we have to intersect the current path with the new one\n\t// we do that into a new path, and then replace c->currentClip with that new path\n\thr = d2dfactory->CreatePathGeometry(&newPath);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating new path\", hr);\n\thr = newPath->Open(&newSink);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error opening new path\", hr);\n\thr = c->currentClip->CombineWithGeometry(\n\t\tpathGeometry(path),\n\t\tD2D1_COMBINE_MODE_INTERSECT,\n\t\tNULL,\n\t\t// TODO is this correct or can this be set per target?\n\t\tD2D1_DEFAULT_FLATTENING_TOLERANCE,\n\t\tnewSink);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error intersecting old path with new path\", hr);\n\thr = newSink->Close();\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error closing new path\", hr);\n\tnewSink->Release();\n\n\t// okay we have the new clip; we just need to replace the old one with it\n\tc->currentClip->Release();\n\tc->currentClip = newPath;\n\t// we have a reference already; no need for another\n}\n\nstruct drawState {\n\tID2D1DrawingStateBlock *dsb;\n\tID2D1PathGeometry *clip;\n};\n\nvoid uiDrawSave(uiDrawContext *c)\n{\n\tstruct drawState state;\n\tHRESULT hr;\n\n\thr = d2dfactory->CreateDrawingStateBlock(\n\t\t// TODO verify that these are correct\n\t\tNULL,\n\t\tNULL,\n\t\t&(state.dsb));\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating drawing state block\", hr);\n\tc->rt->SaveDrawingState(state.dsb);\n\n\t// if we have a clip, we need to hold another reference to it\n\tif (c->currentClip != NULL)\n\t\tc->currentClip->AddRef();\n\tstate.clip = c->currentClip;\t\t// even if NULL assign it\n\n\tc->states->push_back(state);\n}\n\nvoid uiDrawRestore(uiDrawContext *c)\n{\n\tstruct drawState state;\n\n\tstate = (*(c->states))[c->states->size() - 1];\n\tc->states->pop_back();\n\n\tc->rt->RestoreDrawingState(state.dsb);\n\tstate.dsb->Release();\n\n\t// if we have a current clip, we need to drop it\n\tif (c->currentClip != NULL)\n\t\tc->currentClip->Release();\n\t// no need to explicitly addref or release; just transfer the ref\n\tc->currentClip = state.clip;\n}\n"
  },
  {
    "path": "windows/draw.hpp",
    "content": "// 5 may 2016\n\n// TODO resolve overlap between this and the other hpp files (some functions leaked into uipriv_windows.hpp)\n\n// draw.cpp\nextern ID2D1Factory *d2dfactory;\nstruct uiDrawContext {\n\tID2D1RenderTarget *rt;\n\t// TODO find out how this works\n\tstd::vector<struct drawState> *states;\n\tID2D1PathGeometry *currentClip;\n};\n\n// drawpath.cpp\nextern ID2D1PathGeometry *pathGeometry(uiDrawPath *p);\n\n// drawmatrix.cpp\nextern void m2d(uiDrawMatrix *m, D2D1_MATRIX_3X2_F *d);\n"
  },
  {
    "path": "windows/drawmatrix.cpp",
    "content": "// 7 september 2015\n#include \"uipriv_windows.hpp\"\n#include \"draw.hpp\"\n\nvoid m2d(uiDrawMatrix *m, D2D1_MATRIX_3X2_F *d)\n{\n\td->_11 = m->M11;\n\td->_12 = m->M12;\n\td->_21 = m->M21;\n\td->_22 = m->M22;\n\td->_31 = m->M31;\n\td->_32 = m->M32;\n}\n\nstatic void d2m(D2D1_MATRIX_3X2_F *d, uiDrawMatrix *m)\n{\n\tm->M11 = d->_11;\n\tm->M12 = d->_12;\n\tm->M21 = d->_21;\n\tm->M22 = d->_22;\n\tm->M31 = d->_31;\n\tm->M32 = d->_32;\n}\n\nvoid uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y)\n{\n\tD2D1_MATRIX_3X2_F dm;\n\n\tm2d(m, &dm);\n\tdm = dm * D2D1::Matrix3x2F::Translation(x, y);\n\td2m(&dm, m);\n}\n\nvoid uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x, double y)\n{\n\tD2D1_MATRIX_3X2_F dm;\n\tD2D1_POINT_2F center;\n\n\tm2d(m, &dm);\n\tcenter.x = xCenter;\n\tcenter.y = yCenter;\n\tdm = dm * D2D1::Matrix3x2F::Scale(x, y, center);\n\td2m(&dm, m);\n}\n\n#define r2d(x) (x * (180.0 / uiPi))\n\nvoid uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount)\n{\n\tD2D1_MATRIX_3X2_F dm;\n\tD2D1_POINT_2F center;\n\n\tm2d(m, &dm);\n\tcenter.x = x;\n\tcenter.y = y;\n\tdm = dm * D2D1::Matrix3x2F::Rotation(r2d(amount), center);\n\td2m(&dm, m);\n}\n\nvoid uiDrawMatrixSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount)\n{\n\tD2D1_MATRIX_3X2_F dm;\n\tD2D1_POINT_2F center;\n\n\tm2d(m, &dm);\n\tcenter.x = x;\n\tcenter.y = y;\n\tdm = dm * D2D1::Matrix3x2F::Skew(r2d(xamount), r2d(yamount), center);\n\td2m(&dm, m);\n}\n\nvoid uiDrawMatrixMultiply(uiDrawMatrix *dest, uiDrawMatrix *src)\n{\n\tD2D1_MATRIX_3X2_F c, d;\n\n\tm2d(dest, &c);\n\tm2d(src, &d);\n\tc = c * d;\n\td2m(&c, dest);\n}\n\nint uiDrawMatrixInvertible(uiDrawMatrix *m)\n{\n\tD2D1_MATRIX_3X2_F d;\n\n\tm2d(m, &d);\n\treturn D2D1IsMatrixInvertible(&d) != FALSE;\n}\n\nint uiDrawMatrixInvert(uiDrawMatrix *m)\n{\n\tD2D1_MATRIX_3X2_F d;\n\n\tm2d(m, &d);\n\tif (D2D1InvertMatrix(&d) == FALSE)\n\t\treturn 0;\n\td2m(&d, m);\n\treturn 1;\n}\n\nvoid uiDrawMatrixTransformPoint(uiDrawMatrix *m, double *x, double *y)\n{\n\tD2D1::Matrix3x2F dm;\n\tD2D1_POINT_2F pt;\n\n\tm2d(m, &dm);\n\tpt.x = *x;\n\tpt.y = *y;\n\tpt = dm.TransformPoint(pt);\n\t*x = pt.x;\n\t*y = pt.y;\n}\n\nvoid uiDrawMatrixTransformSize(uiDrawMatrix *m, double *x, double *y)\n{\n\tuiprivFallbackTransformSize(m, x, y);\n}\n"
  },
  {
    "path": "windows/drawpath.cpp",
    "content": "// 7 september 2015\n#include \"uipriv_windows.hpp\"\n#include \"draw.hpp\"\n\n// TODO\n// - write a test for transform followed by clip and clip followed by transform to make sure they work the same as on gtk+ and cocoa\n// - write a test for nested transforms for gtk+\n\nstruct uiDrawPath {\n\tID2D1PathGeometry *path;\n\tID2D1GeometrySink *sink;\n\tBOOL inFigure;\n};\n\nuiDrawPath *uiDrawNewPath(uiDrawFillMode fillmode)\n{\n\tuiDrawPath *p;\n\tHRESULT hr;\n\n\tp = uiprivNew(uiDrawPath);\n\thr = d2dfactory->CreatePathGeometry(&(p->path));\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating path\", hr);\n\thr = p->path->Open(&(p->sink));\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error opening path\", hr);\n\tswitch (fillmode) {\n\tcase uiDrawFillModeWinding:\n\t\tp->sink->SetFillMode(D2D1_FILL_MODE_WINDING);\n\t\tbreak;\n\tcase uiDrawFillModeAlternate:\n\t\tp->sink->SetFillMode(D2D1_FILL_MODE_ALTERNATE);\n\t\tbreak;\n\t}\n\treturn p;\n}\n\nvoid uiDrawFreePath(uiDrawPath *p)\n{\n\tif (p->inFigure)\n\t\tp->sink->EndFigure(D2D1_FIGURE_END_OPEN);\n\tif (p->sink != NULL)\n\t\t// TODO close sink first?\n\t\tp->sink->Release();\n\tp->path->Release();\n\tuiprivFree(p);\n}\n\nvoid uiDrawPathNewFigure(uiDrawPath *p, double x, double y)\n{\n\tD2D1_POINT_2F pt;\n\n\tif (p->inFigure)\n\t\tp->sink->EndFigure(D2D1_FIGURE_END_OPEN);\n\tpt.x = x;\n\tpt.y = y;\n\tp->sink->BeginFigure(pt, D2D1_FIGURE_BEGIN_FILLED);\n\tp->inFigure = TRUE;\n}\n\n// Direct2D arcs require a little explanation.\n// An arc in Direct2D is defined by the chord between the endpoints.\n// There are four possible arcs with the same two endpoints that you can draw this way.\n// See https://www.youtube.com/watch?v=ATS0ANW1UxQ for a demonstration.\n// There is a property rotationAngle which deals with the rotation /of the entire ellipse that forms an ellpitical arc/ - it's effectively a transformation on the arc.\n// That is to say, it's NOT THE SWEEP.\n// The sweep is defined by the start and end points and whether the arc is \"large\".\n// As a result, this design does not allow for full circles or ellipses with a single arc; they have to be simulated with two.\n// TODO https://github.com/Microsoft/WinObjC/blob/develop/Frameworks/CoreGraphics/CGPath.mm#L313\n\nstruct arc {\n\tdouble xCenter;\n\tdouble yCenter;\n\tdouble radius;\n\tdouble startAngle;\n\tdouble sweep;\n\tint negative;\n};\n\n// this is used for the comparison below\n// if it falls apart it can be changed later\n#define aerMax 6 * DBL_EPSILON\n\nstatic void drawArc(uiDrawPath *p, struct arc *a, void (*startFunction)(uiDrawPath *, double, double))\n{\n\tdouble sinx, cosx;\n\tdouble startX, startY;\n\tdouble endX, endY;\n\tD2D1_ARC_SEGMENT as;\n\tBOOL fullCircle;\n\tdouble absSweep;\n\n\t// as above, we can't do a full circle with one arc\n\t// simulate it with two half-circles\n\t// of course, we have a dragon: equality on floating-point values!\n\t// I've chosen to do the AlmostEqualRelative() technique in https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/\n\tfullCircle = FALSE;\n\t// use the absolute value to tackle both ≥2π and ≤-2π at the same time\n\tabsSweep = fabs(a->sweep);\n\tif (absSweep > (2 * uiPi))\t\t// this part is easy\n\t\tfullCircle = TRUE;\n\telse {\n\t\tdouble aerDiff;\n\n\t\taerDiff = fabs(absSweep - (2 * uiPi));\n\t\t// if we got here then we know a->sweep is larger (or the same!)\n\t\tfullCircle = aerDiff <= absSweep * aerMax;\n\t}\n\t// TODO make sure this works right for the negative direction\n\tif (fullCircle) {\n\t\ta->sweep = uiPi;\n\t\tdrawArc(p, a, startFunction);\n\t\ta->startAngle += uiPi;\n\t\tdrawArc(p, a, NULL);\n\t\treturn;\n\t}\n\n\t// first, figure out the arc's endpoints\n\t// unfortunately D2D1SinCos() is only defined on Windows 8 and newer\n\t// the MSDN page doesn't say this, but says it requires d2d1_1.h, which is listed as only supported on Windows 8 and newer elsewhere on MSDN\n\t// so we must use sin() and cos() and hope it's right...\n\tsinx = sin(a->startAngle);\n\tcosx = cos(a->startAngle);\n\tstartX = a->xCenter + a->radius * cosx;\n\tstartY = a->yCenter + a->radius * sinx;\n\tsinx = sin(a->startAngle + a->sweep);\n\tcosx = cos(a->startAngle + a->sweep);\n\tendX = a->xCenter + a->radius * cosx;\n\tendY = a->yCenter + a->radius * sinx;\n\n\t// now do the initial step to get the current point to be the start point\n\t// this is either creating a new figure, drawing a line, or (in the case of our full circle code above) doing nothing\n\tif (startFunction != NULL)\n\t\t(*startFunction)(p, startX, startY);\n\n\t// now we can draw the arc\n\tas.point.x = endX;\n\tas.point.y = endY;\n\tas.size.width = a->radius;\n\tas.size.height = a->radius;\n\tas.rotationAngle = 0;\t\t// as above, not relevant for circles\n\tif (a->negative)\n\t\tas.sweepDirection = D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE;\n\telse\n\t\tas.sweepDirection = D2D1_SWEEP_DIRECTION_CLOCKWISE;\n\t// TODO explain the outer if\n\tif (!a->negative)\n\t\tif (a->sweep > uiPi)\n\t\t\tas.arcSize = D2D1_ARC_SIZE_LARGE;\n\t\telse\n\t\t\tas.arcSize = D2D1_ARC_SIZE_SMALL;\n\telse\n\t\t// TODO especially this part\n\t\tif (a->sweep > uiPi)\n\t\t\tas.arcSize = D2D1_ARC_SIZE_SMALL;\n\t\telse\n\t\t\tas.arcSize = D2D1_ARC_SIZE_LARGE;\n\tp->sink->AddArc(&as);\n}\n\nvoid uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)\n{\n\tstruct arc a;\n\n\ta.xCenter = xCenter;\n\ta.yCenter = yCenter;\n\ta.radius = radius;\n\ta.startAngle = startAngle;\n\ta.sweep = sweep;\n\ta.negative = negative;\n\tdrawArc(p, &a, uiDrawPathNewFigure);\n}\n\nvoid uiDrawPathLineTo(uiDrawPath *p, double x, double y)\n{\n\tD2D1_POINT_2F pt;\n\n\tpt.x = x;\n\tpt.y = y;\n\tp->sink->AddLine(pt);\n}\n\nvoid uiDrawPathArcTo(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)\n{\n\tstruct arc a;\n\n\ta.xCenter = xCenter;\n\ta.yCenter = yCenter;\n\ta.radius = radius;\n\ta.startAngle = startAngle;\n\ta.sweep = sweep;\n\ta.negative = negative;\n\tdrawArc(p, &a, uiDrawPathLineTo);\n}\n\nvoid uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, double c2y, double endX, double endY)\n{\n\tD2D1_BEZIER_SEGMENT s;\n\n\ts.point1.x = c1x;\n\ts.point1.y = c1y;\n\ts.point2.x = c2x;\n\ts.point2.y = c2y;\n\ts.point3.x = endX;\n\ts.point3.y = endY;\n\tp->sink->AddBezier(&s);\n}\n\nvoid uiDrawPathCloseFigure(uiDrawPath *p)\n{\n\tp->sink->EndFigure(D2D1_FIGURE_END_CLOSED);\n\tp->inFigure = FALSE;\n}\n\nvoid uiDrawPathAddRectangle(uiDrawPath *p, double x, double y, double width, double height)\n{\n\t// this is the same algorithm used by cairo and Core Graphics, according to their documentations\n\tuiDrawPathNewFigure(p, x, y);\n\tuiDrawPathLineTo(p, x + width, y);\n\tuiDrawPathLineTo(p, x + width, y + height);\n\tuiDrawPathLineTo(p, x, y + height);\n\tuiDrawPathCloseFigure(p);\n}\n\nvoid uiDrawPathEnd(uiDrawPath *p)\n{\n\tHRESULT hr;\n\n\tif (p->inFigure) {\n\t\tp->sink->EndFigure(D2D1_FIGURE_END_OPEN);\n\t\t// needed for uiDrawFreePath()\n\t\tp->inFigure = FALSE;\n\t}\n\thr = p->sink->Close();\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error closing path\", hr);\n\tp->sink->Release();\n\t// also needed for uiDrawFreePath()\n\tp->sink = NULL;\n}\n\nID2D1PathGeometry *pathGeometry(uiDrawPath *p)\n{\n\tif (p->sink != NULL)\n\t\tuiprivUserBug(\"You cannot draw with a uiDrawPath that was not ended. (path: %p)\", p);\n\treturn p->path;\n}\n"
  },
  {
    "path": "windows/drawtext.cpp",
    "content": "// 17 january 2017\n#include \"uipriv_windows.hpp\"\n#include \"draw.hpp\"\n#include \"attrstr.hpp\"\n\n// TODO verify our renderer is correct, especially with regards to snapping\n\nstruct uiDrawTextLayout {\n\tIDWriteTextFormat *format;\n\tIDWriteTextLayout *layout;\n\tstd::vector<struct drawTextBackgroundParams *> *backgroundParams;\n\t// for converting DirectWrite indices from/to byte offsets\n\tsize_t *u8tou16;\n\tsize_t nUTF8;\n\tsize_t *u16tou8;\n\tsize_t nUTF16;\n};\n\n// TODO copy notes about DirectWrite DIPs being equal to Direct2D DIPs here\n\n// typographic points are 1/72 inch; this parameter is 1/96 inch\n// fortunately Microsoft does this too, in https://msdn.microsoft.com/en-us/library/windows/desktop/dd371554%28v=vs.85%29.aspx\n#define pointSizeToDWriteSize(size) (size * (96.0 / 72.0))\n\n// TODO move this and the layout creation stuff to attrstr.cpp like the other ports, or move the other ports into their drawtext.* files\n// 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)\nstatic std::map<uiDrawTextAlign, DWRITE_TEXT_ALIGNMENT> dwriteAligns = {\n\t{ uiDrawTextAlignLeft, DWRITE_TEXT_ALIGNMENT_LEADING },\n\t{ uiDrawTextAlignCenter, DWRITE_TEXT_ALIGNMENT_CENTER },\n\t{ uiDrawTextAlignRight, DWRITE_TEXT_ALIGNMENT_TRAILING },\n};\n\nuiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *p)\n{\n\tuiDrawTextLayout *tl;\n\tWCHAR *wDefaultFamily;\n\tDWRITE_WORD_WRAPPING wrap;\n\tFLOAT maxWidth;\n\tHRESULT hr;\n\n\ttl = uiprivNew(uiDrawTextLayout);\n\n\twDefaultFamily = toUTF16(p->DefaultFont->Family);\n\thr = dwfactory->CreateTextFormat(\n\t\twDefaultFamily, NULL,\n\t\tuiprivWeightToDWriteWeight(p->DefaultFont->Weight),\n\t\tuiprivItalicToDWriteStyle(p->DefaultFont->Italic),\n\t\tuiprivStretchToDWriteStretch(p->DefaultFont->Stretch),\n\t\tpointSizeToDWriteSize(p->DefaultFont->Size),\n\t\t// see http://stackoverflow.com/questions/28397971/idwritefactorycreatetextformat-failing and https://msdn.microsoft.com/en-us/library/windows/desktop/dd368203.aspx\n\t\t// TODO use the current locale?\n\t\tL\"\",\n\t\t&(tl->format));\n\tuiprivFree(wDefaultFamily);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating IDWriteTextFormat\", hr);\n\thr = tl->format->SetTextAlignment(dwriteAligns[p->Align]);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error applying text layout alignment\", hr);\n\n\thr = dwfactory->CreateTextLayout(\n\t\t(const WCHAR *) uiprivAttributedStringUTF16String(p->String), uiprivAttributedStringUTF16Len(p->String),\n\t\ttl->format,\n\t\t// FLOAT is float, not double, so this should work... TODO\n\t\tFLT_MAX, FLT_MAX,\n\t\t&(tl->layout));\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating IDWriteTextLayout\", hr);\n\n\t// and set the width\n\t// 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)\n\twrap = DWRITE_WORD_WRAPPING_WRAP;\n\tmaxWidth = (FLOAT) (p->Width);\n\tif (p->Width < 0) {\n\t\t// TODO is this wrapping juggling even necessary?\n\t\twrap = DWRITE_WORD_WRAPPING_NO_WRAP;\n\t\t// 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\n\t\tmaxWidth = FLT_MAX;\t\t// see TODO above\n\t}\n\thr = tl->layout->SetWordWrapping(wrap);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error setting IDWriteTextLayout word wrapping mode\", hr);\n\thr = tl->layout->SetMaxWidth(maxWidth);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error setting IDWriteTextLayout max layout width\", hr);\n\n\tuiprivAttributedStringApplyAttributesToDWriteTextLayout(p, tl->layout, &(tl->backgroundParams));\n\n\t// and finally copy the UTF-8/UTF-16 index conversion tables\n\ttl->u8tou16 = uiprivAttributedStringCopyUTF8ToUTF16Table(p->String, &(tl->nUTF8));\n\ttl->u16tou8 = uiprivAttributedStringCopyUTF16ToUTF8Table(p->String, &(tl->nUTF16));\n\n\treturn tl;\n}\n\nvoid uiDrawFreeTextLayout(uiDrawTextLayout *tl)\n{\n\tuiprivFree(tl->u16tou8);\n\tuiprivFree(tl->u8tou16);\n\tfor (auto p : *(tl->backgroundParams))\n\t\tuiprivFree(p);\n\tdelete tl->backgroundParams;\n\ttl->layout->Release();\n\ttl->format->Release();\n\tuiprivFree(tl);\n}\n\n// TODO make this shared code somehow\nstatic HRESULT mkSolidBrush(ID2D1RenderTarget *rt, double r, double g, double b, double a, ID2D1SolidColorBrush **brush)\n{\n\tD2D1_BRUSH_PROPERTIES props;\n\tD2D1_COLOR_F color;\n\n\tZeroMemory(&props, sizeof (D2D1_BRUSH_PROPERTIES));\n\tprops.opacity = 1.0;\n\t// identity matrix\n\tprops.transform._11 = 1;\n\tprops.transform._22 = 1;\n\tcolor.r = r;\n\tcolor.g = g;\n\tcolor.b = b;\n\tcolor.a = a;\n\treturn rt->CreateSolidColorBrush(\n\t\t&color,\n\t\t&props,\n\t\tbrush);\n}\n\nstatic ID2D1SolidColorBrush *mustMakeSolidBrush(ID2D1RenderTarget *rt, double r, double g, double b, double a)\n{\n\tID2D1SolidColorBrush *brush;\n\tHRESULT hr;\n\n\thr = mkSolidBrush(rt, r, g, b, a, &brush);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating solid brush\", hr);\n\treturn brush;\n}\n\n// some of the stuff we want to do isn't possible with what DirectWrite provides itself; we need to do it ourselves\n\ndrawingEffectsAttr::drawingEffectsAttr(void)\n{\n\tthis->refcount = 1;\n\tthis->hasColor = false;\n\tthis->hasUnderline = false;\n\tthis->hasUnderlineColor = false;\n}\n\nHRESULT STDMETHODCALLTYPE drawingEffectsAttr::QueryInterface(REFIID riid, void **ppvObject)\n{\n\tif (ppvObject == NULL)\n\t\treturn E_POINTER;\n\tif (riid == IID_IUnknown) {\n\t\tthis->AddRef();\n\t\t*ppvObject = this;\n\t\treturn S_OK;\n\t}\n\t*ppvObject = NULL;\n\treturn E_NOINTERFACE;\n}\n\nULONG STDMETHODCALLTYPE drawingEffectsAttr::AddRef(void)\n{\n\tthis->refcount++;\n\treturn this->refcount;\n}\n\nULONG STDMETHODCALLTYPE drawingEffectsAttr::Release(void)\n{\n\tthis->refcount--;\n\tif (this->refcount == 0) {\n\t\tdelete this;\n\t\treturn 0;\n\t}\n\treturn this->refcount;\n}\n\nvoid drawingEffectsAttr::setColor(double r, double g, double b, double a)\n{\n\tthis->hasColor = true;\n\tthis->r = r;\n\tthis->g = g;\n\tthis->b = b;\n\tthis->a = a;\n}\n\nvoid drawingEffectsAttr::setUnderline(uiUnderline u)\n{\n\tthis->hasUnderline = true;\n\tthis->u = u;\n}\n\nvoid drawingEffectsAttr::setUnderlineColor(double r, double g, double b, double a)\n{\n\tthis->hasUnderlineColor = true;\n\tthis->ur = r;\n\tthis->ug = g;\n\tthis->ub = b;\n\tthis->ua = a;\n}\n\nHRESULT drawingEffectsAttr::mkColorBrush(ID2D1RenderTarget *rt, ID2D1SolidColorBrush **b)\n{\n\tif (!this->hasColor) {\n\t\t*b = NULL;\n\t\treturn S_OK;\n\t}\n\treturn mkSolidBrush(rt, this->r, this->g, this->b, this->a, b);\n}\n\nHRESULT drawingEffectsAttr::underline(uiUnderline *u)\n{\n\tif (u == NULL)\n\t\treturn E_POINTER;\n\tif (!this->hasUnderline)\n\t\treturn E_UNEXPECTED;\n\t*u = this->u;\n\treturn S_OK;\n}\n\nHRESULT drawingEffectsAttr::mkUnderlineBrush(ID2D1RenderTarget *rt, ID2D1SolidColorBrush **b)\n{\n\tif (!this->hasUnderlineColor) {\n\t\t*b = NULL;\n\t\treturn S_OK;\n\t}\n\treturn mkSolidBrush(rt, this->ur, this->ug, this->ub, this->ua, b);\n}\n\n// this is based on http://www.charlespetzold.com/blog/2014/01/Character-Formatting-Extensions-with-DirectWrite.html\nclass textRenderer : public IDWriteTextRenderer {\n\tULONG refcount;\n\tID2D1RenderTarget *rt;\n\tBOOL snap;\n\tID2D1SolidColorBrush *black;\npublic:\n\ttextRenderer(ID2D1RenderTarget *rt, BOOL snap, ID2D1SolidColorBrush *black)\n\t{\n\t\tthis->refcount = 1;\n\t\tthis->rt = rt;\n\t\tthis->snap = snap;\n\t\tthis->black = black;\n\t}\n\n\t// IUnknown\n\tvirtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject)\n\t{\n\t\tif (ppvObject == NULL)\n\t\t\treturn E_POINTER;\n\t\tif (riid == IID_IUnknown ||\n\t\t\triid == __uuidof (IDWritePixelSnapping) ||\n\t\t\triid == __uuidof (IDWriteTextRenderer)) {\n\t\t\tthis->AddRef();\n\t\t\t*ppvObject = this;\n\t\t\treturn S_OK;\n\t\t}\n\t\t*ppvObject = NULL;\n\t\treturn E_NOINTERFACE;\n\t}\n\n\tvirtual ULONG STDMETHODCALLTYPE AddRef(void)\n\t{\n\t\tthis->refcount++;\n\t\treturn this->refcount;\n\t}\n\n\tvirtual ULONG STDMETHODCALLTYPE Release(void)\n\t{\n\t\tthis->refcount--;\n\t\tif (this->refcount == 0) {\n\t\t\tdelete this;\n\t\t\treturn 0;\n\t\t}\n\t\treturn this->refcount;\n\t}\n\n\t// IDWritePixelSnapping\n\tvirtual HRESULT STDMETHODCALLTYPE GetCurrentTransform(void *clientDrawingContext, DWRITE_MATRIX *transform)\n\t{\n\t\tD2D1_MATRIX_3X2_F d2dtf;\n\n\t\tif (transform == NULL)\n\t\t\treturn E_POINTER;\n\t\tthis->rt->GetTransform(&d2dtf);\n\t\ttransform->m11 = d2dtf._11;\n\t\ttransform->m12 = d2dtf._12;\n\t\ttransform->m21 = d2dtf._21;\n\t\ttransform->m22 = d2dtf._22;\n\t\ttransform->dx = d2dtf._31;\n\t\ttransform->dy = d2dtf._32;\n\t\treturn S_OK;\n\t}\n\n\tvirtual HRESULT STDMETHODCALLTYPE GetPixelsPerDip(void *clientDrawingContext, FLOAT *pixelsPerDip)\n\t{\n\t\tFLOAT dpix, dpiy;\n\n\t\tif (pixelsPerDip == NULL)\n\t\t\treturn E_POINTER;\n\t\tthis->rt->GetDpi(&dpix, &dpiy);\n\t\t*pixelsPerDip = dpix / 96;\n\t\treturn S_OK;\n\t}\n\n\tvirtual HRESULT STDMETHODCALLTYPE IsPixelSnappingDisabled(void *clientDrawingContext, BOOL *isDisabled)\n\t{\n\t\tif (isDisabled == NULL)\n\t\t\treturn E_POINTER;\n\t\t*isDisabled = !this->snap;\n\t\treturn S_OK;\n\t}\n\n\t// IDWriteTextRenderer\n\tvirtual 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)\n\t{\n\t\tD2D1_POINT_2F baseline;\n\t\tdrawingEffectsAttr *dea = (drawingEffectsAttr *) clientDrawingEffect;\n\t\tID2D1SolidColorBrush *brush;\n\n\t\tbaseline.x = baselineOriginX;\n\t\tbaseline.y = baselineOriginY;\n\t\tbrush = NULL;\n\t\tif (dea != NULL) {\n\t\t\tHRESULT hr;\n\n\t\t\thr = dea->mkColorBrush(this->rt, &brush);\n\t\t\tif (hr != S_OK)\n\t\t\t\treturn hr;\n\t\t}\n\t\tif (brush == NULL) {\n\t\t\tbrush = this->black;\n\t\t\tbrush->AddRef();\n\t\t}\n\t\tthis->rt->DrawGlyphRun(\n\t\t\tbaseline,\n\t\t\tglyphRun,\n\t\t\tbrush,\n\t\t\tmeasuringMode);\n\t\tbrush->Release();\n\t\treturn S_OK;\n\t}\n\n\tvirtual HRESULT STDMETHODCALLTYPE DrawInlineObject(void *clientDrawingContext, FLOAT originX, FLOAT originY, IDWriteInlineObject *inlineObject, BOOL isSideways, BOOL isRightToLeft, IUnknown *clientDrawingEffect)\n\t{\n\t\tif (inlineObject == NULL)\n\t\t\treturn E_POINTER;\n\t\treturn inlineObject->Draw(clientDrawingContext, this,\n\t\t\toriginX, originY,\n\t\t\tisSideways, isRightToLeft,\n\t\t\tclientDrawingEffect);\n\t}\n\n\tvirtual HRESULT STDMETHODCALLTYPE DrawStrikethrough(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, const DWRITE_STRIKETHROUGH *strikethrough, IUnknown *clientDrawingEffect)\n\t{\n\t\t// we don't support strikethrough\n\t\treturn E_UNEXPECTED;\n\t}\n\n\t// TODO clean this function up\n\tvirtual HRESULT STDMETHODCALLTYPE DrawUnderline(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, const DWRITE_UNDERLINE *underline, IUnknown *clientDrawingEffect)\n\t{\n\t\tdrawingEffectsAttr *dea = (drawingEffectsAttr *) clientDrawingEffect;\n\t\tuiUnderline utype;\n\t\tID2D1SolidColorBrush *brush;\n\t\tD2D1_RECT_F rect;\n\t\tD2D1::Matrix3x2F pixeltf;\n\t\tFLOAT dpix, dpiy;\n\t\tD2D1_POINT_2F pt;\n\t\tHRESULT hr;\n\n\t\tif (underline == NULL)\n\t\t\treturn E_POINTER;\n\t\tif (dea == NULL)\t\t// we can only get here through an underline\n\t\t\treturn E_UNEXPECTED;\n\t\thr = dea->underline(&utype);\n\t\tif (hr != S_OK)\t\t\t// we *should* only get here through an underline that's actually set...\n\t\t\treturn hr;\n\t\thr = dea->mkUnderlineBrush(this->rt, &brush);\n\t\tif (hr != S_OK)\n\t\t\treturn hr;\n\t\tif (brush == NULL) {\n\t\t\t// TODO document this rule if not already done\n\t\t\thr = dea->mkColorBrush(this->rt, &brush);\n\t\t\tif (hr != S_OK)\n\t\t\t\treturn hr;\n\t\t}\n\t\tif (brush == NULL) {\n\t\t\tbrush = this->black;\n\t\t\tbrush->AddRef();\n\t\t}\n\t\trect.left = baselineOriginX;\n\t\trect.top = baselineOriginY + underline->offset;\n\t\trect.right = rect.left + underline->width;\n\t\trect.bottom = rect.top + underline->thickness;\n\t\tswitch (utype) {\n\t\tcase uiUnderlineSingle:\n\t\t\tthis->rt->FillRectangle(&rect, brush);\n\t\t\tbreak;\n\t\tcase uiUnderlineDouble:\n\t\t\t// TODO do any of the matrix methods return errors?\n\t\t\t// TODO standardize double-underline shape across platforms? wavy underline shape?\n\t\t\tthis->rt->GetTransform(&pixeltf);\n\t\t\tthis->rt->GetDpi(&dpix, &dpiy);\n\t\t\tpixeltf = pixeltf * D2D1::Matrix3x2F::Scale(dpix / 96, dpiy / 96);\n\t\t\tpt.x = 0;\n\t\t\tpt.y = rect.top;\n\t\t\tpt = pixeltf.TransformPoint(pt);\n\t\t\trect.top = (FLOAT) ((int) (pt.y + 0.5));\n\t\t\tpixeltf.Invert();\n\t\t\tpt = pixeltf.TransformPoint(pt);\n\t\t\trect.top = pt.y;\n\t\t\t// first line\n\t\t\trect.top -= underline->thickness;\n\t\t\t// and it seems we need to recompute this\n\t\t\trect.bottom = rect.top + underline->thickness;\n\t\t\tthis->rt->FillRectangle(&rect, brush);\n\t\t\t// second line\n\t\t\trect.top += 2 * underline->thickness;\n\t\t\trect.bottom = rect.top + underline->thickness;\n\t\t\tthis->rt->FillRectangle(&rect, brush);\n\t\t\tbreak;\n\t\tcase uiUnderlineSuggestion:\n\t\t\t{\t\t// TODO get rid of the extra block\n\t\t\t\t\t// TODO properly clean resources on failure\n\t\t\t\t\t// TODO use fully qualified C overloads for all methods\n\t\t\t\t\t// TODO ensure all methods properly have errors handled\n\t\t\t\tID2D1PathGeometry *path;\n\t\t\t\tID2D1GeometrySink *sink;\n\t\t\t\tdouble amplitude, period, xOffset, yOffset;\n\t\t\t\tdouble t;\n\t\t\t\tbool first = true;\n\t\t\t\tHRESULT hr;\n\n\t\t\t\thr = d2dfactory->CreatePathGeometry(&path);\n\t\t\t\tif (hr != S_OK)\n\t\t\t\t\treturn hr;\n\t\t\t\thr = path->Open(&sink);\n\t\t\t\tif (hr != S_OK)\n\t\t\t\t\treturn hr;\n\t\t\t\tamplitude = underline->thickness;\n\t\t\t\tperiod = 5 * underline->thickness;\n\t\t\t\txOffset = baselineOriginX;\n\t\t\t\tyOffset = baselineOriginY + underline->offset;\n\t\t\t\tfor (t = 0; t < underline->width; t++) {\n\t\t\t\t\tdouble x, angle, y;\n\t\t\t\t\tD2D1_POINT_2F pt;\n\n\t\t\t\t\tx = t + xOffset;\n\t\t\t\t\tangle = 2 * uiPi * fmod(x, period) / period;\n\t\t\t\t\ty = amplitude * sin(angle) + yOffset;\n\t\t\t\t\tpt.x = x;\n\t\t\t\t\tpt.y = y;\n\t\t\t\t\tif (first) {\n\t\t\t\t\t\tsink->BeginFigure(pt, D2D1_FIGURE_BEGIN_HOLLOW);\n\t\t\t\t\t\tfirst = false;\n\t\t\t\t\t} else\n\t\t\t\t\t\tsink->AddLine(pt);\n\t\t\t\t}\n\t\t\t\tsink->EndFigure(D2D1_FIGURE_END_OPEN);\n\t\t\t\thr = sink->Close();\n\t\t\t\tif (hr != S_OK)\n\t\t\t\t\treturn hr;\n\t\t\t\tsink->Release();\n\t\t\t\tthis->rt->DrawGeometry(path, brush, underline->thickness);\n\t\t\t\tpath->Release();\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tbrush->Release();\n\t\treturn S_OK;\n\t}\n};\n\n// TODO this ignores clipping?\nvoid uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)\n{\n\tD2D1_POINT_2F pt;\n\tID2D1SolidColorBrush *black;\n\ttextRenderer *renderer;\n\tHRESULT hr;\n\n\tfor (auto p : *(tl->backgroundParams)) {\n\t\t// TODO\n\t}\n\n\t// TODO document that fully opaque black is the default text color; figure out whether this is upheld in various scenarios on other platforms\n\t// TODO figure out if this needs to be cleaned out\n\tblack = mustMakeSolidBrush(c->rt, 0.0, 0.0, 0.0, 1.0);\n\n#define renderD2D 0\n#define renderOur 1\n#if renderD2D\n\tpt.x = x;\n\tpt.y = y;\n\t// TODO D2D1_DRAW_TEXT_OPTIONS_NO_SNAP?\n\t// TODO D2D1_DRAW_TEXT_OPTIONS_CLIP?\n\t// TODO LONGTERM when setting 8.1 as minimum (TODO verify), D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT?\n\t// TODO what is our pixel snapping setting related to the OPTIONS enum values?\n\tc->rt->DrawTextLayout(pt, tl->layout, black, D2D1_DRAW_TEXT_OPTIONS_NONE);\n#endif\n#if renderD2D && renderOur\n\t// draw ours semitransparent so we can check\n\t// TODO get the actual color Charles Petzold uses and use that\n\tblack->Release();\n\tblack = mustMakeSolidBrush(c->rt, 1.0, 0.0, 0.0, 0.75);\n#endif\n#if renderOur\n\trenderer = new textRenderer(c->rt,\n\t\tTRUE,\t\t\t// TODO FALSE for no-snap?\n\t\tblack);\n\thr = tl->layout->Draw(NULL,\n\t\trenderer,\n\t\tx, y);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error drawing IDWriteTextLayout\", hr);\n\trenderer->Release();\n#endif\n\n\tblack->Release();\n}\n\n// 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...\n// TODO width does not include trailing whitespace\nvoid uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height)\n{\n\tDWRITE_TEXT_METRICS metrics;\n\tHRESULT hr;\n\n\thr = tl->layout->GetMetrics(&metrics);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error getting IDWriteTextLayout layout metrics\", hr);\n\t*width = metrics.width;\n\t// 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)\n\t*height = metrics.height;\n}\n"
  },
  {
    "path": "windows/dwrite.cpp",
    "content": "// 14 april 2016\n#include \"uipriv_windows.hpp\"\n#include \"attrstr.hpp\"\n\nIDWriteFactory *dwfactory = NULL;\n\n// TOOD rename to something else, maybe\nHRESULT uiprivInitDrawText(void)\n{\n\t// TOOD use DWRITE_FACTORY_TYPE_ISOLATED instead?\n\treturn DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED,\n\t\t__uuidof (IDWriteFactory),\n\t\t(IUnknown **) (&dwfactory));\n}\n\nvoid uiprivUninitDrawText(void)\n{\n\tdwfactory->Release();\n}\n\nfontCollection *uiprivLoadFontCollection(void)\n{\n\tfontCollection *fc;\n\tHRESULT hr;\n\n\tfc = uiprivNew(fontCollection);\n\t// always get the latest available font information\n\thr = dwfactory->GetSystemFontCollection(&(fc->fonts), TRUE);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error getting system font collection\", hr);\n\tfc->userLocaleSuccess = GetUserDefaultLocaleName(fc->userLocale, LOCALE_NAME_MAX_LENGTH);\n\treturn fc;\n}\n\nvoid uiprivFontCollectionFree(fontCollection *fc)\n{\n\tfc->fonts->Release();\n\tuiprivFree(fc);\n}\n\nWCHAR *uiprivFontCollectionFamilyName(fontCollection *fc, IDWriteFontFamily *family)\n{\n\tIDWriteLocalizedStrings *names;\n\tWCHAR *str;\n\tHRESULT hr;\n\n\thr = family->GetFamilyNames(&names);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error getting names of font out\", hr);\n\tstr = uiprivFontCollectionCorrectString(fc, names);\n\tnames->Release();\n\treturn str;\n}\n\nWCHAR *uiprivFontCollectionCorrectString(fontCollection *fc, IDWriteLocalizedStrings *names)\n{\n\tUINT32 index;\n\tBOOL exists;\n\tUINT32 length;\n\tWCHAR *wname;\n\tHRESULT hr;\n\n\t// this is complex, but we ignore failure conditions to allow fallbacks\n\t// 1) If the user locale name was successfully retrieved, try it\n\t// 2) If the user locale name was not successfully retrieved, or that locale's string does not exist, or an error occurred, try L\"en-us\", the US English locale\n\t// 3) And if that fails, assume the first one\n\t// This algorithm is straight from MSDN: https://msdn.microsoft.com/en-us/library/windows/desktop/dd368214%28v=vs.85%29.aspx\n\t// For step 2 to work, start by setting hr to S_OK and exists to FALSE.\n\t// TODO does it skip step 2 entirely if step 1 fails? rewrite it to be a more pure conversion of the MSDN code?\n\thr = S_OK;\n\texists = FALSE;\n\tif (fc->userLocaleSuccess != 0)\n\t\thr = names->FindLocaleName(fc->userLocale, &index, &exists);\n\tif (hr != S_OK || (hr == S_OK && !exists))\n\t\thr = names->FindLocaleName(L\"en-us\", &index, &exists);\n\t// 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?\n\tif (!exists)\n\t\tindex = 0;\n\n\thr = names->GetStringLength(index, &length);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error getting length of font name\", hr);\n\t// GetStringLength() does not include the null terminator, but GetString() does\n\twname = (WCHAR *) uiprivAlloc((length + 1) * sizeof (WCHAR), \"WCHAR[]\");\n\thr = names->GetString(index, wname, length + 1);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error getting font name\", hr);\n\n\treturn wname;\n}\n"
  },
  {
    "path": "windows/editablecombo.cpp",
    "content": "// 20 may 2015\n#include \"uipriv_windows.hpp\"\n\n// TODO no scrollbars? also not sure if true for combobox as well\n\n// 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\n\nstruct uiEditableCombobox {\n\tuiWindowsControl c;\n\tHWND hwnd;\n\tvoid (*onChanged)(uiEditableCombobox *, void *);\n\tvoid *onChangedData;\n};\n\nstatic BOOL onWM_COMMAND(uiControl *cc, HWND hwnd, WORD code, LRESULT *lResult)\n{\n\tuiEditableCombobox *c = uiEditableCombobox(cc);\n\n\tif (code == CBN_SELCHANGE) {\n\t\t// like on OS X, this is sent before the edit has been updated :(\n\t\tif (PostMessage(parentOf(hwnd),\n\t\t\tWM_COMMAND,\n\t\t\tMAKEWPARAM(GetWindowLongPtrW(hwnd, GWLP_ID), CBN_EDITCHANGE),\n\t\t\t(LPARAM) hwnd) == 0)\n\t\t\tlogLastError(L\"error posting CBN_EDITCHANGE after CBN_SELCHANGE\");\n\t\t*lResult = 0;\n\t\treturn TRUE;\n\t}\n\tif (code != CBN_EDITCHANGE)\n\t\treturn FALSE;\n\t(*(c->onChanged))(c, c->onChangedData);\n\t*lResult = 0;\n\treturn TRUE;\n}\n\nvoid uiEditableComboboxDestroy(uiControl *cc)\n{\n\tuiEditableCombobox *c = uiEditableCombobox(cc);\n\n\tuiWindowsUnregisterWM_COMMANDHandler(c->hwnd);\n\tuiWindowsEnsureDestroyWindow(c->hwnd);\n\tuiFreeControl(uiControl(c));\n}\n\nuiWindowsControlAllDefaultsExceptDestroy(uiEditableCombobox)\n\n// from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing\n#define comboboxWidth 107\t/* this is actually the shorter progress bar width, but Microsoft only indicates as wide as necessary; LONGTERM */\n#define comboboxHeight 14\t/* LONGTERM: is this too high? */\n\nstatic void uiEditableComboboxMinimumSize(uiWindowsControl *cc, int *width, int *height)\n{\n\tuiEditableCombobox *c = uiEditableCombobox(cc);\n\tuiWindowsSizing sizing;\n\tint x, y;\n\n\tx = comboboxWidth;\n\ty = comboboxHeight;\n\tuiWindowsGetSizing(c->hwnd, &sizing);\n\tuiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y);\n\t*width = x;\n\t*height = y;\n}\n\nstatic void defaultOnChanged(uiEditableCombobox *c, void *data)\n{\n\t// do nothing\n}\n\nvoid uiEditableComboboxAppend(uiEditableCombobox *c, const char *text)\n{\n\tWCHAR *wtext;\n\tLRESULT res;\n\n\twtext = toUTF16(text);\n\tres = SendMessageW(c->hwnd, CB_ADDSTRING, 0, (LPARAM) wtext);\n\tif (res == (LRESULT) CB_ERR)\n\t\tlogLastError(L\"error appending item to uiEditableCombobox\");\n\telse if (res == (LRESULT) CB_ERRSPACE)\n\t\tlogLastError(L\"memory exhausted appending item to uiEditableCombobox\");\n\tuiprivFree(wtext);\n}\n\nchar *uiEditableComboboxText(uiEditableCombobox *c)\n{\n\treturn uiWindowsWindowText(c->hwnd);\n}\n\nvoid uiEditableComboboxSetText(uiEditableCombobox *c, const char *text)\n{\n\t// does not trigger any notifications\n\tuiWindowsSetWindowText(c->hwnd, text);\n}\n\nvoid uiEditableComboboxOnChanged(uiEditableCombobox *c, void (*f)(uiEditableCombobox *c, void *data), void *data)\n{\n\tc->onChanged = f;\n\tc->onChangedData = data;\n}\n\nuiEditableCombobox *uiNewEditableCombobox(void)\n{\n\tuiEditableCombobox *c;\n\n\tuiWindowsNewControl(uiEditableCombobox, c);\n\n\tc->hwnd = uiWindowsEnsureCreateControlHWND(WS_EX_CLIENTEDGE,\n\t\tL\"combobox\", L\"\",\n\t\tCBS_DROPDOWN | WS_TABSTOP,\n\t\thInstance, NULL,\n\t\tTRUE);\n\n\tuiWindowsRegisterWM_COMMANDHandler(c->hwnd, onWM_COMMAND, uiControl(c));\n\tuiEditableComboboxOnChanged(c, defaultOnChanged, NULL);\n\n\treturn c;\n}\n"
  },
  {
    "path": "windows/entry.cpp",
    "content": "// 8 april 2015\n#include \"uipriv_windows.hpp\"\n\nstruct uiEntry {\n\tuiWindowsControl c;\n\tHWND hwnd;\n\tvoid (*onChanged)(uiEntry *, void *);\n\tvoid *onChangedData;\n\tBOOL inhibitChanged;\n};\n\nstatic BOOL onWM_COMMAND(uiControl *c, HWND hwnd, WORD code, LRESULT *lResult)\n{\n\tuiEntry *e = uiEntry(c);\n\n\tif (code != EN_CHANGE)\n\t\treturn FALSE;\n\tif (e->inhibitChanged)\n\t\treturn FALSE;\n\t(*(e->onChanged))(e, e->onChangedData);\n\t*lResult = 0;\n\treturn TRUE;\n}\n\nstatic void uiEntryDestroy(uiControl *c)\n{\n\tuiEntry *e = uiEntry(c);\n\n\tuiWindowsUnregisterWM_COMMANDHandler(e->hwnd);\n\tuiWindowsEnsureDestroyWindow(e->hwnd);\n\tuiFreeControl(uiControl(e));\n}\n\nuiWindowsControlAllDefaultsExceptDestroy(uiEntry)\n\n// from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing\n#define entryWidth 107 /* this is actually the shorter progress bar width, but Microsoft only indicates as wide as necessary */\n#define entryHeight 14\n\nstatic void uiEntryMinimumSize(uiWindowsControl *c, int *width, int *height)\n{\n\tuiEntry *e = uiEntry(c);\n\tuiWindowsSizing sizing;\n\tint x, y;\n\n\tx = entryWidth;\n\ty = entryHeight;\n\tuiWindowsGetSizing(e->hwnd, &sizing);\n\tuiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y);\n\t*width = x;\n\t*height = y;\n}\n\nstatic void defaultOnChanged(uiEntry *e, void *data)\n{\n\t// do nothing\n}\n\nchar *uiEntryText(uiEntry *e)\n{\n\treturn uiWindowsWindowText(e->hwnd);\n}\n\nvoid uiEntrySetText(uiEntry *e, const char *text)\n{\n\t// doing this raises an EN_CHANGED\n\te->inhibitChanged = TRUE;\n\tuiWindowsSetWindowText(e->hwnd, text);\n\te->inhibitChanged = FALSE;\n\t// don't queue the control for resize; entry sizes are independent of their contents\n}\n\nvoid uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *, void *), void *data)\n{\n\te->onChanged = f;\n\te->onChangedData = data;\n}\n\nint uiEntryReadOnly(uiEntry *e)\n{\n\treturn (getStyle(e->hwnd) & ES_READONLY) != 0;\n}\n\nvoid uiEntrySetReadOnly(uiEntry *e, int readonly)\n{\n\tWPARAM ro;\n\n\tro = (WPARAM) FALSE;\n\tif (readonly)\n\t\tro = (WPARAM) TRUE;\n\tif (SendMessage(e->hwnd, EM_SETREADONLY, ro, 0) == 0)\n\t\tlogLastError(L\"error making uiEntry read-only\");\n}\n\nstatic uiEntry *finishNewEntry(DWORD style)\n{\n\tuiEntry *e;\n\n\tuiWindowsNewControl(uiEntry, e);\n\n\te->hwnd = uiWindowsEnsureCreateControlHWND(WS_EX_CLIENTEDGE,\n\t\tL\"edit\", L\"\",\n\t\tstyle | ES_AUTOHSCROLL | ES_LEFT | ES_NOHIDESEL | WS_TABSTOP,\n\t\thInstance, NULL,\n\t\tTRUE);\n\n\tuiWindowsRegisterWM_COMMANDHandler(e->hwnd, onWM_COMMAND, uiControl(e));\n\tuiEntryOnChanged(e, defaultOnChanged, NULL);\n\n\treturn e;\n}\n\nuiEntry *uiNewEntry(void)\n{\n\treturn finishNewEntry(0);\n}\n\nuiEntry *uiNewPasswordEntry(void)\n{\n\treturn finishNewEntry(ES_PASSWORD);\n}\n\nuiEntry *uiNewSearchEntry(void)\n{\n\tuiEntry *e;\n\tHRESULT hr;\n\n\te = finishNewEntry(0);\n\t// TODO this is from ThemeExplorer; is it documented anywhere?\n\t// TODO SearchBoxEditComposited has no border\n\thr = SetWindowTheme(e->hwnd, L\"SearchBoxEdit\", NULL);\n\t// TODO will hr be S_OK if themes are disabled?\n\treturn e;\n}\n"
  },
  {
    "path": "windows/events.cpp",
    "content": "// 20 may 2015\n#include \"uipriv_windows.hpp\"\n\nstruct handler {\n\tBOOL (*commandHandler)(uiControl *, HWND, WORD, LRESULT *);\n\tBOOL (*notifyHandler)(uiControl *, HWND, NMHDR *, LRESULT *);\n\tBOOL (*hscrollHandler)(uiControl *, HWND, WORD, LRESULT *);\n\tuiControl *c;\n\n\t// just to ensure handlers[new HWND] initializes properly\n\t// TODO gcc can't handle a struct keyword here? or is that a MSVC extension?\n\thandler()\n\t{\n\t\tthis->commandHandler = NULL;\n\t\tthis->notifyHandler = NULL;\n\t\tthis->hscrollHandler = NULL;\n\t\tthis->c = NULL;\n\t}\n};\n\nstatic std::map<HWND, struct handler> handlers;\n\nvoid uiWindowsRegisterWM_COMMANDHandler(HWND hwnd, BOOL (*handler)(uiControl *, HWND, WORD, LRESULT *), uiControl *c)\n{\n\tif (handlers[hwnd].commandHandler != NULL)\n\t\tuiprivImplBug(\"already registered a WM_COMMAND handler to window handle %p\", hwnd);\n\thandlers[hwnd].commandHandler = handler;\n\thandlers[hwnd].c = c;\n}\n\nvoid uiWindowsRegisterWM_NOTIFYHandler(HWND hwnd, BOOL (*handler)(uiControl *, HWND, NMHDR *, LRESULT *), uiControl *c)\n{\n\tif (handlers[hwnd].notifyHandler != NULL)\n\t\tuiprivImplBug(\"already registered a WM_NOTIFY handler to window handle %p\", hwnd);\n\thandlers[hwnd].notifyHandler = handler;\n\thandlers[hwnd].c = c;\n}\n\nvoid uiWindowsRegisterWM_HSCROLLHandler(HWND hwnd, BOOL (*handler)(uiControl *, HWND, WORD, LRESULT *), uiControl *c)\n{\n\tif (handlers[hwnd].hscrollHandler != NULL)\n\t\tuiprivImplBug(\"already registered a WM_HSCROLL handler to window handle %p\", hwnd);\n\thandlers[hwnd].hscrollHandler = handler;\n\thandlers[hwnd].c = c;\n}\n\nvoid uiWindowsUnregisterWM_COMMANDHandler(HWND hwnd)\n{\n\tif (handlers[hwnd].commandHandler == NULL)\n\t\tuiprivImplBug(\"window handle %p not registered to receive WM_COMMAND events\", hwnd);\n\thandlers[hwnd].commandHandler = NULL;\n}\n\nvoid uiWindowsUnregisterWM_NOTIFYHandler(HWND hwnd)\n{\n\tif (handlers[hwnd].notifyHandler == NULL)\n\t\tuiprivImplBug(\"window handle %p not registered to receive WM_NOTIFY events\", hwnd);\n\thandlers[hwnd].notifyHandler = NULL;\n}\n\nvoid uiWindowsUnregisterWM_HSCROLLHandler(HWND hwnd)\n{\n\tif (handlers[hwnd].hscrollHandler == NULL)\n\t\tuiprivImplBug(\"window handle %p not registered to receive WM_HSCROLL events\", hwnd);\n\thandlers[hwnd].hscrollHandler = NULL;\n}\n\ntemplate<typename T>\nstatic BOOL shouldRun(HWND hwnd, T method)\n{\n\t// not from a window\n\tif (hwnd == NULL)\n\t\treturn FALSE;\n\t// don't bounce back if to the utility window, in which case act as if the message was ignored\n\tif (IsChild(utilWindow, hwnd) != 0)\n\t\treturn FALSE;\n\t// registered?\n\treturn method != NULL;\n}\n\nBOOL runWM_COMMAND(WPARAM wParam, LPARAM lParam, LRESULT *lResult)\n{\n\tHWND hwnd;\n\tWORD arg3;\n\tBOOL (*handler)(uiControl *, HWND, WORD, LRESULT *);\n\tuiControl *c;\n\n\thwnd = (HWND) lParam;\n\targ3 = HIWORD(wParam);\n\thandler = handlers[hwnd].commandHandler;\n\tc = handlers[hwnd].c;\n\tif (shouldRun(hwnd, handler))\n\t\treturn (*handler)(c, hwnd, arg3, lResult);\n\treturn FALSE;\n}\n\nBOOL runWM_NOTIFY(WPARAM wParam, LPARAM lParam, LRESULT *lResult)\n{\n\tHWND hwnd;\n\tNMHDR *arg3;\n\tBOOL (*handler)(uiControl *, HWND, NMHDR *, LRESULT *);\n\tuiControl *c;\n\n\targ3 = (NMHDR *) lParam;\n\thwnd = arg3->hwndFrom;\n\thandler = handlers[hwnd].notifyHandler;\n\tc = handlers[hwnd].c;\n\tif (shouldRun(hwnd, handler))\n\t\treturn (*handler)(c, hwnd, arg3, lResult);\n\treturn FALSE;\n}\n\nBOOL runWM_HSCROLL(WPARAM wParam, LPARAM lParam, LRESULT *lResult)\n{\n\tHWND hwnd;\n\tWORD arg3;\n\tBOOL (*handler)(uiControl *, HWND, WORD, LRESULT *);\n\tuiControl *c;\n\n\thwnd = (HWND) lParam;\n\targ3 = LOWORD(wParam);\n\thandler = handlers[hwnd].hscrollHandler;\n\tc = handlers[hwnd].c;\n\tif (shouldRun(hwnd, handler))\n\t\treturn (*handler)(c, hwnd, arg3, lResult);\n\treturn FALSE;\n}\n\nstatic std::map<HWND, bool> wininichanges;\n\nvoid uiWindowsRegisterReceiveWM_WININICHANGE(HWND hwnd)\n{\n\tif (wininichanges[hwnd])\n\t\tuiprivImplBug(\"window handle %p already subscribed to receive WM_WINICHANGEs\", hwnd);\n\twininichanges[hwnd] = true;\n}\n\nvoid uiWindowsUnregisterReceiveWM_WININICHANGE(HWND hwnd)\n{\n\tif (!wininichanges[hwnd])\n\t\tuiprivImplBug(\"window handle %p not registered to receive WM_WININICHANGEs\", hwnd);\n\twininichanges[hwnd] = false;\n}\n\nvoid issueWM_WININICHANGE(WPARAM wParam, LPARAM lParam)\n{\n\tstruct wininichange *ch;\n\n\tfor (const auto &iter : wininichanges)\n\t\tSendMessageW(iter.first, WM_WININICHANGE, wParam, lParam);\n}\n"
  },
  {
    "path": "windows/fontbutton.cpp",
    "content": "// 14 april 2016\n#include \"uipriv_windows.hpp\"\n#include \"attrstr.hpp\"\n\nstruct uiFontButton {\n\tuiWindowsControl c;\n\tHWND hwnd;\n\tstruct fontDialogParams params;\n\tBOOL already;\n\tvoid (*onChanged)(uiFontButton *, void *);\n\tvoid *onChangedData;\n};\n\nstatic void uiFontButtonDestroy(uiControl *c)\n{\n\tuiFontButton *b = uiFontButton(c);\n\n\tuiWindowsUnregisterWM_COMMANDHandler(b->hwnd);\n\tuiprivDestroyFontDialogParams(&(b->params));\n\tuiWindowsEnsureDestroyWindow(b->hwnd);\n\tuiFreeControl(uiControl(b));\n}\n\nstatic void updateFontButtonLabel(uiFontButton *b)\n{\n\tWCHAR *text;\n\n\ttext = uiprivFontDialogParamsToString(&(b->params));\n\tsetWindowText(b->hwnd, text);\n\tuiprivFree(text);\n\n\t// changing the text might necessitate a change in the button's size\n\tuiWindowsControlMinimumSizeChanged(uiWindowsControl(b));\n}\n\nstatic BOOL onWM_COMMAND(uiControl *c, HWND hwnd, WORD code, LRESULT *lResult)\n{\n\tuiFontButton *b = uiFontButton(c);\n\tHWND parent;\n\n\tif (code != BN_CLICKED)\n\t\treturn FALSE;\n\n\tparent = parentToplevel(b->hwnd);\n\tif (uiprivShowFontDialog(parent, &(b->params))) {\n\t\tupdateFontButtonLabel(b);\n\t\t(*(b->onChanged))(b, b->onChangedData);\n\t}\n\n\t*lResult = 0;\n\treturn TRUE;\n}\n\nuiWindowsControlAllDefaultsExceptDestroy(uiFontButton)\n\n// from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing\n#define buttonHeight 14\n\nstatic void uiFontButtonMinimumSize(uiWindowsControl *c, int *width, int *height)\n{\n\tuiFontButton *b = uiFontButton(c);\n\tSIZE size;\n\tuiWindowsSizing sizing;\n\tint y;\n\n\t// try the comctl32 version 6 way\n\tsize.cx = 0;\t\t// explicitly ask for ideal size\n\tsize.cy = 0;\n\tif (SendMessageW(b->hwnd, BCM_GETIDEALSIZE, 0, (LPARAM) (&size)) != FALSE) {\n\t\t*width = size.cx;\n\t\t*height = size.cy;\n\t\treturn;\n\t}\n\n\t// that didn't work; fall back to using Microsoft's metrics\n\t// Microsoft says to use a fixed width for all buttons; this isn't good enough\n\t// use the text width instead, with some edge padding\n\t*width = uiWindowsWindowTextWidth(b->hwnd) + (2 * GetSystemMetrics(SM_CXEDGE));\n\ty = buttonHeight;\n\tuiWindowsGetSizing(b->hwnd, &sizing);\n\tuiWindowsSizingDlgUnitsToPixels(&sizing, NULL, &y);\n\t*height = y;\n}\n\nstatic void defaultOnChanged(uiFontButton *b, void *data)\n{\n\t// do nothing\n}\n\nvoid uiFontButtonFont(uiFontButton *b, uiFontDescriptor *desc)\n{\n\tuiprivFontDescriptorFromIDWriteFont(b->params.font, desc);\n\tdesc->Family = toUTF8(b->params.familyName);\n\tdesc->Size = b->params.size;\n}\n\nvoid uiFontButtonOnChanged(uiFontButton *b, void (*f)(uiFontButton *, void *), void *data)\n{\n\tb->onChanged = f;\n\tb->onChangedData = data;\n}\n\nuiFontButton *uiNewFontButton(void)\n{\n\tuiFontButton *b;\n\n\tuiWindowsNewControl(uiFontButton, b);\n\n\tb->hwnd = uiWindowsEnsureCreateControlHWND(0,\n\t\tL\"button\", L\"you should not be seeing this\",\n\t\tBS_PUSHBUTTON | WS_TABSTOP,\n\t\thInstance, NULL,\n\t\tTRUE);\n\n\tuiprivLoadInitialFontDialogParams(&(b->params));\n\n\tuiWindowsRegisterWM_COMMANDHandler(b->hwnd, onWM_COMMAND, uiControl(b));\n\tuiFontButtonOnChanged(b, defaultOnChanged, NULL);\n\n\tupdateFontButtonLabel(b);\n\n\treturn b;\n}\n\nvoid uiFreeFontButtonFont(uiFontDescriptor *desc)\n{\n\tuiprivFree((char *) (desc->Family));\n}\n"
  },
  {
    "path": "windows/fontdialog.cpp",
    "content": "// 14 april 2016\n#include \"uipriv_windows.hpp\"\n#include \"attrstr.hpp\"\n\n// TODOs\n// - quote the Choose Font sample here for reference\n// - the Choose Font sample defaults to Regular/Italic/Bold/Bold Italic in some case (no styles?); do we? find out what the case is\n// - do we set initial family and style topmost as well?\n// - this should probably just handle IDWriteFonts\n// - localization?\n// - the Sample window overlaps the groupbox in a weird way (compare to the real ChooseFont() dialog)\n\nstruct fontDialog {\n\tHWND hwnd;\n\tHWND familyCombobox;\n\tHWND styleCombobox;\n\tHWND sizeCombobox;\n\n\tstruct fontDialogParams *params;\n\n\tstruct fontCollection *fc;\n\n\tRECT sampleRect;\n\tHWND sampleBox;\n\n\t// we store the current selections in case an invalid string is typed in (partial or nonexistent or invalid number)\n\t// on OK, these are what are read\n\tLRESULT curFamily;\n\tLRESULT curStyle;\n\tdouble curSize;\n\n\t// these are finding the style that's closest to the previous one (these fields) when changing a font\n\tDWRITE_FONT_WEIGHT weight;\n\tDWRITE_FONT_STYLE style;\n\tDWRITE_FONT_STRETCH stretch;\n};\n\nstatic LRESULT cbAddString(HWND cb, const WCHAR *str)\n{\n\tLRESULT lr;\n\n\tlr = SendMessageW(cb, CB_ADDSTRING, 0, (LPARAM) str);\n\tif (lr == (LRESULT) CB_ERR || lr == (LRESULT) CB_ERRSPACE)\n\t\tlogLastError(L\"error adding item to combobox\");\n\treturn lr;\n}\n\nstatic LRESULT cbInsertString(HWND cb, const WCHAR *str, WPARAM pos)\n{\n\tLRESULT lr;\n\n\tlr = SendMessageW(cb, CB_INSERTSTRING, pos, (LPARAM) str);\n\tif (lr != (LRESULT) pos)\n\t\tlogLastError(L\"error inserting item to combobox\");\n\treturn lr;\n}\n\nstatic LRESULT cbGetItemData(HWND cb, WPARAM item)\n{\n\tLRESULT data;\n\n\tdata = SendMessageW(cb, CB_GETITEMDATA, item, 0);\n\tif (data == (LRESULT) CB_ERR)\n\t\tlogLastError(L\"error getting combobox item data for font dialog\");\n\treturn data;\n}\n\nstatic void cbSetItemData(HWND cb, WPARAM item, LPARAM data)\n{\n\tif (SendMessageW(cb, CB_SETITEMDATA, item, data) == (LRESULT) CB_ERR)\n\t\tlogLastError(L\"error setting combobox item data\");\n}\n\nstatic BOOL cbGetCurSel(HWND cb, LRESULT *sel)\n{\n\tLRESULT n;\n\n\tn = SendMessageW(cb, CB_GETCURSEL, 0, 0);\n\tif (n == (LRESULT) CB_ERR)\n\t\treturn FALSE;\n\tif (sel != NULL)\n\t\t*sel = n;\n\treturn TRUE;\n}\n\nstatic void cbSetCurSel(HWND cb, WPARAM item)\n{\n\tif (SendMessageW(cb, CB_SETCURSEL, item, 0) != (LRESULT) item)\n\t\tlogLastError(L\"error selecting combobox item\");\n}\n\nstatic LRESULT cbGetCount(HWND cb)\n{\n\tLRESULT n;\n\n\tn = SendMessageW(cb, CB_GETCOUNT, 0, 0);\n\tif (n == (LRESULT) CB_ERR)\n\t\tlogLastError(L\"error getting combobox item count\");\n\treturn n;\n}\n\nstatic void cbWipeAndReleaseData(HWND cb)\n{\n\tIUnknown *obj;\n\tLRESULT i, n;\n\n\tn = cbGetCount(cb);\n\tfor (i = 0; i < n; i++) {\n\t\tobj = (IUnknown *) cbGetItemData(cb, (WPARAM) i);\n\t\tobj->Release();\n\t}\n\tSendMessageW(cb, CB_RESETCONTENT, 0, 0);\n}\n\nstatic WCHAR *cbGetItemText(HWND cb, WPARAM item)\n{\n\tLRESULT len;\n\tWCHAR *text;\n\n\t// note: neither message includes the terminating L'\\0'\n\tlen = SendMessageW(cb, CB_GETLBTEXTLEN, item, 0);\n\tif (len == (LRESULT) CB_ERR)\n\t\tlogLastError(L\"error getting item text length from combobox\");\n\ttext = (WCHAR *) uiprivAlloc((len + 1) * sizeof (WCHAR), \"WCHAR[]\");\n\tif (SendMessageW(cb, CB_GETLBTEXT, item, (LPARAM) text) != len)\n\t\tlogLastError(L\"error getting item text from combobox\");\n\treturn text;\n}\n\nstatic BOOL cbTypeToSelect(HWND cb, LRESULT *posOut, BOOL restoreAfter)\n{\n\tWCHAR *text;\n\tLRESULT pos;\n\tDWORD selStart, selEnd;\n\n\t// start by saving the current selection as setting the item will change the selection\n\tSendMessageW(cb, CB_GETEDITSEL, (WPARAM) (&selStart), (LPARAM) (&selEnd));\n\ttext = windowText(cb);\n\tpos = SendMessageW(cb, CB_FINDSTRINGEXACT, (WPARAM) (-1), (LPARAM) text);\n\tif (pos == (LRESULT) CB_ERR) {\n\t\tuiprivFree(text);\n\t\treturn FALSE;\n\t}\n\tcbSetCurSel(cb, (WPARAM) pos);\n\tif (posOut != NULL)\n\t\t*posOut = pos;\n\tif (restoreAfter)\n\t\tif (SendMessageW(cb, WM_SETTEXT, 0, (LPARAM) text) != (LRESULT) TRUE)\n\t\t\tlogLastError(L\"error restoring old combobox text\");\n\tuiprivFree(text);\n\t// and restore the selection like above\n\t// TODO isn't there a 32-bit version of this\n\tif (SendMessageW(cb, CB_SETEDITSEL, 0, MAKELPARAM(selStart, selEnd)) != (LRESULT) TRUE)\n\t\tlogLastError(L\"error restoring combobox edit selection\");\n\treturn TRUE;\n}\n\nstatic void wipeStylesBox(struct fontDialog *f)\n{\n\tcbWipeAndReleaseData(f->styleCombobox);\n}\n\nstatic WCHAR *fontStyleName(struct fontCollection *fc, IDWriteFont *font)\n{\n\tIDWriteLocalizedStrings *str;\n\tWCHAR *wstr;\n\tHRESULT hr;\n\n\thr = font->GetFaceNames(&str);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error getting font style name for font dialog\", hr);\n\twstr = uiprivFontCollectionCorrectString(fc, str);\n\tstr->Release();\n\treturn wstr;\n}\n\nstatic void queueRedrawSampleText(struct fontDialog *f)\n{\n\t// TODO TRUE?\n\tinvalidateRect(f->sampleBox, NULL, TRUE);\n}\n\nstatic void styleChanged(struct fontDialog *f)\n{\n\tLRESULT pos;\n\tBOOL selected;\n\tIDWriteFont *font;\n\n\tselected = cbGetCurSel(f->styleCombobox, &pos);\n\tif (!selected)\t\t// on deselect, do nothing\n\t\treturn;\n\tf->curStyle = pos;\n\n\tfont = (IDWriteFont *) cbGetItemData(f->styleCombobox, (WPARAM) (f->curStyle));\n\t// these are for the nearest match when changing the family; see below\n\tf->weight = font->GetWeight();\n\tf->style = font->GetStyle();\n\tf->stretch = font->GetStretch();\n\n\tqueueRedrawSampleText(f);\n}\n\nstatic void styleEdited(struct fontDialog *f)\n{\n\tif (cbTypeToSelect(f->styleCombobox, &(f->curStyle), FALSE))\n\t\tstyleChanged(f);\n}\n\nstatic void familyChanged(struct fontDialog *f)\n{\n\tLRESULT pos;\n\tBOOL selected;\n\tIDWriteFontFamily *family;\n\tIDWriteFont *font, *matchFont;\n\tDWRITE_FONT_WEIGHT weight;\n\tDWRITE_FONT_STYLE style;\n\tDWRITE_FONT_STRETCH stretch;\n\tUINT32 i, n;\n\tUINT32 matching;\n\tWCHAR *label;\n\tHRESULT hr;\n\n\tselected = cbGetCurSel(f->familyCombobox, &pos);\n\tif (!selected)\t\t// on deselect, do nothing\n\t\treturn;\n\tf->curFamily = pos;\n\n\tfamily = (IDWriteFontFamily *) cbGetItemData(f->familyCombobox, (WPARAM) (f->curFamily));\n\n\t// for the nearest style match\n\t// when we select a new family, we want the nearest style to the previously selected one to be chosen\n\t// this is how the Choose Font sample does it\n\thr = family->GetFirstMatchingFont(\n\t\tf->weight,\n\t\tf->stretch,\n\t\tf->style,\n\t\t&matchFont);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error finding first matching font to previous style in font dialog\", hr);\n\t// we can't just compare pointers; a \"newly created\" object comes out\n\t// the Choose Font sample appears to do this instead\n\tweight = matchFont->GetWeight();\n\tstyle = matchFont->GetStyle();\n\tstretch = matchFont->GetStretch();\n\tmatchFont->Release();\n\n\t// TODO test mutliple streteches; all the fonts I have have only one stretch value?\n\twipeStylesBox(f);\n\tn = family->GetFontCount();\n\tmatching = 0;\t\t\t// a safe/suitable default just in case\n\tfor (i = 0; i < n; i++) {\n\t\thr = family->GetFont(i, &font);\n\t\tif (hr != S_OK)\n\t\t\tlogHRESULT(L\"error getting font for filling styles box\", hr);\n\t\tlabel = fontStyleName(f->fc, font);\n\t\tpos = cbAddString(f->styleCombobox, label);\n\t\tuiprivFree(label);\n\t\tcbSetItemData(f->styleCombobox, (WPARAM) pos, (LPARAM) font);\n\t\tif (font->GetWeight() == weight &&\n\t\t\tfont->GetStyle() == style &&\n\t\t\tfont->GetStretch() == stretch)\n\t\t\tmatching = i;\n\t}\n\n\t// and now, load the match\n\tcbSetCurSel(f->styleCombobox, (WPARAM) matching);\n\tstyleChanged(f);\n}\n\n// TODO search language variants like the sample does\nstatic void familyEdited(struct fontDialog *f)\n{\n\tif (cbTypeToSelect(f->familyCombobox, &(f->curFamily), FALSE))\n\t\tfamilyChanged(f);\n}\n\nstatic const struct {\n\tconst WCHAR *text;\n\tdouble value;\n} defaultSizes[] = {\n\t{ L\"8\", 8 },\n\t{ L\"9\", 9 },\n\t{ L\"10\", 10 },\n\t{ L\"11\", 11 },\n\t{ L\"12\", 12 },\n\t{ L\"14\", 14 },\n\t{ L\"16\", 16 },\n\t{ L\"18\", 18 },\n\t{ L\"20\", 20 },\n\t{ L\"22\", 22 },\n\t{ L\"24\", 24 },\n\t{ L\"26\", 26 },\n\t{ L\"28\", 28 },\n\t{ L\"36\", 36 },\n\t{ L\"48\", 48 },\n\t{ L\"72\", 72 },\n\t{ NULL, 0 },\n};\n\nstatic void sizeChanged(struct fontDialog *f)\n{\n\tLRESULT pos;\n\tBOOL selected;\n\n\tselected = cbGetCurSel(f->sizeCombobox, &pos);\n\tif (!selected)\t\t// on deselect, do nothing\n\t\treturn;\n\tf->curSize = defaultSizes[pos].value;\n\tqueueRedrawSampleText(f);\n}\n\nstatic void sizeEdited(struct fontDialog *f)\n{\n\tWCHAR *wsize;\n\tdouble size;\n\n\t// handle type-to-selection\n\tif (cbTypeToSelect(f->sizeCombobox, NULL, FALSE)) {\n\t\tsizeChanged(f);\n\t\treturn;\n\t}\n\t// selection not chosen, try to parse the typing\n\twsize = windowText(f->sizeCombobox);\n\t// this is what the Choose Font dialog does; it swallows errors while the real ChooseFont() is not lenient (and only checks on OK)\n\tsize = wcstod(wsize, NULL);\n\t// TODO free wsize? I forget already\n\tif (size <= 0)\t\t// don't change on invalid size\n\t\treturn;\n\tf->curSize = size;\n\tqueueRedrawSampleText(f);\n}\n\nstatic void fontDialogDrawSampleText(struct fontDialog *f, ID2D1RenderTarget *rt)\n{\n\tD2D1_COLOR_F color;\n\tD2D1_BRUSH_PROPERTIES props;\n\tID2D1SolidColorBrush *black;\n\tIDWriteFont *font;\n\tIDWriteLocalizedStrings *sampleStrings;\n\tBOOL exists;\n\tWCHAR *sample;\n\tWCHAR *family;\n\tIDWriteTextFormat *format;\n\tD2D1_RECT_F rect;\n\tHRESULT hr;\n\n\tcolor.r = 0.0;\n\tcolor.g = 0.0;\n\tcolor.b = 0.0;\n\tcolor.a = 1.0;\n\tZeroMemory(&props, sizeof (D2D1_BRUSH_PROPERTIES));\n\tprops.opacity = 1.0;\n\t// identity matrix\n\tprops.transform._11 = 1;\n\tprops.transform._22 = 1;\n\thr = rt->CreateSolidColorBrush(\n\t\t&color,\n\t\t&props,\n\t\t&black);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating solid brush\", hr);\n\n\tfont = (IDWriteFont *) cbGetItemData(f->styleCombobox, (WPARAM) f->curStyle);\n\thr = font->GetInformationalStrings(DWRITE_INFORMATIONAL_STRING_SAMPLE_TEXT, &sampleStrings, &exists);\n\tif (hr != S_OK)\n\t\texists = FALSE;\n\tif (exists) {\n\t\tsample = uiprivFontCollectionCorrectString(f->fc, sampleStrings);\n\t\tsampleStrings->Release();\n\t} else\n\t\tsample = (WCHAR *) L\"The quick brown fox jumps over the lazy dog.\";\t\t\t// TODO\n\n\t// DirectWrite doesn't allow creating a text format from a font; we need to get this ourselves\n\tfamily = cbGetItemText(f->familyCombobox, f->curFamily);\n\thr = dwfactory->CreateTextFormat(family,\n\t\tNULL,\n\t\tfont->GetWeight(),\n\t\tfont->GetStyle(),\n\t\tfont->GetStretch(),\n\t\t// typographic points are 1/72 inch; this parameter is 1/96 inch\n\t\t// fortunately Microsoft does this too, in https://msdn.microsoft.com/en-us/library/windows/desktop/dd371554%28v=vs.85%29.aspx\n\t\tf->curSize * (96.0 / 72.0),\n\t\t// see http://stackoverflow.com/questions/28397971/idwritefactorycreatetextformat-failing and https://msdn.microsoft.com/en-us/library/windows/desktop/dd368203.aspx\n\t\t// TODO use the current locale again?\n\t\tL\"\",\n\t\t&format);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating IDWriteTextFormat\", hr);\n\tuiprivFree(family);\n\n\trect.left = 0;\n\trect.top = 0;\n\trect.right = realGetSize(rt).width;\n\trect.bottom = realGetSize(rt).height;\n\trt->DrawText(sample, wcslen(sample),\n\t\tformat,\n\t\t&rect,\n\t\tblack,\n\t\t// TODO really?\n\t\tD2D1_DRAW_TEXT_OPTIONS_NONE,\n\t\tDWRITE_MEASURING_MODE_NATURAL);\n\n\tformat->Release();\n\tif (exists)\n\t\tuiprivFree(sample);\n\tblack->Release();\n}\n\nstatic LRESULT CALLBACK fontDialogSampleSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)\n{\n\tID2D1RenderTarget *rt;\n\tstruct fontDialog *f;\n\n\tswitch (uMsg) {\n\tcase msgD2DScratchPaint:\n\t\trt = (ID2D1RenderTarget *) lParam;\n\t\tf = (struct fontDialog *) dwRefData;\n\t\tfontDialogDrawSampleText(f, rt);\n\t\treturn 0;\n\tcase WM_NCDESTROY:\n\t\tif (RemoveWindowSubclass(hwnd, fontDialogSampleSubProc, uIdSubclass) == FALSE)\n\t\t\tlogLastError(L\"error removing font dialog sample text subclass\");\n\t\tbreak;\n\t}\n\treturn DefSubclassProc(hwnd, uMsg, wParam, lParam);\n}\n\nstatic void setupInitialFontDialogState(struct fontDialog *f)\n{\n\tWCHAR wsize[512];\t\t// this should be way more than enough\n\tLRESULT pos;\n\n\t// first let's load the size\n\t// the real font dialog:\n\t// - if the chosen font size is in the list, it selects that item AND makes it topmost\n\t// - if the chosen font size is not in the list, don't bother\n\t// we'll simulate it by setting the text to a %f representation, then pretending as if it was entered\n\t// TODO is 512 the correct number to pass to _snwprintf()?\n\t// TODO will this revert to scientific notation?\n\t_snwprintf(wsize, 512, L\"%g\", f->params->size);\n\t// TODO make this a setWindowText()\n\tif (SendMessageW(f->sizeCombobox, WM_SETTEXT, 0, (LPARAM) wsize) != (LRESULT) TRUE)\n\t\tlogLastError(L\"error setting size combobox to initial font size\");\n\tsizeEdited(f);\n\tif (cbGetCurSel(f->sizeCombobox, &pos))\n\t\tif (SendMessageW(f->sizeCombobox, CB_SETTOPINDEX, (WPARAM) pos, 0) != 0)\n\t\t\tlogLastError(L\"error making chosen size topmost in the size combobox\");\n\n\t// now we set the family and style\n\t// we do this by first setting the previous style attributes, then simulating a font entered\n\tf->weight = f->params->font->GetWeight();\n\tf->style = f->params->font->GetStyle();\n\tf->stretch = f->params->font->GetStretch();\n\tif (SendMessageW(f->familyCombobox, WM_SETTEXT, 0, (LPARAM) (f->params->familyName)) != (LRESULT) TRUE)\n\t\tlogLastError(L\"error setting family combobox to initial font family\");\n\tfamilyEdited(f);\n}\n\nstatic struct fontDialog *beginFontDialog(HWND hwnd, LPARAM lParam)\n{\n\tstruct fontDialog *f;\n\tUINT32 i, nFamilies;\n\tIDWriteFontFamily *family;\n\tWCHAR *wname;\n\tLRESULT pos;\n\tHWND samplePlacement;\n\tHRESULT hr;\n\n\tf = uiprivNew(struct fontDialog);\n\tf->hwnd = hwnd;\n\tf->params = (struct fontDialogParams *) lParam;\n\n\tf->familyCombobox = getDlgItem(f->hwnd, rcFontFamilyCombobox);\n\tf->styleCombobox = getDlgItem(f->hwnd, rcFontStyleCombobox);\n\tf->sizeCombobox = getDlgItem(f->hwnd, rcFontSizeCombobox);\n\n\tf->fc = uiprivLoadFontCollection();\n\tnFamilies = f->fc->fonts->GetFontFamilyCount();\n\tfor (i = 0; i < nFamilies; i++) {\n\t\thr = f->fc->fonts->GetFontFamily(i, &family);\n\t\tif (hr != S_OK)\n\t\t\tlogHRESULT(L\"error getting font family\", hr);\n\t\twname = uiprivFontCollectionFamilyName(f->fc, family);\n\t\tpos = cbAddString(f->familyCombobox, wname);\n\t\tuiprivFree(wname);\n\t\tcbSetItemData(f->familyCombobox, (WPARAM) pos, (LPARAM) family);\n\t}\n\n\tfor (i = 0; defaultSizes[i].text != NULL; i++)\n\t\tcbInsertString(f->sizeCombobox, defaultSizes[i].text, (WPARAM) i);\n\n\tsamplePlacement = getDlgItem(f->hwnd, rcFontSamplePlacement);\n\tuiWindowsEnsureGetWindowRect(samplePlacement, &(f->sampleRect));\n\tmapWindowRect(NULL, f->hwnd, &(f->sampleRect));\n\tuiWindowsEnsureDestroyWindow(samplePlacement);\n\tf->sampleBox = newD2DScratch(f->hwnd, &(f->sampleRect), (HMENU) rcFontSamplePlacement, fontDialogSampleSubProc, (DWORD_PTR) f);\n\n\tsetupInitialFontDialogState(f);\n\treturn f;\n}\n\nstatic void endFontDialog(struct fontDialog *f, INT_PTR code)\n{\n\twipeStylesBox(f);\n\tcbWipeAndReleaseData(f->familyCombobox);\n\tuiprivFontCollectionFree(f->fc);\n\tif (EndDialog(f->hwnd, code) == 0)\n\t\tlogLastError(L\"error ending font dialog\");\n\tuiprivFree(f);\n}\n\nstatic INT_PTR tryFinishDialog(struct fontDialog *f, WPARAM wParam)\n{\n\tIDWriteFontFamily *family;\n\n\t// cancelling\n\tif (LOWORD(wParam) != IDOK) {\n\t\tendFontDialog(f, 1);\n\t\treturn TRUE;\n\t}\n\n\t// OK\n\tuiprivDestroyFontDialogParams(f->params);\n\tf->params->font = (IDWriteFont *) cbGetItemData(f->styleCombobox, f->curStyle);\n\t// we need to save font from being destroyed with the combobox\n\tf->params->font->AddRef();\n\tf->params->size = f->curSize;\n\tfamily = (IDWriteFontFamily *) cbGetItemData(f->familyCombobox, f->curFamily);\n\tf->params->familyName = uiprivFontCollectionFamilyName(f->fc, family);\n\tf->params->styleName = fontStyleName(f->fc, f->params->font);\n\tendFontDialog(f, 2);\n\treturn TRUE;\n}\n\nstatic INT_PTR CALLBACK fontDialogDlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)\n{\n\tstruct fontDialog *f;\n\n\tf = (struct fontDialog *) GetWindowLongPtrW(hwnd, DWLP_USER);\n\tif (f == NULL) {\n\t\tif (uMsg == WM_INITDIALOG) {\n\t\t\tf = beginFontDialog(hwnd, lParam);\n\t\t\tSetWindowLongPtrW(hwnd, DWLP_USER, (LONG_PTR) f);\n\t\t\treturn TRUE;\n\t\t}\n\t\treturn FALSE;\n\t}\n\n\tswitch (uMsg) {\n\tcase WM_COMMAND:\n\t\tSetWindowLongPtrW(f->hwnd, DWLP_MSGRESULT, 0);\t\t// just in case\n\t\tswitch (LOWORD(wParam)) {\n\t\tcase IDOK:\n\t\tcase IDCANCEL:\n\t\t\tif (HIWORD(wParam) != BN_CLICKED)\n\t\t\t\treturn FALSE;\n\t\t\treturn tryFinishDialog(f, wParam);\n\t\tcase rcFontFamilyCombobox:\n\t\t\tif (HIWORD(wParam) == CBN_SELCHANGE) {\n\t\t\t\tfamilyChanged(f);\n\t\t\t\treturn TRUE;\n\t\t\t}\n\t\t\tif (HIWORD(wParam) == CBN_EDITCHANGE) {\n\t\t\t\tfamilyEdited(f);\n\t\t\t\treturn TRUE;\n\t\t\t}\n\t\t\treturn FALSE;\n\t\tcase rcFontStyleCombobox:\n\t\t\tif (HIWORD(wParam) == CBN_SELCHANGE) {\n\t\t\t\tstyleChanged(f);\n\t\t\t\treturn TRUE;\n\t\t\t}\n\t\t\tif (HIWORD(wParam) == CBN_EDITCHANGE) {\n\t\t\t\tstyleEdited(f);\n\t\t\t\treturn TRUE;\n\t\t\t}\n\t\t\treturn FALSE;\n\t\tcase rcFontSizeCombobox:\n\t\t\tif (HIWORD(wParam) == CBN_SELCHANGE) {\n\t\t\t\tsizeChanged(f);\n\t\t\t\treturn TRUE;\n\t\t\t}\n\t\t\tif (HIWORD(wParam) == CBN_EDITCHANGE) {\n\t\t\t\tsizeEdited(f);\n\t\t\t\treturn TRUE;\n\t\t\t}\n\t\t\treturn FALSE;\n\t\t}\n\t\treturn FALSE;\n\t}\n\treturn FALSE;\n}\n\n// because Windows doesn't really support resources in static libraries, we have to embed this directly; oh well\n/*\n// this is for our custom DirectWrite-based font dialog (see fontdialog.cpp)\n// this is based on the \"New Font Dialog with Syslink\" in Microsoft's font.dlg\n// LONGTERM look at localization\n// LONGTERM make it look tighter and nicer like the real one, including the actual heights of the font family and style comboboxes\nrcFontDialog DIALOGEX 13, 54, 243, 200\nSTYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_3DLOOK\nCAPTION \"Font\"\nFONT 9, \"Segoe UI\"\nBEGIN\n\tLTEXT\t\t\"&Font:\", -1, 7, 7, 98, 9\n\tCOMBOBOX\trcFontFamilyCombobox, 7, 16, 98, 76,\n\t\tCBS_SIMPLE | CBS_AUTOHSCROLL | CBS_DISABLENOSCROLL |\n\t\tCBS_SORT | WS_VSCROLL | WS_TABSTOP | CBS_HASSTRINGS\n\n\tLTEXT\t\t\"Font st&yle:\", -1, 114, 7, 74, 9\n\tCOMBOBOX\trcFontStyleCombobox, 114, 16, 74, 76,\n\t\tCBS_SIMPLE | CBS_AUTOHSCROLL | CBS_DISABLENOSCROLL |\n\t\tWS_VSCROLL | WS_TABSTOP | CBS_HASSTRINGS\n\n\tLTEXT\t\t\"&Size:\", -1, 198, 7, 36, 9\n\tCOMBOBOX\trcFontSizeCombobox, 198, 16, 36, 76,\n\t\tCBS_SIMPLE | CBS_AUTOHSCROLL | CBS_DISABLENOSCROLL |\n\t\tCBS_SORT | WS_VSCROLL | WS_TABSTOP | CBS_HASSTRINGS\n\n\tGROUPBOX\t\t\"Sample\", -1, 7, 97, 227, 70, WS_GROUP\n\tCTEXT\t\t\t\"AaBbYyZz\", rcFontSamplePlacement, 9, 106, 224, 60, SS_NOPREFIX | NOT WS_VISIBLE\n\n\tDEFPUSHBUTTON\t\"OK\", IDOK, 141, 181, 45, 14, WS_GROUP\n\tPUSHBUTTON\t\t\"Cancel\", IDCANCEL, 190, 181, 45, 14, WS_GROUP\nEND\n*/\nstatic const uint8_t data_rcFontDialog[] = {\n\t0x01, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0xC4, 0x00, 0xC8, 0x80,\n\t0x0A, 0x00, 0x0D, 0x00, 0x36, 0x00, 0xF3, 0x00,\n\t0xC8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00,\n\t0x6F, 0x00, 0x6E, 0x00, 0x74, 0x00, 0x00, 0x00,\n\t0x09, 0x00, 0x00, 0x00, 0x00, 0x01, 0x53, 0x00,\n\t0x65, 0x00, 0x67, 0x00, 0x6F, 0x00, 0x65, 0x00,\n\t0x20, 0x00, 0x55, 0x00, 0x49, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x02, 0x50, 0x07, 0x00, 0x07, 0x00,\n\t0x62, 0x00, 0x09, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,\n\t0xFF, 0xFF, 0x82, 0x00, 0x26, 0x00, 0x46, 0x00,\n\t0x6F, 0x00, 0x6E, 0x00, 0x74, 0x00, 0x3A, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x41, 0x0B, 0x21, 0x50,\n\t0x07, 0x00, 0x10, 0x00, 0x62, 0x00, 0x4C, 0x00,\n\t0xE8, 0x03, 0x00, 0x00, 0xFF, 0xFF, 0x85, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50,\n\t0x72, 0x00, 0x07, 0x00, 0x4A, 0x00, 0x09, 0x00,\n\t0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x82, 0x00,\n\t0x46, 0x00, 0x6F, 0x00, 0x6E, 0x00, 0x74, 0x00,\n\t0x20, 0x00, 0x73, 0x00, 0x74, 0x00, 0x26, 0x00,\n\t0x79, 0x00, 0x6C, 0x00, 0x65, 0x00, 0x3A, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x41, 0x0A, 0x21, 0x50,\n\t0x72, 0x00, 0x10, 0x00, 0x4A, 0x00, 0x4C, 0x00,\n\t0xE9, 0x03, 0x00, 0x00, 0xFF, 0xFF, 0x85, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x50,\n\t0xC6, 0x00, 0x07, 0x00, 0x24, 0x00, 0x09, 0x00,\n\t0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x82, 0x00,\n\t0x26, 0x00, 0x53, 0x00, 0x69, 0x00, 0x7A, 0x00,\n\t0x65, 0x00, 0x3A, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x41, 0x0B, 0x21, 0x50, 0xC6, 0x00, 0x10, 0x00,\n\t0x24, 0x00, 0x4C, 0x00, 0xEA, 0x03, 0x00, 0x00,\n\t0xFF, 0xFF, 0x85, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x07, 0x00, 0x02, 0x50, 0x07, 0x00, 0x61, 0x00,\n\t0xE3, 0x00, 0x46, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,\n\t0xFF, 0xFF, 0x80, 0x00, 0x53, 0x00, 0x61, 0x00,\n\t0x6D, 0x00, 0x70, 0x00, 0x6C, 0x00, 0x65, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x02, 0x40,\n\t0x09, 0x00, 0x6A, 0x00, 0xE0, 0x00, 0x3C, 0x00,\n\t0xEB, 0x03, 0x00, 0x00, 0xFF, 0xFF, 0x82, 0x00,\n\t0x41, 0x00, 0x61, 0x00, 0x42, 0x00, 0x62, 0x00,\n\t0x59, 0x00, 0x79, 0x00, 0x5A, 0x00, 0x7A, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x50,\n\t0x8D, 0x00, 0xB5, 0x00, 0x2D, 0x00, 0x0E, 0x00,\n\t0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00,\n\t0x4F, 0x00, 0x4B, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x03, 0x50, 0xBE, 0x00, 0xB5, 0x00,\n\t0x2D, 0x00, 0x0E, 0x00, 0x02, 0x00, 0x00, 0x00,\n\t0xFF, 0xFF, 0x80, 0x00, 0x43, 0x00, 0x61, 0x00,\n\t0x6E, 0x00, 0x63, 0x00, 0x65, 0x00, 0x6C, 0x00,\n\t0x00, 0x00, 0x00, 0x00, \n};\nstatic_assert(ARRAYSIZE(data_rcFontDialog) == 476, \"wrong size for resource rcFontDialog\");\n\nBOOL uiprivShowFontDialog(HWND parent, struct fontDialogParams *params)\n{\n\tswitch (DialogBoxIndirectParamW(hInstance, (const DLGTEMPLATE *) data_rcFontDialog, parent, fontDialogDlgProc, (LPARAM) params)) {\n\tcase 1:\t\t\t// cancel\n\t\treturn FALSE;\n\tcase 2:\t\t\t// ok\n\t\t// make the compiler happy by putting the return after the switch\n\t\tbreak;\n\tdefault:\n\t\tlogLastError(L\"error running font dialog\");\n\t}\n\treturn TRUE;\n}\n\nstatic IDWriteFontFamily *tryFindFamily(IDWriteFontCollection *fc, const WCHAR *name)\n{\n\tUINT32 index;\n\tBOOL exists;\n\tIDWriteFontFamily *family;\n\tHRESULT hr;\n\n\thr = fc->FindFamilyName(name, &index, &exists);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error finding font family for font dialog\", hr);\n\tif (!exists)\n\t\treturn NULL;\n\thr = fc->GetFontFamily(index, &family);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error extracting found font family for font dialog\", hr);\n\treturn family;\n}\n\nvoid uiprivLoadInitialFontDialogParams(struct fontDialogParams *params)\n{\n\tstruct fontCollection *fc;\n\tIDWriteFontFamily *family;\n\tIDWriteFont *font;\n\tHRESULT hr;\n\n\t// Our preferred font is Arial 10 Regular.\n\t// 10 comes from the official font dialog.\n\t// Arial Regular is a reasonable, if arbitrary, default; it's similar to the defaults on other systems.\n\t// 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.\n\n\t// 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.\n\tfc = uiprivLoadFontCollection();\n\tfamily = tryFindFamily(fc->fonts, L\"Arial\");\n\tif (family == NULL) {\n\t\tfamily = tryFindFamily(fc->fonts, L\"Helvetica\");\n\t\tif (family == NULL) {\n\t\t\tfamily = tryFindFamily(fc->fonts, L\"MS Sans Serif\");\n\t\t\tif (family == NULL) {\n\t\t\t\thr = fc->fonts->GetFontFamily(0, &family);\n\t\t\t\tif (hr != S_OK)\n\t\t\t\t\tlogHRESULT(L\"error getting first font out of font collection (worst case scenario)\", hr);\n\t\t\t}\n\t\t}\n\t}\n\n\t// next part is simple: just get the closest match to regular\n\thr = family->GetFirstMatchingFont(\n\t\tDWRITE_FONT_WEIGHT_NORMAL,\n\t\tDWRITE_FONT_STRETCH_NORMAL,\n\t\tDWRITE_FONT_STYLE_NORMAL,\n\t\t&font);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error getting Regular font from Arial\", hr);\n\n\tparams->font = font;\n\tparams->size = 10;\n\tparams->familyName = uiprivFontCollectionFamilyName(fc, family);\n\tparams->styleName = fontStyleName(fc, font);\n\n\t// don't release font; we still need it\n\tfamily->Release();\n\tuiprivFontCollectionFree(fc);\n}\n\nvoid uiprivDestroyFontDialogParams(struct fontDialogParams *params)\n{\n\tparams->font->Release();\n\tuiprivFree(params->familyName);\n\tuiprivFree(params->styleName);\n}\n\nWCHAR *uiprivFontDialogParamsToString(struct fontDialogParams *params)\n{\n\tWCHAR *text;\n\n\t// TODO dynamically allocate\n\ttext = (WCHAR *) uiprivAlloc(512 * sizeof (WCHAR), \"WCHAR[]\");\n\t_snwprintf(text, 512, L\"%s %s %g\",\n\t\tparams->familyName,\n\t\tparams->styleName,\n\t\tparams->size);\n\treturn text;\n}\n"
  },
  {
    "path": "windows/fontmatch.cpp",
    "content": "// 11 march 2018\n#include \"uipriv_windows.hpp\"\n#include \"attrstr.hpp\"\n\n// 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)\nstatic std::map<uiTextItalic, DWRITE_FONT_STYLE> dwriteItalics = {\n\t{ uiTextItalicNormal, DWRITE_FONT_STYLE_NORMAL },\n\t{ uiTextItalicOblique, DWRITE_FONT_STYLE_OBLIQUE },\n\t{ uiTextItalicItalic, DWRITE_FONT_STYLE_ITALIC },\n};\n\n// 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)\nstatic std::map<uiTextStretch, DWRITE_FONT_STRETCH> dwriteStretches = {\n\t{ uiTextStretchUltraCondensed, DWRITE_FONT_STRETCH_ULTRA_CONDENSED },\n\t{ uiTextStretchExtraCondensed, DWRITE_FONT_STRETCH_EXTRA_CONDENSED },\n\t{ uiTextStretchCondensed, DWRITE_FONT_STRETCH_CONDENSED },\n\t{ uiTextStretchSemiCondensed, DWRITE_FONT_STRETCH_SEMI_CONDENSED },\n\t{ uiTextStretchNormal, DWRITE_FONT_STRETCH_NORMAL },\n\t{ uiTextStretchSemiExpanded, DWRITE_FONT_STRETCH_SEMI_EXPANDED },\n\t{ uiTextStretchExpanded, DWRITE_FONT_STRETCH_EXPANDED },\n\t{ uiTextStretchExtraExpanded, DWRITE_FONT_STRETCH_EXTRA_EXPANDED },\n\t{ uiTextStretchUltraExpanded, DWRITE_FONT_STRETCH_ULTRA_EXPANDED },\n};\n\n// for the most part, DirectWrite weights correlate to ours\n// the differences:\n// - Minimum — libui: 0, DirectWrite: 1\n// - Maximum — libui: 1000, DirectWrite: 999\n// 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)\nDWRITE_FONT_WEIGHT uiprivWeightToDWriteWeight(uiTextWeight w)\n{\n\treturn (DWRITE_FONT_WEIGHT) w;\n}\n\nDWRITE_FONT_STYLE uiprivItalicToDWriteStyle(uiTextItalic i)\n{\n\treturn dwriteItalics[i];\n}\n\nDWRITE_FONT_STRETCH uiprivStretchToDWriteStretch(uiTextStretch s)\n{\n\treturn dwriteStretches[s];\n}\n\nvoid uiprivFontDescriptorFromIDWriteFont(IDWriteFont *font, uiFontDescriptor *uidesc)\n{\n\tDWRITE_FONT_STYLE dwitalic;\n\tDWRITE_FONT_STRETCH dwstretch;\n\n\tdwitalic = font->GetStyle();\n\t// TODO reverse the above misalignment if it is corrected\n\tuidesc->Weight = (uiTextWeight) (font->GetWeight());\n\tdwstretch = font->GetStretch();\n\n\tfor (uidesc->Italic = uiTextItalicNormal; uidesc->Italic < uiTextItalicItalic; uidesc->Italic++)\n\t\tif (dwriteItalics[uidesc->Italic] == dwitalic)\n\t\t\tbreak;\n\tfor (uidesc->Stretch = uiTextStretchUltraCondensed; uidesc->Stretch < uiTextStretchUltraExpanded; uidesc->Stretch++)\n\t\tif (dwriteStretches[uidesc->Stretch] == dwstretch)\n\t\t\tbreak;\n}\n"
  },
  {
    "path": "windows/form.cpp",
    "content": "// 8 june 2016\n#include \"uipriv_windows.hpp\"\n\nstruct formChild {\n\tuiControl *c;\n\tHWND label;\n\tint stretchy;\n\tint height;\n};\n\nstruct uiForm {\n\tuiWindowsControl c;\n\tHWND hwnd;\n\tstd::vector<struct formChild> *controls;\n\tint padded;\n};\n\nstatic void formPadding(uiForm *f, int *xpadding, int *ypadding)\n{\n\tuiWindowsSizing sizing;\n\n\t*xpadding = 0;\n\t*ypadding = 0;\n\tif (f->padded) {\n\t\tuiWindowsGetSizing(f->hwnd, &sizing);\n\t\tuiWindowsSizingStandardPadding(&sizing, xpadding, ypadding);\n\t}\n}\n\n// via http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing\n#define labelHeight 8\n#define labelYOffset 3\n\nstatic void formRelayout(uiForm *f)\n{\n\tRECT r;\n\tint x, y, width, height;\n\tint xpadding, ypadding;\n\tint nStretchy;\n\tint labelwid, stretchyht;\n\tint thiswid;\n\tint i;\n\tint minimumWidth, minimumHeight;\n\tuiWindowsSizing sizing;\n\tint labelht, labelyoff;\n\tint nVisible;\n\n\tif (f->controls->size() == 0)\n\t\treturn;\n\n\tuiWindowsEnsureGetClientRect(f->hwnd, &r);\n\tx = r.left;\n\ty = r.top;\n\twidth = r.right - r.left;\n\theight = r.bottom - r.top;\n\n\t// 0) get this Form's padding\n\tformPadding(f, &xpadding, &ypadding);\n\n\t// 1) get width of labels and height of non-stretchy controls\n\t// this will tell us how much space will be left for controls\n\tlabelwid = 0;\n\tstretchyht = height;\n\tnStretchy = 0;\n\tnVisible = 0;\n\tfor (struct formChild &fc : *(f->controls)) {\n\t\tif (!uiControlVisible(fc.c)) {\n\t\t\tShowWindow(fc.label, SW_HIDE);\n\t\t\tcontinue;\n\t\t}\n\t\tShowWindow(fc.label, SW_SHOW);\n\t\tnVisible++;\n\t\tthiswid = uiWindowsWindowTextWidth(fc.label);\n\t\tif (labelwid < thiswid)\n\t\t\tlabelwid = thiswid;\n\t\tif (fc.stretchy) {\n\t\t\tnStretchy++;\n\t\t\tcontinue;\n\t\t}\n\t\tuiWindowsControlMinimumSize(uiWindowsControl(fc.c), &minimumWidth, &minimumHeight);\n\t\tfc.height = minimumHeight;\n\t\tstretchyht -= minimumHeight;\n\t}\n\tif (nVisible == 0)\t\t// nothing to do\n\t\treturn;\n\n\t// 2) inset the available rect by the needed padding\n\twidth -= xpadding;\n\theight -= (nVisible - 1) * ypadding;\n\tstretchyht -= (nVisible - 1) * ypadding;\n\n\t// 3) now get the width of controls and the height of stretchy controls\n\twidth -= labelwid;\n\tif (nStretchy != 0) {\n\t\tstretchyht /= nStretchy;\n\t\tfor (struct formChild &fc : *(f->controls)) {\n\t\t\tif (!uiControlVisible(fc.c))\n\t\t\t\tcontinue;\n\t\t\tif (fc.stretchy)\n\t\t\t\tfc.height = stretchyht;\n\t\t}\n\t}\n\n\t// 4) get the y offset\n\tlabelyoff = labelYOffset;\n\tuiWindowsGetSizing(f->hwnd, &sizing);\n\tuiWindowsSizingDlgUnitsToPixels(&sizing, NULL, &labelyoff);\n\n\t// 5) now we can position controls\n\t// first, make relative to the top-left corner of the container\n\t// also prefer left alignment on Windows\n\tx = labelwid + xpadding;\n\ty = 0;\n\tfor (const struct formChild &fc : *(f->controls)) {\n\t\tif (!uiControlVisible(fc.c))\n\t\t\tcontinue;\n\t\tlabelht = labelHeight;\n\t\tuiWindowsGetSizing(f->hwnd, &sizing);\n\t\tuiWindowsSizingDlgUnitsToPixels(&sizing, NULL, &labelht);\n\t\tuiWindowsEnsureMoveWindowDuringResize(fc.label, 0, y + labelyoff - sizing.InternalLeading, labelwid, labelht);\n\t\tuiWindowsEnsureMoveWindowDuringResize((HWND) uiControlHandle(fc.c), x, y, width, fc.height);\n\t\ty += fc.height + ypadding;\n\t}\n}\n\nstatic void uiFormDestroy(uiControl *c)\n{\n\tuiForm *f = uiForm(c);\n\n\tfor (const struct formChild &fc : *(f->controls)) {\n\t\tuiControlSetParent(fc.c, NULL);\n\t\tuiControlDestroy(fc.c);\n\t\tuiWindowsEnsureDestroyWindow(fc.label);\n\t}\n\tdelete f->controls;\n\tuiWindowsEnsureDestroyWindow(f->hwnd);\n\tuiFreeControl(uiControl(f));\n}\n\nuiWindowsControlDefaultHandle(uiForm)\nuiWindowsControlDefaultParent(uiForm)\nuiWindowsControlDefaultSetParent(uiForm)\nuiWindowsControlDefaultToplevel(uiForm)\nuiWindowsControlDefaultVisible(uiForm)\nuiWindowsControlDefaultShow(uiForm)\nuiWindowsControlDefaultHide(uiForm)\nuiWindowsControlDefaultEnabled(uiForm)\nuiWindowsControlDefaultEnable(uiForm)\nuiWindowsControlDefaultDisable(uiForm)\n\nstatic void uiFormSyncEnableState(uiWindowsControl *c, int enabled)\n{\n\tuiForm *f = uiForm(c);\n\n\tif (uiWindowsShouldStopSyncEnableState(uiWindowsControl(f), enabled))\n\t\treturn;\n\tfor (const struct formChild &fc : *(f->controls))\n\t\tuiWindowsControlSyncEnableState(uiWindowsControl(fc.c), enabled);\n}\n\nuiWindowsControlDefaultSetParentHWND(uiForm)\n\nstatic void uiFormMinimumSize(uiWindowsControl *c, int *width, int *height)\n{\n\tuiForm *f = uiForm(c);\n\tint xpadding, ypadding;\n\tint nStretchy;\n\t// these two contain the largest minimum width and height of all stretchy controls in the form\n\t// all stretchy controls will use this value to determine the final minimum size\n\tint maxLabelWidth, maxControlWidth;\n\tint maxStretchyHeight;\n\tint labelwid;\n\tint i;\n\tint minimumWidth, minimumHeight;\n\tint nVisible;\n\tuiWindowsSizing sizing;\n\n\t*width = 0;\n\t*height = 0;\n\tif (f->controls->size() == 0)\n\t\treturn;\n\n\t// 0) get this Form's padding\n\tformPadding(f, &xpadding, &ypadding);\n\n\t// 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\n\t// we still add in like direction of stretchy controls\n\tnStretchy = 0;\n\tmaxLabelWidth = 0;\n\tmaxControlWidth = 0;\n\tmaxStretchyHeight = 0;\n\tnVisible = 0;\n\tfor (const struct formChild &fc : *(f->controls)) {\n\t\tif (!uiControlVisible(fc.c))\n\t\t\tcontinue;\n\t\tnVisible++;\n\t\tlabelwid = uiWindowsWindowTextWidth(fc.label);\n\t\tif (maxLabelWidth < labelwid)\n\t\t\tmaxLabelWidth = labelwid;\n\t\tuiWindowsControlMinimumSize(uiWindowsControl(fc.c), &minimumWidth, &minimumHeight);\n\t\tif (fc.stretchy) {\n\t\t\tnStretchy++;\n\t\t\tif (maxStretchyHeight < minimumHeight)\n\t\t\t\tmaxStretchyHeight = minimumHeight;\n\t\t}\n\t\tif (maxControlWidth < minimumWidth)\n\t\t\tmaxControlWidth = minimumWidth;\n\t\tif (!fc.stretchy)\n\t\t\t*height += minimumHeight;\n\t}\n\tif (nVisible == 0)\t\t// nothing to show; return 0x0\n\t\treturn;\n\t*width += maxLabelWidth + maxControlWidth;\n\n\t// 2) outset the desired rect with the needed padding\n\t*width += xpadding;\n\t*height += (nVisible - 1) * ypadding;\n\n\t// 3) and now we can add in stretchy controls\n\t*height += nStretchy * maxStretchyHeight;\n}\n\nstatic void uiFormMinimumSizeChanged(uiWindowsControl *c)\n{\n\tuiForm *f = uiForm(c);\n\n\tif (uiWindowsControlTooSmall(uiWindowsControl(f))) {\n\t\tuiWindowsControlContinueMinimumSizeChanged(uiWindowsControl(f));\n\t\treturn;\n\t}\n\tformRelayout(f);\n}\n\nuiWindowsControlDefaultLayoutRect(uiForm)\nuiWindowsControlDefaultAssignControlIDZOrder(uiForm)\n\nstatic void uiFormChildVisibilityChanged(uiWindowsControl *c)\n{\n\t// TODO eliminate the redundancy\n\tuiWindowsControlMinimumSizeChanged(c);\n}\n\nstatic void formArrangeChildren(uiForm *f)\n{\n\tLONG_PTR controlID;\n\tHWND insertAfter;\n\tint i;\n\n\tcontrolID = 100;\n\tinsertAfter = NULL;\n\tfor (const struct formChild &fc : *(f->controls)) {\n\t\t// TODO assign label ID and z-order\n\t\tuiWindowsControlAssignControlIDZOrder(uiWindowsControl(fc.c), &controlID, &insertAfter);\n\t}\n}\n\nvoid uiFormAppend(uiForm *f, const char *label, uiControl *c, int stretchy)\n{\n\tstruct formChild fc;\n\tWCHAR *wlabel;\n\n\tfc.c = c;\n\twlabel = toUTF16(label);\n\tfc.label = uiWindowsEnsureCreateControlHWND(0,\n\t\tL\"STATIC\", wlabel,\n\t\tSS_LEFT | SS_NOPREFIX,\n\t\thInstance, NULL,\n\t\tTRUE);\n\tuiprivFree(wlabel);\n\tuiWindowsEnsureSetParentHWND(fc.label, f->hwnd);\n\tfc.stretchy = stretchy;\n\tuiControlSetParent(fc.c, uiControl(f));\n\tuiWindowsControlSetParentHWND(uiWindowsControl(fc.c), f->hwnd);\n\tf->controls->push_back(fc);\n\tformArrangeChildren(f);\n\tuiWindowsControlMinimumSizeChanged(uiWindowsControl(f));\n}\n\nvoid uiFormDelete(uiForm *f, int index)\n{\n\tstruct formChild fc;\n\n\tfc = (*(f->controls))[index];\n\tuiControlSetParent(fc.c, NULL);\n\tuiWindowsControlSetParentHWND(uiWindowsControl(fc.c), NULL);\n\tuiWindowsEnsureDestroyWindow(fc.label);\n\tf->controls->erase(f->controls->begin() + index);\n\tformArrangeChildren(f);\n\tuiWindowsControlMinimumSizeChanged(uiWindowsControl(f));\n}\n\nint uiFormPadded(uiForm *f)\n{\n\treturn f->padded;\n}\n\nvoid uiFormSetPadded(uiForm *f, int padded)\n{\n\tf->padded = padded;\n\tuiWindowsControlMinimumSizeChanged(uiWindowsControl(f));\n}\n\nstatic void onResize(uiWindowsControl *c)\n{\n\tformRelayout(uiForm(c));\n}\n\nuiForm *uiNewForm(void)\n{\n\tuiForm *f;\n\n\tuiWindowsNewControl(uiForm, f);\n\n\tf->hwnd = uiWindowsMakeContainer(uiWindowsControl(f), onResize);\n\n\tf->controls = new std::vector<struct formChild>;\n\n\treturn f;\n}\n"
  },
  {
    "path": "windows/graphemes.cpp",
    "content": "// 25 may 2016\n#include \"uipriv_windows.hpp\"\n#include \"attrstr.hpp\"\n\n// 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).\n// 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.\n// Let's just deal with the CharNextW() bug.\n\nint uiprivGraphemesTakesUTF16(void)\n{\n\treturn 1;\n}\n\nuiprivGraphemes *uiprivNewGraphemes(void *s, size_t len)\n{\n\tuiprivGraphemes *g;\n\tWCHAR *str;\n\tsize_t *pPTG, *pGTP;\n\n\tg = uiprivNew(uiprivGraphemes);\n\n\tg->len = 0;\n\tstr = (WCHAR *) s;\n\twhile (*str != L'\\0') {\n\t\tg->len++;\n\t\tstr = CharNextW(str);\n\t\t// no need to worry about surrogates if we're just counting\n\t}\n\n\tg->pointsToGraphemes = (size_t *) uiprivAlloc((len + 1) * sizeof (size_t), \"size_t[] (graphemes)\");\n\tg->graphemesToPoints = (size_t *) uiprivAlloc((g->len + 1) * sizeof (size_t), \"size_t[] (graphemes)\");\n\n\tpPTG = g->pointsToGraphemes;\n\tpGTP = g->graphemesToPoints;\n\tstr = (WCHAR *) s;\n\twhile (*str != L'\\0') {\n\t\tWCHAR *next, *p;\n\t\tptrdiff_t nextoff;\n\n\t\t// 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\n\t\tnextoff = 0;\n\t\tif (IS_HIGH_SURROGATE(*str))\n\t\t\tnextoff = 1;\n\t\tnext = CharNextW(str + nextoff);\n\t\tif (IS_LOW_SURROGATE(*next))\n\t\t\tnext--;\n\n\t\t*pGTP = pPTG - g->pointsToGraphemes;\n\t\tfor (p = str; p < next; p++)\n\t\t\t*pPTG++ = pGTP - g->graphemesToPoints;\n\t\tpGTP++;\n\n\t\tstr = next;\n\t}\n\t// and handle the last item for the end of the string\n\t*pGTP = pPTG - g->pointsToGraphemes;\n\t*pPTG = pGTP - g->graphemesToPoints;\n\n\treturn g;\n}\n"
  },
  {
    "path": "windows/grid.cpp",
    "content": "// 10 june 2016\n#include \"uipriv_windows.hpp\"\n\n// TODO compare with GTK+:\n// - what happens if you call InsertAt() twice?\n// - what happens if you call Append() twice?\n\n// TODOs\n// - the Assorted page has clipping and repositioning issues\n\nstruct gridChild {\n\tuiControl *c;\n\tint left;\n\tint top;\n\tint xspan;\n\tint yspan;\n\tint hexpand;\n\tuiAlign halign;\n\tint vexpand;\n\tuiAlign valign;\n\n\t// have these here so they don't need to be reallocated each relayout\n\tint finalx, finaly;\n\tint finalwidth, finalheight;\n\tint minwidth, minheight;\n};\n\nstruct uiGrid {\n\tuiWindowsControl c;\n\tHWND hwnd;\n\tstd::vector<struct gridChild *> *children;\n\tstd::map<uiControl *, size_t> *indexof;\n\tint padded;\n\n\tint xmin, ymin;\n\tint xmax, ymax;\n};\n\nstatic bool gridRecomputeMinMax(uiGrid *g)\n{\n\tbool first = true;\n\n\tfor (struct gridChild *gc : *(g->children)) {\n\t\t// this is important; we want g->xmin/g->ymin to satisfy gridLayoutData::visibleRow()/visibleColumn()\n\t\tif (!uiControlVisible(gc->c))\n\t\t\tcontinue;\n\t\tif (first) {\n\t\t\tg->xmin = gc->left;\n\t\t\tg->ymin = gc->top;\n\t\t\tg->xmax = gc->left + gc->xspan;\n\t\t\tg->ymax = gc->top + gc->yspan;\n\t\t\tfirst = false;\n\t\t\tcontinue;\n\t\t}\n\t\tif (g->xmin > gc->left)\n\t\t\tg->xmin = gc->left;\n\t\tif (g->ymin > gc->top)\n\t\t\tg->ymin = gc->top;\n\t\tif (g->xmax < (gc->left + gc->xspan))\n\t\t\tg->xmax = gc->left + gc->xspan;\n\t\tif (g->ymax < (gc->top + gc->yspan))\n\t\t\tg->ymax = gc->top + gc->yspan;\n\t}\n\treturn first != false;\n}\n\n#define xcount(g) ((g)->xmax - (g)->xmin)\n#define ycount(g) ((g)->ymax - (g)->ymin)\n#define toxindex(g, x) ((x) - (g)->xmin)\n#define toyindex(g, y) ((y) - (g)->ymin)\n\nclass gridLayoutData {\n\tint ycount;\npublic:\n\tint **gg;\t\t// topological map gg[y][x] = control index\n\tint *colwidths;\n\tint *rowheights;\n\tbool *hexpand;\n\tbool *vexpand;\n\tint nVisibleRows;\n\tint nVisibleColumns;\n\n\tbool noVisible;\n\n\tgridLayoutData(uiGrid *g)\n\t{\n\t\tsize_t i;\n\t\tint x, y;\n\n\t\tthis->noVisible = gridRecomputeMinMax(g);\n\n\t\tthis->gg = new int *[ycount(g)];\n\t\tfor (y = 0; y < ycount(g); y++) {\n\t\t\tthis->gg[y] = new int[xcount(g)];\n\t\t\tfor (x = 0; x < xcount(g); x++)\n\t\t\t\tthis->gg[y][x] = -1;\n\t\t}\n\n\t\tfor (i = 0; i < g->children->size(); i++) {\n\t\t\tstruct gridChild *gc;\n\n\t\t\tgc = (*(g->children))[i];\n\t\t\tif (!uiControlVisible(gc->c))\n\t\t\t\tcontinue;\n\t\t\tfor (y = gc->top; y < gc->top + gc->yspan; y++)\n\t\t\t\tfor (x = gc->left; x < gc->left + gc->xspan; x++)\n\t\t\t\t\tthis->gg[toyindex(g, y)][toxindex(g, x)] = i;\n\t\t}\n\n\t\tthis->colwidths = new int[xcount(g)];\n\t\tZeroMemory(this->colwidths, xcount(g) * sizeof (int));\n\t\tthis->rowheights = new int[ycount(g)];\n\t\tZeroMemory(this->rowheights, ycount(g) * sizeof (int));\n\t\tthis->hexpand = new bool[xcount(g)];\n\t\tZeroMemory(this->hexpand, xcount(g) * sizeof (bool));\n\t\tthis->vexpand = new bool[ycount(g)];\n\t\tZeroMemory(this->vexpand, ycount(g) * sizeof (bool));\n\n\t\tthis->ycount = ycount(g);\n\n\t\t// 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\n\t\t// note that the first row and column will always be visible because gridRecomputeMinMax() computed a smallest fitting rectangle\n\t\tif (this->noVisible)\n\t\t\treturn;\n\t\tthis->nVisibleRows = 0;\n\t\tfor (y = 0; y < this->ycount; y++)\n\t\t\tif (this->visibleRow(g, y))\n\t\t\t\tthis->nVisibleRows++;\n\t\tthis->nVisibleColumns = 0;\n\t\tfor (x = 0; x < xcount(g); x++)\n\t\t\tif (this->visibleColumn(g, x))\n\t\t\t\tthis->nVisibleColumns++;\n\t}\n\n\t~gridLayoutData()\n\t{\n\t\tsize_t y;\n\n\t\tdelete[] this->hexpand;\n\t\tdelete[] this->vexpand;\n\t\tdelete[] this->colwidths;\n\t\tdelete[] this->rowheights;\n\t\tfor (y = 0; y < this->ycount; y++)\n\t\t\tdelete[] this->gg[y];\n\t\tdelete[] this->gg;\n\t}\n\n\tbool visibleRow(uiGrid *g, int y)\n\t{\n\t\tint x;\n\t\tstruct gridChild *gc;\n\n\t\tfor (x = 0; x < xcount(g); x++)\n\t\t\tif (this->gg[y][x] != -1) {\n\t\t\t\tgc = (*(g->children))[this->gg[y][x]];\n\t\t\t\tif (gc->yspan == 1 || gc->top - g->ymin == y)\n\t\t\t\t\treturn true;\n\t\t\t}\n\t\treturn false;\n\t}\n\n\tbool visibleColumn(uiGrid *g, int x)\n\t{\n\t\tint y;\n\t\tstruct gridChild *gc;\n\n\t\tfor (y = 0; y < this->ycount; y++)\n\t\t\tif (this->gg[y][x] != -1) {\n\t\t\t\tgc = (*(g->children))[this->gg[y][x]];\n\t\t\t\tif (gc->xspan == 1 || gc->left - g->xmin == x)\n\t\t\t\t\treturn true;\n\t\t\t}\n\t\treturn false;\n\t}\n};\n\nstatic void gridPadding(uiGrid *g, int *xpadding, int *ypadding)\n{\n\tuiWindowsSizing sizing;\n\n\t*xpadding = 0;\n\t*ypadding = 0;\n\tif (g->padded) {\n\t\tuiWindowsGetSizing(g->hwnd, &sizing);\n\t\tuiWindowsSizingStandardPadding(&sizing, xpadding, ypadding);\n\t}\n}\n\nstatic void gridRelayout(uiGrid *g)\n{\n\tRECT r;\n\tint x, y, width, height;\n\tgridLayoutData *ld;\n\tint xpadding, ypadding;\n\tint ix, iy;\n\tint iwidth, iheight;\n\tint i;\n\tstruct gridChild *gc;\n\tint nhexpand, nvexpand;\n\n\tif (g->children->size() == 0)\n\t\treturn;\t\t// nothing to do\n\n\tuiWindowsEnsureGetClientRect(g->hwnd, &r);\n\tx = r.left;\n\ty = r.top;\n\twidth = r.right - r.left;\n\theight = r.bottom - r.top;\n\n\tgridPadding(g, &xpadding, &ypadding);\n\tld = new gridLayoutData(g);\n\tif (ld->noVisible) {\t\t// nothing to do\n\t\tdelete ld;\n\t\treturn;\n\t}\n\n\t// 0) discount padding from width/height\n\twidth -= (ld->nVisibleColumns - 1) * xpadding;\n\theight -= (ld->nVisibleRows - 1) * ypadding;\n\n\t// 1) compute colwidths and rowheights before handling expansion\n\t// we only count non-spanning controls to avoid weirdness\n\tfor (iy = 0; iy < ycount(g); iy++)\n\t\tfor (ix = 0; ix < xcount(g); ix++) {\n\t\t\ti = ld->gg[iy][ix];\n\t\t\tif (i == -1)\n\t\t\t\tcontinue;\n\t\t\tgc = (*(g->children))[i];\n\t\t\tuiWindowsControlMinimumSize(uiWindowsControl(gc->c), &iwidth, &iheight);\n\t\t\tif (gc->xspan == 1)\n\t\t\t\tif (ld->colwidths[ix] < iwidth)\n\t\t\t\t\tld->colwidths[ix] = iwidth;\n\t\t\tif (gc->yspan == 1)\n\t\t\t\tif (ld->rowheights[iy] < iheight)\n\t\t\t\t\tld->rowheights[iy] = iheight;\n\t\t\t// save these for step 6\n\t\t\tgc->minwidth = iwidth;\n\t\t\tgc->minheight = iheight;\n\t\t}\n\n\t// 2) figure out which rows/columns expand but not span\n\t// we need to know which expanding rows/columns don't span before we can handle the ones that do\n\tfor (i = 0; i < g->children->size(); i++) {\n\t\tgc = (*(g->children))[i];\n\t\tif (!uiControlVisible(gc->c))\n\t\t\tcontinue;\n\t\tif (gc->hexpand && gc->xspan == 1)\n\t\t\tld->hexpand[toxindex(g, gc->left)] = true;\n\t\tif (gc->vexpand && gc->yspan == 1)\n\t\t\tld->vexpand[toyindex(g, gc->top)] = true;\n\t}\n\n\t// 3) figure out which rows/columns expand that do span\n\t// the way we handle this is simple: if none of the spanned rows/columns expand, make all rows/columns expand\n\tfor (i = 0; i < g->children->size(); i++) {\n\t\tgc = (*(g->children))[i];\n\t\tif (!uiControlVisible(gc->c))\n\t\t\tcontinue;\n\t\tif (gc->hexpand && gc->xspan != 1) {\n\t\t\tbool doit = true;\n\n\t\t\tfor (ix = gc->left; ix < gc->left + gc->xspan; ix++)\n\t\t\t\tif (ld->hexpand[toxindex(g, ix)]) {\n\t\t\t\t\tdoit = false;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tif (doit)\n\t\t\t\tfor (ix = gc->left; ix < gc->left + gc->xspan; ix++)\n\t\t\t\t\tld->hexpand[toxindex(g, ix)] = true;\n\t\t}\n\t\tif (gc->vexpand && gc->yspan != 1) {\n\t\t\tbool doit = true;\n\n\t\t\tfor (iy = gc->top; iy < gc->top + gc->yspan; iy++)\n\t\t\t\tif (ld->vexpand[toyindex(g, iy)]) {\n\t\t\t\t\tdoit = false;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\tif (doit)\n\t\t\t\tfor (iy = gc->top; iy < gc->top + gc->yspan; iy++)\n\t\t\t\t\tld->vexpand[toyindex(g, iy)] = true;\n\t\t}\n\t}\n\n\t// 4) compute and assign expanded widths/heights\n\tnhexpand = 0;\n\tnvexpand = 0;\n\tfor (i = 0; i < xcount(g); i++)\n\t\tif (ld->hexpand[i])\n\t\t\tnhexpand++;\n\t\telse\n\t\t\twidth -= ld->colwidths[i];\n\tfor (i = 0; i < ycount(g); i++)\n\t\tif (ld->vexpand[i])\n\t\t\tnvexpand++;\n\t\telse\n\t\t\theight -= ld->rowheights[i];\n\tfor (i = 0; i < xcount(g); i++)\n\t\tif (ld->hexpand[i])\n\t\t\tld->colwidths[i] = width / nhexpand;\n\tfor (i = 0; i < ycount(g); i++)\n\t\tif (ld->vexpand[i])\n\t\t\tld->rowheights[i] = height / nvexpand;\n\n\t// 5) reset the final coordinates for the next step\n\tfor (i = 0; i < g->children->size(); i++) {\n\t\tgc = (*(g->children))[i];\n\t\tif (!uiControlVisible(gc->c))\n\t\t\tcontinue;\n\t\tgc->finalx = 0;\n\t\tgc->finaly = 0;\n\t\tgc->finalwidth = 0;\n\t\tgc->finalheight = 0;\n\t}\n\n\t// 6) compute cell positions and sizes\n\tfor (iy = 0; iy < ycount(g); iy++) {\n\t\tint curx;\n\t\tint prev;\n\n\t\tcurx = 0;\n\t\tprev = -1;\n\t\tfor (ix = 0; ix < xcount(g); ix++) {\n\t\t\tif (!ld->visibleColumn(g, ix))\n\t\t\t\tcontinue;\n\t\t\ti = ld->gg[iy][ix];\n\t\t\tif (i != -1) {\n\t\t\t\tgc = (*(g->children))[i];\n\t\t\t\tif (iy == toyindex(g, gc->top)) {\t\t// don't repeat this step if the control spans vertically\n\t\t\t\t\tif (i != prev)\n\t\t\t\t\t\tgc->finalx = curx;\n\t\t\t\t\telse\n\t\t\t\t\t\tgc->finalwidth += xpadding;\n\t\t\t\t\tgc->finalwidth += ld->colwidths[ix];\n\t\t\t\t}\n\t\t\t}\n\t\t\tcurx += ld->colwidths[ix] + xpadding;\n\t\t\tprev = i;\n\t\t}\n\t}\n\tfor (ix = 0; ix < xcount(g); ix++) {\n\t\tint cury;\n\t\tint prev;\n\n\t\tcury = 0;\n\t\tprev = -1;\n\t\tfor (iy = 0; iy < ycount(g); iy++) {\n\t\t\tif (!ld->visibleRow(g, iy))\n\t\t\t\tcontinue;\n\t\t\ti = ld->gg[iy][ix];\n\t\t\tif (i != -1) {\n\t\t\t\tgc = (*(g->children))[i];\n\t\t\t\tif (ix == toxindex(g, gc->left)) {\t\t// don't repeat this step if the control spans horizontally\n\t\t\t\t\tif (i != prev)\n\t\t\t\t\t\tgc->finaly = cury;\n\t\t\t\t\telse\n\t\t\t\t\t\tgc->finalheight += ypadding;\n\t\t\t\t\tgc->finalheight += ld->rowheights[iy];\n\t\t\t\t}\n\t\t\t}\n\t\t\tcury += ld->rowheights[iy] + ypadding;\n\t\t\tprev = i;\n\t\t}\n\t}\n\n\t// 7) everything as it stands now is set for xalign == Fill yalign == Fill; set the correct alignments\n\t// this is why we saved minwidth/minheight above\n\tfor (i = 0; i < g->children->size(); i++) {\n\t\tgc = (*(g->children))[i];\n\t\tif (!uiControlVisible(gc->c))\n\t\t\tcontinue;\n\t\tif (gc->halign != uiAlignFill) {\n\t\t\tswitch (gc->halign) {\n\t\t\tcase uiAlignEnd:\n\t\t\t\tgc->finalx += gc->finalwidth - gc->minwidth;\n\t\t\t\tbreak;\n\t\t\tcase uiAlignCenter:\n\t\t\t\tgc->finalx += (gc->finalwidth - gc->minwidth) / 2;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tgc->finalwidth = gc->minwidth;\t\t// for all three\n\t\t}\n\t\tif (gc->valign != uiAlignFill) {\n\t\t\tswitch (gc->valign) {\n\t\t\tcase uiAlignEnd:\n\t\t\t\tgc->finaly += gc->finalheight - gc->minheight;\n\t\t\t\tbreak;\n\t\t\tcase uiAlignCenter:\n\t\t\t\tgc->finaly += (gc->finalheight - gc->minheight) / 2;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tgc->finalheight = gc->minheight;\t\t// for all three\n\t\t}\n\t}\n\n\t// 8) and FINALLY we resize\n\tfor (iy = 0; iy < ycount(g); iy++)\n\t\tfor (ix = 0; ix < xcount(g); ix++) {\n\t\t\ti = ld->gg[iy][ix];\n\t\t\tif (i != -1) {\t\t// treat empty cells like spaces\n\t\t\t\tgc = (*(g->children))[i];\n\t\t\t\tuiWindowsEnsureMoveWindowDuringResize(\n\t\t\t\t\t(HWND) uiControlHandle(gc->c),\n \t\t\t\t\tgc->finalx,//TODO + x,\n\t\t\t\t\tgc->finaly,//TODO + y,\n\t\t\t\t\tgc->finalwidth,\n\t\t\t\t\tgc->finalheight);\n\t\t\t}\n\t\t}\n\n\tdelete ld;\n}\n\nstatic void uiGridDestroy(uiControl *c)\n{\n\tuiGrid *g = uiGrid(c);\n\n\tfor (struct gridChild *gc : *(g->children)) {\n\t\tuiControlSetParent(gc->c, NULL);\n\t\tuiControlDestroy(gc->c);\n\t\tuiprivFree(gc);\n\t}\n\tdelete g->indexof;\n\tdelete g->children;\n\tuiWindowsEnsureDestroyWindow(g->hwnd);\n\tuiFreeControl(uiControl(g));\n}\n\nuiWindowsControlDefaultHandle(uiGrid)\nuiWindowsControlDefaultParent(uiGrid)\nuiWindowsControlDefaultSetParent(uiGrid)\nuiWindowsControlDefaultToplevel(uiGrid)\nuiWindowsControlDefaultVisible(uiGrid)\nuiWindowsControlDefaultShow(uiGrid)\nuiWindowsControlDefaultHide(uiGrid)\nuiWindowsControlDefaultEnabled(uiGrid)\nuiWindowsControlDefaultEnable(uiGrid)\nuiWindowsControlDefaultDisable(uiGrid)\n\nstatic void uiGridSyncEnableState(uiWindowsControl *c, int enabled)\n{\n\tuiGrid *g = uiGrid(c);\n\n\tif (uiWindowsShouldStopSyncEnableState(uiWindowsControl(g), enabled))\n\t\treturn;\n\tfor (const struct gridChild *gc : *(g->children))\n\t\tuiWindowsControlSyncEnableState(uiWindowsControl(gc->c), enabled);\n}\n\nuiWindowsControlDefaultSetParentHWND(uiGrid)\n\nstatic void uiGridMinimumSize(uiWindowsControl *c, int *width, int *height)\n{\n\tuiGrid *g = uiGrid(c);\n\tint xpadding, ypadding;\n\tgridLayoutData *ld;\n\tint x, y;\n\tint i;\n\tstruct gridChild *gc;\n\tint minwid, minht;\n\tint colwidth, rowheight;\n\n\t*width = 0;\n\t*height = 0;\n\tif (g->children->size() == 0)\n\t\treturn;\t\t// nothing to do\n\n\tgridPadding(g, &xpadding, &ypadding);\n\tld = new gridLayoutData(g);\n\tif (ld->noVisible) {\t\t// nothing to do; return 0x0\n\t\tdelete ld;\n\t\treturn;\n\t}\n\n\t// 1) compute colwidths and rowheights before handling expansion\n\t// TODO put this in its own function (but careful about the spanning calculation in gridRelayout())\n\tfor (y = 0; y < ycount(g); y++)\n\t\tfor (x = 0; x < xcount(g); x++) {\n\t\t\ti = ld->gg[y][x];\n\t\t\tif (i == -1)\n\t\t\t\tcontinue;\n\t\t\tgc = (*(g->children))[i];\n\t\t\tuiWindowsControlMinimumSize(uiWindowsControl(gc->c), &minwid, &minht);\n\t\t\t// allot equal space in the presence of spanning to keep things sane\n\t\t\tif (ld->colwidths[x] < minwid / gc->xspan)\n\t\t\t\tld->colwidths[x] = minwid / gc->xspan;\n\t\t\tif (ld->rowheights[y] < minht / gc->yspan)\n\t\t\t\tld->rowheights[y] = minht / gc->yspan;\n\t\t\t// save these for step 6\n\t\t\tgc->minwidth = minwid;\n\t\t\tgc->minheight = minht;\n\t\t}\n\n\t// 2) compute total column width/row height\n\tcolwidth = 0;\n\trowheight = 0;\n\tfor (x = 0; x < xcount(g); x++)\n\t\tcolwidth += ld->colwidths[x];\n\tfor (y = 0; y < ycount(g); y++)\n\t\trowheight += ld->rowheights[y];\n\n\t// and that's it; just account for padding\n\t*width = colwidth + (ld->nVisibleColumns - 1) * xpadding;\n\t*height = rowheight + (ld->nVisibleRows - 1) * ypadding;\n}\n\nstatic void uiGridMinimumSizeChanged(uiWindowsControl *c)\n{\n\tuiGrid *g = uiGrid(c);\n\n\tif (uiWindowsControlTooSmall(uiWindowsControl(g))) {\n\t\tuiWindowsControlContinueMinimumSizeChanged(uiWindowsControl(g));\n\t\treturn;\n\t}\n\tgridRelayout(g);\n}\n\nuiWindowsControlDefaultLayoutRect(uiGrid)\nuiWindowsControlDefaultAssignControlIDZOrder(uiGrid)\n\nstatic void uiGridChildVisibilityChanged(uiWindowsControl *c)\n{\n\t// TODO eliminate the redundancy\n\tuiWindowsControlMinimumSizeChanged(c);\n}\n\n// must have called gridRecomputeMinMax() first\nstatic void gridArrangeChildren(uiGrid *g)\n{\n\tLONG_PTR controlID;\n\tHWND insertAfter;\n\tgridLayoutData *ld;\n\tbool *visited;\n\tint x, y;\n\tint i;\n\tstruct gridChild *gc;\n\n\tif (g->children->size() == 0)\n\t\treturn;\t\t// nothing to do\n\tld = new gridLayoutData(g);\n\tcontrolID = 100;\n\tinsertAfter = NULL;\n\tvisited = new bool[g->children->size()];\n\tZeroMemory(visited, g->children->size() * sizeof (bool));\n\tfor (y = 0; y < ycount(g); y++)\n\t\tfor (x = 0; x < xcount(g); x++) {\n\t\t\ti = ld->gg[y][x];\n\t\t\tif (i == -1)\n\t\t\t\tcontinue;\n\t\t\tif (visited[i])\n\t\t\t\tcontinue;\n\t\t\tvisited[i] = true;\n\t\t\tgc = (*(g->children))[i];\n\t\t\tuiWindowsControlAssignControlIDZOrder(uiWindowsControl(gc->c), &controlID, &insertAfter);\n\t\t}\n\tdelete[] visited;\n\tdelete ld;\n}\n\nstatic struct gridChild *toChild(uiControl *c, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign)\n{\n\tstruct gridChild *gc;\n\n\tif (xspan < 0)\n\t\tuiprivUserBug(\"You cannot have a negative xspan in a uiGrid cell.\");\n\tif (yspan < 0)\n\t\tuiprivUserBug(\"You cannot have a negative yspan in a uiGrid cell.\");\n\tgc = uiprivNew(struct gridChild);\n\tgc->c = c;\n\tgc->xspan = xspan;\n\tgc->yspan = yspan;\n\tgc->hexpand = hexpand;\n\tgc->halign = halign;\n\tgc->vexpand = vexpand;\n\tgc->valign = valign;\n\treturn gc;\n}\n\nstatic void add(uiGrid *g, struct gridChild *gc)\n{\n\tuiControlSetParent(gc->c, uiControl(g));\n\tuiWindowsControlSetParentHWND(uiWindowsControl(gc->c), g->hwnd);\n\tg->children->push_back(gc);\n\t(*(g->indexof))[gc->c] = g->children->size() - 1;\n\tgridRecomputeMinMax(g);\n\tgridArrangeChildren(g);\n\tuiWindowsControlMinimumSizeChanged(uiWindowsControl(g));\n}\n\nvoid uiGridAppend(uiGrid *g, uiControl *c, int left, int top, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign)\n{\n\tstruct gridChild *gc;\n\n\tgc = toChild(c, xspan, yspan, hexpand, halign, vexpand, valign);\n\tgc->left = left;\n\tgc->top = top;\n\tadd(g, gc);\n}\n\n// TODO decide what happens if existing is NULL\nvoid uiGridInsertAt(uiGrid *g, uiControl *c, uiControl *existing, uiAt at, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign)\n{\n\tstruct gridChild *gc;\n\tstruct gridChild *other;\n\n\tgc = toChild(c, xspan, yspan, hexpand, halign, vexpand, valign);\n\tother = (*(g->children))[(*(g->indexof))[existing]];\n\tswitch (at) {\n\tcase uiAtLeading:\n\t\tgc->left = other->left - gc->xspan;\n\t\tgc->top = other->top;\n\t\tbreak;\n\tcase uiAtTop:\n\t\tgc->left = other->left;\n\t\tgc->top = other->top - gc->yspan;\n\t\tbreak;\n\tcase uiAtTrailing:\n\t\tgc->left = other->left + other->xspan;\n\t\tgc->top = other->top;\n\t\tbreak;\n\tcase uiAtBottom:\n\t\tgc->left = other->left;\n\t\tgc->top = other->top + other->yspan;\n\t\tbreak;\n\t// TODO add error checks to ALL enums\n\t}\n\tadd(g, gc);\n}\n\nint uiGridPadded(uiGrid *g)\n{\n\treturn g->padded;\n}\n\nvoid uiGridSetPadded(uiGrid *g, int padded)\n{\n\tg->padded = padded;\n\tuiWindowsControlMinimumSizeChanged(uiWindowsControl(g));\n}\n\nstatic void onResize(uiWindowsControl *c)\n{\n\tgridRelayout(uiGrid(c));\n}\n\nuiGrid *uiNewGrid(void)\n{\n\tuiGrid *g;\n\n\tuiWindowsNewControl(uiGrid, g);\n\n\tg->hwnd = uiWindowsMakeContainer(uiWindowsControl(g), onResize);\n\n\tg->children = new std::vector<struct gridChild *>;\n\tg->indexof = new std::map<uiControl *, size_t>;\n\n\treturn g;\n}\n"
  },
  {
    "path": "windows/group.cpp",
    "content": "// 16 may 2015\n#include \"uipriv_windows.hpp\"\n\nstruct uiGroup {\n\tuiWindowsControl c;\n\tHWND hwnd;\n\tstruct uiControl *child;\n\tint margined;\n};\n\n// from https://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing\n#define groupXMargin 6\n#define groupYMarginTop 11 /* note this value /includes/ the groupbox label */\n#define groupYMarginBottom 7\n\n// unfortunately because the client area of a groupbox includes the frame and caption text, we have to apply some margins ourselves, even if we don't want \"any\"\n// these were deduced by hand based on the standard DLU conversions; the X and Y top margins are the width and height, respectively, of one character cell\n// they can be fine-tuned later\n#define groupUnmarginedXMargin 4\n#define groupUnmarginedYMarginTop 8\n#define groupUnmarginedYMarginBottom 3\n\nstatic void groupMargins(uiGroup *g, int *mx, int *mtop, int *mbottom)\n{\n\tuiWindowsSizing sizing;\n\n\t*mx = groupUnmarginedXMargin;\n\t*mtop = groupUnmarginedYMarginTop;\n\t*mbottom = groupUnmarginedYMarginBottom;\n\tif (g->margined) {\n\t\t*mx = groupXMargin;\n\t\t*mtop = groupYMarginTop;\n\t\t*mbottom = groupYMarginBottom;\n\t}\n\tuiWindowsGetSizing(g->hwnd, &sizing);\n\tuiWindowsSizingDlgUnitsToPixels(&sizing, mx, mtop);\n\tuiWindowsSizingDlgUnitsToPixels(&sizing, NULL, mbottom);\n}\n\nstatic void groupRelayout(uiGroup *g)\n{\n\tRECT r;\n\tint mx, mtop, mbottom;\n\n\tif (g->child == NULL)\n\t\treturn;\n\tuiWindowsEnsureGetClientRect(g->hwnd, &r);\n\tgroupMargins(g, &mx, &mtop, &mbottom);\n\tr.left += mx;\n\tr.top += mtop;\n\tr.right -= mx;\n\tr.bottom -= mbottom;\n\tuiWindowsEnsureMoveWindowDuringResize((HWND) uiControlHandle(g->child), r.left, r.top, r.right - r.left, r.bottom - r.top);\n}\n\nstatic void uiGroupDestroy(uiControl *c)\n{\n\tuiGroup *g = uiGroup(c);\n\n\tif (g->child != NULL) {\n\t\tuiControlSetParent(g->child, NULL);\n\t\tuiControlDestroy(g->child);\n\t}\n\tuiWindowsEnsureDestroyWindow(g->hwnd);\n\tuiFreeControl(uiControl(g));\n}\n\nuiWindowsControlDefaultHandle(uiGroup)\nuiWindowsControlDefaultParent(uiGroup)\nuiWindowsControlDefaultSetParent(uiGroup)\nuiWindowsControlDefaultToplevel(uiGroup)\nuiWindowsControlDefaultVisible(uiGroup)\nuiWindowsControlDefaultShow(uiGroup)\nuiWindowsControlDefaultHide(uiGroup)\nuiWindowsControlDefaultEnabled(uiGroup)\nuiWindowsControlDefaultEnable(uiGroup)\nuiWindowsControlDefaultDisable(uiGroup)\n\nstatic void uiGroupSyncEnableState(uiWindowsControl *c, int enabled)\n{\n\tuiGroup *g = uiGroup(c);\n\n\tif (uiWindowsShouldStopSyncEnableState(uiWindowsControl(g), enabled))\n\t\treturn;\n\tEnableWindow(g->hwnd, enabled);\n\tif (g->child != NULL)\n\t\tuiWindowsControlSyncEnableState(uiWindowsControl(g->child), enabled);\n}\n\nuiWindowsControlDefaultSetParentHWND(uiGroup)\n\nstatic void uiGroupMinimumSize(uiWindowsControl *c, int *width, int *height)\n{\n\tuiGroup *g = uiGroup(c);\n\tint mx, mtop, mbottom;\n\tint labelWidth;\n\n\t*width = 0;\n\t*height = 0;\n\tif (g->child != NULL)\n\t\tuiWindowsControlMinimumSize(uiWindowsControl(g->child), width, height);\n\tlabelWidth = uiWindowsWindowTextWidth(g->hwnd);\n\tif (*width < labelWidth)\t\t// don't clip the label; it doesn't ellipsize\n\t\t*width = labelWidth;\n\tgroupMargins(g, &mx, &mtop, &mbottom);\n\t*width += 2 * mx;\n\t*height += mtop + mbottom;\n}\n\nstatic void uiGroupMinimumSizeChanged(uiWindowsControl *c)\n{\n\tuiGroup *g = uiGroup(c);\n\n\tif (uiWindowsControlTooSmall(uiWindowsControl(g))) {\n\t\tuiWindowsControlContinueMinimumSizeChanged(uiWindowsControl(g));\n\t\treturn;\n\t}\n\tgroupRelayout(g);\n}\n\nuiWindowsControlDefaultLayoutRect(uiGroup)\nuiWindowsControlDefaultAssignControlIDZOrder(uiGroup)\n\nstatic void uiGroupChildVisibilityChanged(uiWindowsControl *c)\n{\n\t// TODO eliminate the redundancy\n\tuiWindowsControlMinimumSizeChanged(c);\n}\n\nchar *uiGroupTitle(uiGroup *g)\n{\n\treturn uiWindowsWindowText(g->hwnd);\n}\n\nvoid uiGroupSetTitle(uiGroup *g, const char *text)\n{\n\tuiWindowsSetWindowText(g->hwnd, text);\n\t// changing the text might necessitate a change in the groupbox's size\n\tuiWindowsControlMinimumSizeChanged(uiWindowsControl(g));\n}\n\nvoid uiGroupSetChild(uiGroup *g, uiControl *child)\n{\n\tif (g->child != NULL) {\n\t\tuiControlSetParent(g->child, NULL);\n\t\tuiWindowsControlSetParentHWND(uiWindowsControl(g->child), NULL);\n\t}\n\tg->child = child;\n\tif (g->child != NULL) {\n\t\tuiControlSetParent(g->child, uiControl(g));\n\t\tuiWindowsControlSetParentHWND(uiWindowsControl(g->child), g->hwnd);\n\t\tuiWindowsControlAssignSoleControlIDZOrder(uiWindowsControl(g->child));\n\t\tuiWindowsControlMinimumSizeChanged(uiWindowsControl(g));\n\t}\n}\n\nint uiGroupMargined(uiGroup *g)\n{\n\treturn g->margined;\n}\n\nvoid uiGroupSetMargined(uiGroup *g, int margined)\n{\n\tg->margined = margined;\n\tuiWindowsControlMinimumSizeChanged(uiWindowsControl(g));\n}\n\nstatic LRESULT CALLBACK groupSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)\n{\n\tuiGroup *g = uiGroup(dwRefData);\n\tWINDOWPOS *wp = (WINDOWPOS *) lParam;\n\tMINMAXINFO *mmi = (MINMAXINFO *) lParam;\n\tint minwid, minht;\n\tLRESULT lResult;\n\n\tif (handleParentMessages(hwnd, uMsg, wParam, lParam, &lResult) != FALSE)\n\t\treturn lResult;\n\tswitch (uMsg) {\n\tcase WM_WINDOWPOSCHANGED:\n\t\tif ((wp->flags & SWP_NOSIZE) != 0)\n\t\t\tbreak;\n\t\tgroupRelayout(g);\n\t\treturn 0;\n\tcase WM_GETMINMAXINFO:\n\t\tlResult = DefWindowProcW(hwnd, uMsg, wParam, lParam);\n\t\tuiWindowsControlMinimumSize(uiWindowsControl(g), &minwid, &minht);\n\t\tmmi->ptMinTrackSize.x = minwid;\n\t\tmmi->ptMinTrackSize.y = minht;\n\t\treturn lResult;\n\tcase WM_NCDESTROY:\n\t\tif (RemoveWindowSubclass(hwnd, groupSubProc, uIdSubclass) == FALSE)\n\t\t\tlogLastError(L\"error removing groupbox subclass\");\n\t\tbreak;\n\t}\n\treturn DefSubclassProc(hwnd, uMsg, wParam, lParam);\n}\n\nuiGroup *uiNewGroup(const char *text)\n{\n\tuiGroup *g;\n\tWCHAR *wtext;\n\n\tuiWindowsNewControl(uiGroup, g);\n\n\twtext = toUTF16(text);\n\tg->hwnd = uiWindowsEnsureCreateControlHWND(WS_EX_CONTROLPARENT,\n\t\tL\"button\", wtext,\n\t\tBS_GROUPBOX,\n\t\thInstance, NULL,\n\t\tTRUE);\n\tuiprivFree(wtext);\n\n\tif (SetWindowSubclass(g->hwnd, groupSubProc, 0, (DWORD_PTR) g) == FALSE)\n\t\tlogLastError(L\"error subclassing groupbox to handle parent messages\");\n\n\treturn g;\n}\n"
  },
  {
    "path": "windows/image.cpp",
    "content": "#include \"uipriv_windows.hpp\"\n\n// TODO:\n// - is the alpha channel ignored when drawing images in tables?\n\nIWICImagingFactory *uiprivWICFactory = NULL;\n\nHRESULT uiprivInitImage(void)\n{\n\treturn CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER,\n\t\tIID_IWICImagingFactory, (void **) (&uiprivWICFactory));\n}\n\nvoid uiprivUninitImage(void)\n{\n\tuiprivWICFactory->Release();\n\tuiprivWICFactory = NULL;\n}\n\nstruct uiImage {\n\tdouble width;\n\tdouble height;\n\tstd::vector<IWICBitmap *> *bitmaps;\n};\n\nuiImage *uiNewImage(double width, double height)\n{\n\tuiImage *i;\n\n\ti = uiprivNew(uiImage);\n\ti->width = width;\n\ti->height = height;\n\ti->bitmaps = new std::vector<IWICBitmap *>;\n\treturn i;\n}\n\nvoid uiFreeImage(uiImage *i)\n{\n\tfor (IWICBitmap *b : *(i->bitmaps))\n\t\tb->Release();\n\tdelete i->bitmaps;\n\tuiprivFree(i);\n}\n\n// 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))\n// but what WIC format is system-endian ARGB? for a little-endian system, that's BGRA\n// 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)\n// it also turns out Direct2D requires PBGRA for drawing (https://docs.microsoft.com/en-us/windows/desktop/wic/-wic-bitmapsources-howto-drawusingd2d)\n// so I guess we can assume PBGRA is correct...? (TODO)\n#define formatForGDI GUID_WICPixelFormat32bppPBGRA\n\nvoid uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int byteStride)\n{\n\tIWICBitmap *b;\n\tWICRect r;\n\tIWICBitmapLock *l;\n\tuint8_t *pix, *data;\n\t// TODO WICInProcPointer is not available in MinGW-w64\n\tBYTE *dipp;\n\tUINT size;\n\tUINT realStride;\n\tint x, y;\n\tHRESULT hr;\n\n\thr = uiprivWICFactory->CreateBitmap(pixelWidth, pixelHeight,\n\t\tformatForGDI, WICBitmapCacheOnDemand,\n\t\t&b);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error calling CreateBitmap() in uiImageAppend()\", hr);\n\tr.X = 0;\n\tr.Y = 0;\n\tr.Width = pixelWidth;\n\tr.Height = pixelHeight;\n\thr = b->Lock(&r, WICBitmapLockWrite, &l);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error calling Lock() in uiImageAppend()\", hr);\n\n\tpix = (uint8_t *) pixels;\n\t// TODO can size be NULL?\n\thr = l->GetDataPointer(&size, &dipp);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error calling GetDataPointer() in uiImageAppend()\", hr);\n\tdata = (uint8_t *) dipp;\n\thr = l->GetStride(&realStride);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error calling GetStride() in uiImageAppend()\", hr);\n\tfor (y = 0; y < pixelHeight; y++) {\n\t\tfor (x = 0; x < pixelWidth * 4; x += 4) {\n\t\t\tunion {\n\t\t\t\tuint32_t v32;\n\t\t\t\tuint8_t v8[4];\n\t\t\t} v;\n\n\t\t\tv.v32 = ((uint32_t) (pix[x + 3])) << 24;\n\t\t\tv.v32 |= ((uint32_t) (pix[x])) << 16;\n\t\t\tv.v32 |= ((uint32_t) (pix[x + 1])) << 8;\n\t\t\tv.v32 |= ((uint32_t) (pix[x + 2]));\n\t\t\tdata[x] = v.v8[0];\n\t\t\tdata[x + 1] = v.v8[1];\n\t\t\tdata[x + 2] = v.v8[2];\n\t\t\tdata[x + 3] = v.v8[3];\n\t\t}\n\t\tpix += byteStride;\n\t\tdata += realStride;\n\t}\n\n\tl->Release();\n\ti->bitmaps->push_back(b);\n}\n\nstruct matcher {\n\tIWICBitmap *best;\n\tint distX;\n\tint distY;\n\tint targetX;\n\tint targetY;\n\tbool foundLarger;\n};\n\n// TODO is this the right algorithm?\nstatic void match(IWICBitmap *b, struct matcher *m)\n{\n\tUINT ux, uy;\n\tint x, y;\n\tint x2, y2;\n\tHRESULT hr;\n\n\thr = b->GetSize(&ux, &uy);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error calling GetSize() in match()\", hr);\n\tx = ux;\n\ty = uy;\n\tif (m->best == NULL)\n\t\tgoto writeMatch;\n\n\tif (x < m->targetX && y < m->targetY)\n\t\tif (m->foundLarger)\n\t\t\t// always prefer larger ones\n\t\t\treturn;\n\tif (x >= m->targetX && y >= m->targetY && !m->foundLarger)\n\t\t// we set foundLarger below\n\t\tgoto writeMatch;\n\n// TODO\n#define abs(x) ((x) < 0 ? -(x) : (x))\n\tx2 = abs(m->targetX - x);\n\ty2 = abs(m->targetY - y);\n\tif (x2 < m->distX && y2 < m->distY)\n\t\tgoto writeMatch;\n\n\t// TODO weight one dimension? threshhold?\n\treturn;\n\nwriteMatch:\n\t// must set this here too; otherwise the first image will never have ths set\n\tif (x >= m->targetX && y >= m->targetY && !m->foundLarger)\n\t\tm->foundLarger = true;\n\tm->best = b;\n\tm->distX = abs(m->targetX - x);\n\tm->distY = abs(m->targetY - y);\n}\n\nIWICBitmap *uiprivImageAppropriateForDC(uiImage *i, HDC dc)\n{\n\tstruct matcher m;\n\n\tm.best = NULL;\n\tm.distX = INT_MAX;\n\tm.distY = INT_MAX;\n\t// TODO explain this\n\tm.targetX = MulDiv(i->width, GetDeviceCaps(dc, LOGPIXELSX), 96);\n\tm.targetY = MulDiv(i->height, GetDeviceCaps(dc, LOGPIXELSY), 96);\n\tm.foundLarger = false;\n\tfor (IWICBitmap *b : *(i->bitmaps))\n\t\tmatch(b, &m);\n\treturn m.best;\n}\n\n// TODO this needs to center images if the given size is not the same aspect ratio\nHRESULT uiprivWICToGDI(IWICBitmap *b, HDC dc, int width, int height, HBITMAP *hb)\n{\n\tUINT ux, uy;\n\tint x, y;\n\tIWICBitmapSource *src;\n\tBITMAPINFO bmi;\n\tVOID *bits;\n\tBITMAP bmp;\n\tHRESULT hr;\n\n\thr = b->GetSize(&ux, &uy);\n\tif (hr != S_OK)\n\t\treturn hr;\n\tx = ux;\n\ty = uy;\n\tif (width == 0)\n\t\twidth = x;\n\tif (height == 0)\n\t\theight = y;\n\n\t// special case: don't invoke a scaler if the size is the same\n\tif (width == x && height == y) {\n\t\tb->AddRef();\t\t// for the Release() later\n\t\tsrc = b;\n\t} else {\n\t\tIWICBitmapScaler *scaler;\n\t\tWICPixelFormatGUID guid;\n\t\tIWICFormatConverter *conv;\n\n\t\thr = uiprivWICFactory->CreateBitmapScaler(&scaler);\n\t\tif (hr != S_OK)\n\t\t\treturn hr;\n\t\thr = scaler->Initialize(b, width, height,\n\t\t\t// 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())\n\t\t\tWICBitmapInterpolationModeNearestNeighbor);\n\t\tif (hr != S_OK) {\n\t\t\tscaler->Release();\n\t\t\treturn hr;\n\t\t}\n\n\t\t// But we are not done yet! IWICBitmapScaler can use an\n\t\t// entirely different pixel format than what we gave it,\n\t\t// and by extension, what GDI wants. See also:\n\t\t// - https://stackoverflow.com/questions/28323228/iwicbitmapscaler-doesnt-work-for-96bpprgbfloat-format\n\t\t// - https://github.com/Microsoft/DirectXTex/blob/0d94e9469bc3e6080a71145f35efa559f8f2e522/DirectXTex/DirectXTexResize.cpp#L83\n\t\thr = scaler->GetPixelFormat(&guid);\n\t\tif (hr != S_OK) {\n\t\t\tscaler->Release();\n\t\t\treturn hr;\n\t\t}\n\t\tif (IsEqualGUID(guid, formatForGDI))\n\t\t\tsrc = scaler;\n\t\telse {\n\t\t\thr = uiprivWICFactory->CreateFormatConverter(&conv);\n\t\t\tif (hr != S_OK) {\n\t\t\t\tscaler->Release();\n\t\t\t\treturn hr;\n\t\t\t}\n\t\t\thr = conv->Initialize(scaler, formatForGDI,\n\t\t\t\t// TODO is the dither type correct in all cases?\n\t\t\t\tWICBitmapDitherTypeNone, NULL, 0, WICBitmapPaletteTypeMedianCut);\n\t\t\tscaler->Release();\n\t\t\tif (hr != S_OK) {\n\t\t\t\tconv->Release();\n\t\t\t\treturn hr;\n\t\t\t}\n\t\t\tsrc = conv;\n\t\t}\n\t}\n\n\tZeroMemory(&bmi, sizeof (BITMAPINFO));\n\tbmi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER);\n\tbmi.bmiHeader.biWidth = width;\n\tbmi.bmiHeader.biHeight = -((int) height);\n\tbmi.bmiHeader.biPlanes = 1;\n\tbmi.bmiHeader.biBitCount = 32;\n\tbmi.bmiHeader.biCompression = BI_RGB;\n\t*hb = CreateDIBSection(dc, &bmi, DIB_RGB_COLORS,\n\t\t&bits, NULL, 0);\n\tif (*hb == NULL) {\n\t\tlogLastError(L\"CreateDIBSection()\");\n\t\thr = E_FAIL;\n\t\tgoto fail;\n\t}\n\n\t// now we need to figure out the stride of the image data GDI gave us\n\t// TODO find out if CreateDIBSection() fills that in bmi for us\n\t// TODO fill in the error returns here too\n\tif (GetObject(*hb, sizeof (BITMAP), &bmp) == 0)\n\t\tlogLastError(L\"error calling GetObject() in uiprivWICToGDI()\");\n\thr = src->CopyPixels(NULL, bmp.bmWidthBytes,\n\t\tbmp.bmWidthBytes * bmp.bmHeight, (BYTE *) bits);\n\nfail:\n\tif (*hb != NULL && hr != S_OK) {\n\t\t// don't bother with the error returned here\n\t\tDeleteObject(*hb);\n\t\t*hb = NULL;\n\t}\n\tsrc->Release();\n\treturn hr;\n}\n"
  },
  {
    "path": "windows/init.cpp",
    "content": "// 6 april 2015\n#include \"uipriv_windows.hpp\"\n#include \"attrstr.hpp\"\n\nHINSTANCE hInstance;\nint nCmdShow;\n\nHFONT hMessageFont;\n\n// LONGTERM needed?\nHBRUSH hollowBrush;\n\n// the returned pointer is actually to the second character\n// if the first character is - then free, otherwise don't\nstatic const char *initerr(const char *message, const WCHAR *label, DWORD value)\n{\n\tWCHAR *sysmsg;\n\tBOOL hassysmsg;\n\tWCHAR *wmessage;\n\tWCHAR *wout;\n\tchar *out;\n\n\thassysmsg = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, value, 0, (LPWSTR) (&sysmsg), 0, NULL) != 0;\n\tif (!hassysmsg)\n\t\tsysmsg = (WCHAR *) L\"\";\t\t\t// TODO\n\twmessage = toUTF16(message + 1);\n\twout = strf(L\"-error initializing libui: %s; code %I32d (0x%08I32X) %s\",\n\t\twmessage,\n\t\tvalue, value,\n\t\tsysmsg);\n\tuiprivFree(wmessage);\n\tif (hassysmsg)\n\t\tLocalFree(sysmsg);\t\t// ignore error\n\tout = toUTF8(wout);\n\tuiprivFree(wout);\n\treturn out + 1;\n}\n\n#define ieLastErr(msg) initerr(\"=\" msg, L\"GetLastError() ==\", GetLastError())\n#define ieHRESULT(msg, hr) initerr(\"=\" msg, L\"HRESULT\", (DWORD) hr)\n\n// LONGTERM put this declaration in a common file\nuiInitOptions uiprivOptions;\n\n#define wantedICCClasses ( \\\n\tICC_STANDARD_CLASSES |\t/* user32.dll controls */\t\t\\\n\tICC_PROGRESS_CLASS |\t\t/* progress bars */\t\t\t\\\n\tICC_TAB_CLASSES |\t\t\t/* tabs */\t\t\t\t\t\\\n\tICC_LISTVIEW_CLASSES |\t\t/* table headers */\t\t\t\\\n\tICC_UPDOWN_CLASS |\t\t/* spinboxes */\t\t\t\\\n\tICC_BAR_CLASSES |\t\t\t/* trackbar */\t\t\t\t\\\n\tICC_DATE_CLASSES |\t\t/* date/time picker */\t\t\\\n\t0)\n\nconst char *uiInit(uiInitOptions *o)\n{\n\tSTARTUPINFOW si;\n\tconst char *ce;\n\tHICON hDefaultIcon;\n\tHCURSOR hDefaultCursor;\n\tNONCLIENTMETRICSW ncm;\n\tINITCOMMONCONTROLSEX icc;\n\tHRESULT hr;\n\n\tuiprivOptions = *o;\n\n\tinitAlloc();\n\n\tnCmdShow = SW_SHOWDEFAULT;\n\tGetStartupInfoW(&si);\n\tif ((si.dwFlags & STARTF_USESHOWWINDOW) != 0)\n\t\tnCmdShow = si.wShowWindow;\n\n\t// LONGTERM set DPI awareness\n\n\thDefaultIcon = LoadIconW(NULL, IDI_APPLICATION);\n\tif (hDefaultIcon == NULL)\n\t\treturn ieLastErr(\"loading default icon for window classes\");\n\thDefaultCursor = LoadCursorW(NULL, IDC_ARROW);\n\tif (hDefaultCursor == NULL)\n\t\treturn ieLastErr(\"loading default cursor for window classes\");\n\n\tce = initUtilWindow(hDefaultIcon, hDefaultCursor);\n\tif (ce != NULL)\n\t\treturn initerr(ce, L\"GetLastError() ==\", GetLastError());\n\n\tif (registerWindowClass(hDefaultIcon, hDefaultCursor) == 0)\n\t\treturn ieLastErr(\"registering uiWindow window class\");\n\n\tZeroMemory(&ncm, sizeof (NONCLIENTMETRICSW));\n\tncm.cbSize = sizeof (NONCLIENTMETRICSW);\n\tif (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof (NONCLIENTMETRICSW), &ncm, sizeof (NONCLIENTMETRICSW)) == 0)\n\t\treturn ieLastErr(\"getting default fonts\");\n\thMessageFont = CreateFontIndirectW(&(ncm.lfMessageFont));\n\tif (hMessageFont == NULL)\n\t\treturn ieLastErr(\"loading default messagebox font; this is the default UI font\");\n\n\tif (initContainer(hDefaultIcon, hDefaultCursor) == 0)\n\t\treturn ieLastErr(\"initializing uiWindowsMakeContainer() window class\");\n\n\thollowBrush = (HBRUSH) GetStockObject(HOLLOW_BRUSH);\n\tif (hollowBrush == NULL)\n\t\treturn ieLastErr(\"getting hollow brush\");\n\n\tZeroMemory(&icc, sizeof (INITCOMMONCONTROLSEX));\n\ticc.dwSize = sizeof (INITCOMMONCONTROLSEX);\n\ticc.dwICC = wantedICCClasses;\n\tif (InitCommonControlsEx(&icc) == 0)\n\t\treturn ieLastErr(\"initializing Common Controls\");\n\n\thr = CoInitialize(NULL);\n\tif (hr != S_OK && hr != S_FALSE)\n\t\treturn ieHRESULT(\"initializing COM\", hr);\n\t// LONGTERM initialize COM security\n\t// LONGTERM (windows vista) turn off COM exception handling\n\n\thr = initDraw();\n\tif (hr != S_OK)\n\t\treturn ieHRESULT(\"initializing Direct2D\", hr);\n\n\thr = uiprivInitDrawText();\n\tif (hr != S_OK)\n\t\treturn ieHRESULT(\"initializing DirectWrite\", hr);\n\n\tif (registerAreaClass(hDefaultIcon, hDefaultCursor) == 0)\n\t\treturn ieLastErr(\"registering uiArea window class\");\n\n\tif (registerMessageFilter() == 0)\n\t\treturn ieLastErr(\"registering libui message filter\");\n\n\tif (registerD2DScratchClass(hDefaultIcon, hDefaultCursor) == 0)\n\t\treturn ieLastErr(\"initializing D2D scratch window class\");\n\n\thr = uiprivInitImage();\n\tif (hr != S_OK)\n\t\treturn ieHRESULT(\"initializing WIC\", hr);\n\n\treturn NULL;\n}\n\nvoid uiUninit(void)\n{\n\tuiprivUninitTimers();\n\tuiprivUninitImage();\n\tuninitMenus();\n\tunregisterD2DScratchClass();\n\tunregisterMessageFilter();\n\tunregisterArea();\n\tuiprivUninitDrawText();\n\tuninitDraw();\n\tCoUninitialize();\n\tif (DeleteObject(hollowBrush) == 0)\n\t\tlogLastError(L\"error freeing hollow brush\");\n\tuninitContainer();\n\tif (DeleteObject(hMessageFont) == 0)\n\t\tlogLastError(L\"error deleting control font\");\n\tunregisterWindowClass();\n\t// no need to delete the default icon or cursor; see http://stackoverflow.com/questions/30603077/\n\tuninitUtilWindow();\n\tuninitAlloc();\n}\n\nvoid uiFreeInitError(const char *err)\n{\n\tif (*(err - 1) == '-')\n\t\tuiprivFree((void *) (err - 1));\n}\n\nBOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)\n{\n\tif (fdwReason == DLL_PROCESS_ATTACH)\n\t\thInstance = hinstDLL;\n\treturn TRUE;\n}\n"
  },
  {
    "path": "windows/label.cpp",
    "content": "// 11 april 2015\n#include \"uipriv_windows.hpp\"\n\nstruct uiLabel {\n\tuiWindowsControl c;\n\tHWND hwnd;\n};\n\nuiWindowsControlAllDefaults(uiLabel)\n\n// via http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing\n#define labelHeight 8\n\nstatic void uiLabelMinimumSize(uiWindowsControl *c, int *width, int *height)\n{\n\tuiLabel *l = uiLabel(c);\n\tuiWindowsSizing sizing;\n\tint y;\n\n\t*width = uiWindowsWindowTextWidth(l->hwnd);\n\ty = labelHeight;\n\tuiWindowsGetSizing(l->hwnd, &sizing);\n\tuiWindowsSizingDlgUnitsToPixels(&sizing, NULL, &y);\n\t*height = y;\n}\n\nchar *uiLabelText(uiLabel *l)\n{\n\treturn uiWindowsWindowText(l->hwnd);\n}\n\nvoid uiLabelSetText(uiLabel *l, const char *text)\n{\n\tuiWindowsSetWindowText(l->hwnd, text);\n\t// changing the text might necessitate a change in the label's size\n\tuiWindowsControlMinimumSizeChanged(uiWindowsControl(l));\n}\n\nuiLabel *uiNewLabel(const char *text)\n{\n\tuiLabel *l;\n\tWCHAR *wtext;\n\n\tuiWindowsNewControl(uiLabel, l);\n\n\twtext = toUTF16(text);\n\tl->hwnd = uiWindowsEnsureCreateControlHWND(0,\n\t\tL\"static\", wtext,\n\t\t// SS_LEFTNOWORDWRAP clips text past the end; SS_NOPREFIX avoids accelerator translation\n\t\t// controls are vertically aligned to the top by default (thanks Xeek in irc.freenode.net/#winapi)\n\t\tSS_LEFTNOWORDWRAP | SS_NOPREFIX,\n\t\thInstance, NULL,\n\t\tTRUE);\n\tuiprivFree(wtext);\n\n\treturn l;\n}\n"
  },
  {
    "path": "windows/libui.manifest",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">\n<assemblyIdentity\n    version=\"1.0.0.0\"\n    processorArchitecture=\"*\"\n    name=\"CompanyName.ProductName.YourApplication\"\n    type=\"win32\"\n/>\n<description>Your application description here.</description>\n<dependency>\n    <dependentAssembly>\n        <assemblyIdentity\n            type=\"win32\"\n            name=\"Microsoft.Windows.Common-Controls\"\n            version=\"6.0.0.0\"\n            processorArchitecture=\"*\"\n            publicKeyToken=\"6595b64144ccf1df\"\n            language=\"*\"\n        />\n    </dependentAssembly>\n</dependency>\n<compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\">\n    <application>\n        <!--The ID below indicates application support for Windows Vista -->\n        <supportedOS Id=\"{e2011457-1546-43c5-a5fe-008deee3d3f0}\"/>\n        <!--The ID below indicates application support for Windows 7 -->\n        <supportedOS Id=\"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}\"/>\n    </application>\n</compatibility>\n</assembly>\n\n"
  },
  {
    "path": "windows/main.cpp",
    "content": "// 6 april 2015\n#include \"uipriv_windows.hpp\"\n\nstatic HHOOK filter;\n\nstatic LRESULT CALLBACK filterProc(int code, WPARAM wParam, LPARAM lParam)\n{\n\tMSG *msg = (MSG *) lParam;\n\n\tif (code < 0)\n\t\tgoto callNext;\n\n\tif (areaFilter(msg))\t\t// don't continue to our IsDialogMessage() hack if the area handled it\n\t\tgoto discard;\n\n\t// TODO IsDialogMessage() hack here\n\n\t// otherwise keep going\n\tgoto callNext;\n\ndiscard:\n\t// we handled it; discard the message so the dialog manager doesn't see it\n\treturn 1;\n\ncallNext:\n\treturn CallNextHookEx(filter, code, wParam, lParam);\n}\n\nint registerMessageFilter(void)\n{\n\tfilter = SetWindowsHookExW(WH_MSGFILTER,\n\t\tfilterProc,\n\t\thInstance,\n\t\tGetCurrentThreadId());\n\treturn filter != NULL;\n}\n\nvoid unregisterMessageFilter(void)\n{\n\tif (UnhookWindowsHookEx(filter) == 0)\n\t\tlogLastError(L\"error unregistering libui message filter\");\n}\n\n// LONGTERM http://blogs.msdn.com/b/oldnewthing/archive/2005/04/08/406509.aspx when adding accelerators, TranslateAccelerators() before IsDialogMessage()\n\nstatic void processMessage(MSG *msg)\n{\n\tHWND correctParent;\n\n\tif (msg->hwnd != NULL)\n\t\tcorrectParent = parentToplevel(msg->hwnd);\n\telse\t\t// just to be safe\n\t\tcorrectParent = GetActiveWindow();\n\tif (correctParent != NULL)\n\t\t// this calls our mesage filter above for us\n\t\tif (IsDialogMessage(correctParent, msg) != 0)\n\t\t\treturn;\n\tTranslateMessage(msg);\n\tDispatchMessageW(msg);\n}\n\nstatic int waitMessage(MSG *msg)\n{\n\tint res;\n\n\tres = GetMessageW(msg, NULL, 0, 0);\n\tif (res < 0) {\n\t\tlogLastError(L\"error calling GetMessage()\");\n\t\treturn 0;\t\t// bail out on error\n\t}\n\treturn res != 0;\t\t// returns false on WM_QUIT\n}\n\nvoid uiMain(void)\n{\n\twhile (uiMainStep(1))\n\t\t;\n}\n\nvoid uiMainSteps(void)\n{\n\t// don't need to do anything here\n}\n\nstatic int peekMessage(MSG *msg)\n{\n\tBOOL res;\n\n\tres = PeekMessageW(msg, NULL, 0, 0, PM_REMOVE);\n\tif (res == 0)\n\t\treturn 2;\t\t// no message available\n\tif (msg->message != WM_QUIT)\n\t\treturn 1;\t\t// a message\n\treturn 0;\t\t\t// WM_QUIT\n}\n\nint uiMainStep(int wait)\n{\n\tMSG msg;\n\n\tif (wait) {\n\t\tif (!waitMessage(&msg))\n\t\t\treturn 0;\n\t\tprocessMessage(&msg);\n\t\treturn 1;\n\t}\n\n\t// don't wait for a message\n\tswitch (peekMessage(&msg)) {\n\tcase 0:\t\t// quit\n\t\t// TODO PostQuitMessage() again?\n\t\treturn 0;\n\tcase 1:\t\t// process a message\n\t\tprocessMessage(&msg);\n\t\t// fall out to the case for no message\n\t}\n\treturn 1;\t\t// no message\n}\n\nvoid uiQuit(void)\n{\n\tPostQuitMessage(0);\n}\n\nvoid uiQueueMain(void (*f)(void *data), void *data)\n{\n\tif (PostMessageW(utilWindow, msgQueued, (WPARAM) f, (LPARAM) data) == 0)\n\t\t// LONGTERM this is likely not safe to call across threads (allocates memory)\n\t\tlogLastError(L\"error queueing function to run on main thread\");\n}\n\nstatic std::map<uiprivTimer *, bool> timers;\n\nvoid uiTimer(int milliseconds, int (*f)(void *data), void *data)\n{\n\tuiprivTimer *timer;\n\n\ttimer = uiprivNew(uiprivTimer);\n\ttimer->f = f;\n\ttimer->data = data;\n\t// 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\n\tif (SetTimer(utilWindow, (UINT_PTR) timer, milliseconds, NULL) == 0)\n\t\tlogLastError(L\"error calling SetTimer() in uiTimer()\");\n\ttimers[timer] = true;\n}\n\nvoid uiprivFreeTimer(uiprivTimer *t)\n{\n\ttimers.erase(t);\n\tuiprivFree(t);\n}\n\n// since timers use uiprivAlloc(), we have to clean them up in uiUninit(), or else we'll get dangling allocation errors\nvoid uiprivUninitTimers(void)\n{\n\t// TODO why doesn't auto t : timers work?\n\tfor (auto t = timers.begin(); t != timers.end(); t++)\n\t\tuiprivFree(t->first);\n\ttimers.clear();\n}\n"
  },
  {
    "path": "windows/menu.cpp",
    "content": "// 24 april 2015\n#include \"uipriv_windows.hpp\"\n\n// LONGTERM migrate to std::vector\n\nstatic uiMenu **menus = NULL;\nstatic size_t len = 0;\nstatic size_t cap = 0;\nstatic BOOL menusFinalized = FALSE;\nstatic WORD curID = 100;\t\t\t// start somewhere safe\nstatic BOOL hasQuit = FALSE;\nstatic BOOL hasPreferences = FALSE;\nstatic BOOL hasAbout = FALSE;\n\nstruct uiMenu {\n\tWCHAR *name;\n\tuiMenuItem **items;\n\tsize_t len;\n\tsize_t cap;\n};\n\nstruct uiMenuItem {\n\tWCHAR *name;\n\tint type;\n\tWORD id;\n\tvoid (*onClicked)(uiMenuItem *, uiWindow *, void *);\n\tvoid *onClickedData;\n\tBOOL disabled;\t\t\t\t// template for new instances; kept in sync with everything else\n\tBOOL checked;\n\tHMENU *hmenus;\n\tsize_t len;\n\tsize_t cap;\n};\n\nenum {\n\ttypeRegular,\n\ttypeCheckbox,\n\ttypeQuit,\n\ttypePreferences,\n\ttypeAbout,\n\ttypeSeparator,\n};\n\n#define grow 32\n\nstatic void sync(uiMenuItem *item)\n{\n\tsize_t i;\n\tMENUITEMINFOW mi;\n\n\tZeroMemory(&mi, sizeof (MENUITEMINFOW));\n\tmi.cbSize = sizeof (MENUITEMINFOW);\n\tmi.fMask = MIIM_STATE;\n\tif (item->disabled)\n\t\tmi.fState |= MFS_DISABLED;\n\tif (item->checked)\n\t\tmi.fState |= MFS_CHECKED;\n\n\tfor (i = 0; i < item->len; i++)\n\t\tif (SetMenuItemInfo(item->hmenus[i], item->id, FALSE, &mi) == 0)\n\t\t\tlogLastError(L\"error synchronizing menu items\");\n}\n\nstatic void defaultOnClicked(uiMenuItem *item, uiWindow *w, void *data)\n{\n\t// do nothing\n}\n\nstatic void onQuitClicked(uiMenuItem *item, uiWindow *w, void *data)\n{\n\tif (uiprivShouldQuit())\n\t\tuiQuit();\n}\n\nvoid uiMenuItemEnable(uiMenuItem *i)\n{\n\ti->disabled = FALSE;\n\tsync(i);\n}\n\nvoid uiMenuItemDisable(uiMenuItem *i)\n{\n\ti->disabled = TRUE;\n\tsync(i);\n}\n\nvoid uiMenuItemOnClicked(uiMenuItem *i, void (*f)(uiMenuItem *, uiWindow *, void *), void *data)\n{\n\tif (i->type == typeQuit)\n\t\tuiprivUserBug(\"You can not call uiMenuItemOnClicked() on a Quit item; use uiOnShouldQuit() instead.\");\n\ti->onClicked = f;\n\ti->onClickedData = data;\n}\n\nint uiMenuItemChecked(uiMenuItem *i)\n{\n\treturn i->checked != FALSE;\n}\n\nvoid uiMenuItemSetChecked(uiMenuItem *i, int checked)\n{\n\t// use explicit values\n\ti->checked = FALSE;\n\tif (checked)\n\t\ti->checked = TRUE;\n\tsync(i);\n}\n\nstatic uiMenuItem *newItem(uiMenu *m, int type, const char *name)\n{\n\tuiMenuItem *item;\n\n\tif (menusFinalized)\n\t\tuiprivUserBug(\"You can not create a new menu item after menus have been finalized.\");\n\n\tif (m->len >= m->cap) {\n\t\tm->cap += grow;\n\t\tm->items = (uiMenuItem **) uiprivRealloc(m->items, m->cap * sizeof (uiMenuItem *), \"uiMenuitem *[]\");\n\t}\n\n\titem = uiprivNew(uiMenuItem);\n\n\tm->items[m->len] = item;\n\tm->len++;\n\n\titem->type = type;\n\tswitch (item->type) {\n\tcase typeQuit:\n\t\titem->name = toUTF16(\"Quit\");\n\t\tbreak;\n\tcase typePreferences:\n\t\titem->name = toUTF16(\"Preferences...\");\n\t\tbreak;\n\tcase typeAbout:\n\t\titem->name = toUTF16(\"About\");\n\t\tbreak;\n\tcase typeSeparator:\n\t\tbreak;\n\tdefault:\n\t\titem->name = toUTF16(name);\n\t\tbreak;\n\t}\n\n\tif (item->type != typeSeparator) {\n\t\titem->id = curID;\n\t\tcurID++;\n\t}\n\n\tif (item->type == typeQuit) {\n\t\t// can't call uiMenuItemOnClicked() here\n\t\titem->onClicked = onQuitClicked;\n\t\titem->onClickedData = NULL;\n\t} else\n\t\tuiMenuItemOnClicked(item, defaultOnClicked, NULL);\n\n\treturn item;\n}\n\nuiMenuItem *uiMenuAppendItem(uiMenu *m, const char *name)\n{\n\treturn newItem(m, typeRegular, name);\n}\n\nuiMenuItem *uiMenuAppendCheckItem(uiMenu *m, const char *name)\n{\n\treturn newItem(m, typeCheckbox, name);\n}\n\nuiMenuItem *uiMenuAppendQuitItem(uiMenu *m)\n{\n\tif (hasQuit)\n\t\tuiprivUserBug(\"You can not have multiple Quit menu items in a program.\");\n\thasQuit = TRUE;\n\tnewItem(m, typeSeparator, NULL);\n\treturn newItem(m, typeQuit, NULL);\n}\n\nuiMenuItem *uiMenuAppendPreferencesItem(uiMenu *m)\n{\n\tif (hasPreferences)\n\t\tuiprivUserBug(\"You can not have multiple Preferences menu items in a program.\");\n\thasPreferences = TRUE;\n\tnewItem(m, typeSeparator, NULL);\n\treturn newItem(m, typePreferences, NULL);\n}\n\nuiMenuItem *uiMenuAppendAboutItem(uiMenu *m)\n{\n\tif (hasAbout)\n\t\t// TODO place these uiprivImplBug() and uiprivUserBug() strings in a header\n\t\tuiprivUserBug(\"You can not have multiple About menu items in a program.\");\n\thasAbout = TRUE;\n\tnewItem(m, typeSeparator, NULL);\n\treturn newItem(m, typeAbout, NULL);\n}\n\nvoid uiMenuAppendSeparator(uiMenu *m)\n{\n\tnewItem(m, typeSeparator, NULL);\n}\n\nuiMenu *uiNewMenu(const char *name)\n{\n\tuiMenu *m;\n\n\tif (menusFinalized)\n\t\tuiprivUserBug(\"You can not create a new menu after menus have been finalized.\");\n\tif (len >= cap) {\n\t\tcap += grow;\n\t\tmenus = (uiMenu **) uiprivRealloc(menus, cap * sizeof (uiMenu *), \"uiMenu *[]\");\n\t}\n\n\tm = uiprivNew(uiMenu);\n\n\tmenus[len] = m;\n\tlen++;\n\n\tm->name = toUTF16(name);\n\n\treturn m;\n}\n\nstatic void appendMenuItem(HMENU menu, uiMenuItem *item)\n{\n\tUINT uFlags;\n\n\tuFlags = MF_SEPARATOR;\n\tif (item->type != typeSeparator) {\n\t\tuFlags = MF_STRING;\n\t\tif (item->disabled)\n\t\t\tuFlags |= MF_DISABLED | MF_GRAYED;\n\t\tif (item->checked)\n\t\t\tuFlags |= MF_CHECKED;\n\t}\n\tif (AppendMenuW(menu, uFlags, item->id, item->name) == 0)\n\t\tlogLastError(L\"error appending menu item\");\n\n\tif (item->len >= item->cap) {\n\t\titem->cap += grow;\n\t\titem->hmenus = (HMENU *) uiprivRealloc(item->hmenus, item->cap * sizeof (HMENU), \"HMENU[]\");\n\t}\n\titem->hmenus[item->len] = menu;\n\titem->len++;\n}\n\nstatic HMENU makeMenu(uiMenu *m)\n{\n\tHMENU menu;\n\tsize_t i;\n\n\tmenu = CreatePopupMenu();\n\tif (menu == NULL)\n\t\tlogLastError(L\"error creating menu\");\n\tfor (i = 0; i < m->len; i++)\n\t\tappendMenuItem(menu, m->items[i]);\n\treturn menu;\n}\n\nHMENU makeMenubar(void)\n{\n\tHMENU menubar;\n\tHMENU menu;\n\tsize_t i;\n\n\tmenusFinalized = TRUE;\n\n\tmenubar = CreateMenu();\n\tif (menubar == NULL)\n\t\tlogLastError(L\"error creating menubar\");\n\n\tfor (i = 0; i < len; i++) {\n\t\tmenu = makeMenu(menus[i]);\n\t\tif (AppendMenuW(menubar, MF_POPUP | MF_STRING, (UINT_PTR) menu, menus[i]->name) == 0)\n\t\t\tlogLastError(L\"error appending menu to menubar\");\n\t}\n\n\treturn menubar;\n}\n\nvoid runMenuEvent(WORD id, uiWindow *w)\n{\n\tuiMenu *m;\n\tuiMenuItem *item;\n\tsize_t i, j;\n\n\t// this isn't optimal, but it works, and it should be just fine for most cases\n\tfor (i = 0; i < len; i++) {\n\t\tm = menus[i];\n\t\tfor (j = 0; j < m->len; j++) {\n\t\t\titem = m->items[j];\n\t\t\tif (item->id == id)\n\t\t\t\tgoto found;\n\t\t}\n\t}\n\t// no match\n\tuiprivImplBug(\"unknown menu ID %hu in runMenuEvent()\", id);\n\nfound:\n\t// first toggle checkboxes, if any\n\tif (item->type == typeCheckbox)\n\t\tuiMenuItemSetChecked(item, !uiMenuItemChecked(item));\n\n\t// then run the event\n\t(*(item->onClicked))(item, w, item->onClickedData);\n}\n\nstatic void freeMenu(uiMenu *m, HMENU submenu)\n{\n\tsize_t i;\n\tuiMenuItem *item;\n\tsize_t j;\n\n\tfor (i = 0; i < m->len; i++) {\n\t\titem = m->items[i];\n\t\tfor (j = 0; j < item->len; j++)\n\t\t\tif (item->hmenus[j] == submenu)\n\t\t\t\tbreak;\n\t\tif (j >= item->len)\n\t\t\tuiprivImplBug(\"submenu handle %p not found in freeMenu()\", submenu);\n\t\tfor (; j < item->len - 1; j++)\n\t\t\titem->hmenus[j] = item->hmenus[j + 1];\n\t\titem->hmenus[j] = NULL;\n\t\titem->len--;\n\t}\n}\n\nvoid freeMenubar(HMENU menubar)\n{\n\tsize_t i;\n\tMENUITEMINFOW mi;\n\n\tfor (i = 0; i < len; i++) {\n\t\tZeroMemory(&mi, sizeof (MENUITEMINFOW));\n\t\tmi.cbSize = sizeof (MENUITEMINFOW);\n\t\tmi.fMask = MIIM_SUBMENU;\n\t\tif (GetMenuItemInfoW(menubar, i, TRUE, &mi) == 0)\n\t\t\tlogLastError(L\"error getting menu to delete item references from\");\n\t\tfreeMenu(menus[i], mi.hSubMenu);\n\t}\n\t// no need to worry about destroying any menus; destruction of the window they're in will do it for us\n}\n\nvoid uninitMenus(void)\n{\n\tuiMenu *m;\n\tuiMenuItem *item;\n\tsize_t i, j;\n\n\tfor (i = 0; i < len; i++) {\n\t\tm = menus[i];\n\t\tuiprivFree(m->name);\n\t\tfor (j = 0; j < m->len; j++) {\n\t\t\titem = m->items[j];\n\t\t\tif (item->len != 0)\n\t\t\t\t// LONGTERM uiprivUserBug()?\n\t\t\t\tuiprivImplBug(\"menu item %p (%ws) still has uiWindows attached; did you forget to destroy some windows?\", item, item->name);\n\t\t\tif (item->name != NULL)\n\t\t\t\tuiprivFree(item->name);\n\t\t\tif (item->hmenus != NULL)\n\t\t\t\tuiprivFree(item->hmenus);\n\t\t\tuiprivFree(item);\n\t\t}\n\t\tif (m->items != NULL)\n\t\t\tuiprivFree(m->items);\n\t\tuiprivFree(m);\n\t}\n\tif (menus != NULL)\n\t\tuiprivFree(menus);\n}\n"
  },
  {
    "path": "windows/meson.build",
    "content": "# 23 march 2019\n\nwindows = import('windows')\n\nlibui_sources += [\n\t'windows/alloc.cpp',\n\t'windows/area.cpp',\n\t'windows/areadraw.cpp',\n\t'windows/areaevents.cpp',\n\t'windows/areascroll.cpp',\n\t'windows/areautil.cpp',\n\t'windows/attrstr.cpp',\n\t'windows/box.cpp',\n\t'windows/button.cpp',\n\t'windows/checkbox.cpp',\n\t'windows/colorbutton.cpp',\n\t'windows/colordialog.cpp',\n\t'windows/combobox.cpp',\n\t'windows/container.cpp',\n\t'windows/control.cpp',\n\t'windows/d2dscratch.cpp',\n\t'windows/datetimepicker.cpp',\n\t'windows/debug.cpp',\n\t'windows/draw.cpp',\n\t'windows/drawmatrix.cpp',\n\t'windows/drawpath.cpp',\n\t'windows/drawtext.cpp',\n\t'windows/dwrite.cpp',\n\t'windows/editablecombo.cpp',\n\t'windows/entry.cpp',\n\t'windows/events.cpp',\n\t'windows/fontbutton.cpp',\n\t'windows/fontdialog.cpp',\n\t'windows/fontmatch.cpp',\n\t'windows/form.cpp',\n\t'windows/graphemes.cpp',\n\t'windows/grid.cpp',\n\t'windows/group.cpp',\n\t'windows/image.cpp',\n\t'windows/init.cpp',\n\t'windows/label.cpp',\n\t'windows/main.cpp',\n\t'windows/menu.cpp',\n\t'windows/multilineentry.cpp',\n\t'windows/opentype.cpp',\n\t'windows/parent.cpp',\n\t'windows/progressbar.cpp',\n\t'windows/radiobuttons.cpp',\n\t'windows/separator.cpp',\n\t'windows/sizing.cpp',\n\t'windows/slider.cpp',\n\t'windows/spinbox.cpp',\n\t'windows/stddialogs.cpp',\n\t'windows/tab.cpp',\n\t'windows/table.cpp',\n\t'windows/tabledispinfo.cpp',\n\t'windows/tabledraw.cpp',\n\t'windows/tableediting.cpp',\n\t'windows/tablemetrics.cpp',\n\t'windows/tabpage.cpp',\n\t'windows/text.cpp',\n\t'windows/utf16.cpp',\n\t'windows/utilwin.cpp',\n\t'windows/window.cpp',\n\t'windows/winpublic.cpp',\n\t'windows/winutil.cpp',\n]\n\n# resources.rc only contains the libui manifest.\n# For a DLL, we have to include this directly, so we do so.\n# Windows won't link resources in static libraries, so including this would have no effect.\n# 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).\nif libui_mode == 'shared'\n\tlibui_sources += [\n\t\twindows.compile_resources('resources.rc',\n\t\t\targs: libui_manifest_args,\n\t\t\tdepend_files: ['libui.manifest']),\n\t]\nendif\n\n# TODO prune this list\nforeach lib : ['user32', 'kernel32', 'gdi32', 'comctl32', 'uxtheme', 'msimg32', 'comdlg32', 'd2d1', 'dwrite', 'ole32', 'oleaut32', 'oleacc', 'uuid', 'windowscodecs']\n\tlibui_deps += [\n\t\tmeson.get_compiler('cpp').find_library(lib,\n\t\t\trequired: true),\n\t]\nendforeach\n\nif libui_OS == 'windows' and libui_mode == 'shared' and not libui_MSVC\n\terror('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.')\nendif\n"
  },
  {
    "path": "windows/multilineentry.cpp",
    "content": "// 8 april 2015\n#include \"uipriv_windows.hpp\"\n\n// TODO there's alpha darkening of text going on in read-only ones; something is up in our parent logic\n\nstruct uiMultilineEntry {\n\tuiWindowsControl c;\n\tHWND hwnd;\n\tvoid (*onChanged)(uiMultilineEntry *, void *);\n\tvoid *onChangedData;\n\tBOOL inhibitChanged;\n};\n\nstatic BOOL onWM_COMMAND(uiControl *c, HWND hwnd, WORD code, LRESULT *lResult)\n{\n\tuiMultilineEntry *e = uiMultilineEntry(c);\n\n\tif (code != EN_CHANGE)\n\t\treturn FALSE;\n\tif (e->inhibitChanged)\n\t\treturn FALSE;\n\t(*(e->onChanged))(e, e->onChangedData);\n\t*lResult = 0;\n\treturn TRUE;\n}\n\nstatic void uiMultilineEntryDestroy(uiControl *c)\n{\n\tuiMultilineEntry *e = uiMultilineEntry(c);\n\n\tuiWindowsUnregisterWM_COMMANDHandler(e->hwnd);\n\tuiWindowsEnsureDestroyWindow(e->hwnd);\n\tuiFreeControl(uiControl(e));\n}\n\nuiWindowsControlAllDefaultsExceptDestroy(uiMultilineEntry)\n\n// from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing\n#define entryWidth 107 /* this is actually the shorter progress bar width, but Microsoft only indicates as wide as necessary */\n// LONGTERM change this for multiline text boxes (longterm because how?)\n#define entryHeight 14\n\nstatic void uiMultilineEntryMinimumSize(uiWindowsControl *c, int *width, int *height)\n{\n\tuiMultilineEntry *e = uiMultilineEntry(c);\n\tuiWindowsSizing sizing;\n\tint x, y;\n\n\tx = entryWidth;\n\ty = entryHeight;\n\tuiWindowsGetSizing(e->hwnd, &sizing);\n\tuiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y);\n\t*width = x;\n\t*height = y;\n}\n\nstatic void defaultOnChanged(uiMultilineEntry *e, void *data)\n{\n\t// do nothing\n}\n\nchar *uiMultilineEntryText(uiMultilineEntry *e)\n{\n\tchar *out;\n\n\tout = uiWindowsWindowText(e->hwnd);\n\tCRLFtoLF(out);\n\treturn out;\n}\n\nvoid uiMultilineEntrySetText(uiMultilineEntry *e, const char *text)\n{\n\tchar *crlf;\n\n\t// doing this raises an EN_CHANGED\n\te->inhibitChanged = TRUE;\n\tcrlf = LFtoCRLF(text);\n\tuiWindowsSetWindowText(e->hwnd, crlf);\n\tuiprivFree(crlf);\n\te->inhibitChanged = FALSE;\n\t// don't queue the control for resize; entry sizes are independent of their contents\n}\n\nvoid uiMultilineEntryAppend(uiMultilineEntry *e, const char *text)\n{\n\tLRESULT n;\n\tchar *crlf;\n\tWCHAR *wtext;\n\n\t// doing this raises an EN_CHANGED\n\te->inhibitChanged = TRUE;\n\t// TODO preserve selection? caret? what if caret used to be at end?\n\t// TODO scroll to bottom?\n\tn = SendMessageW(e->hwnd, WM_GETTEXTLENGTH, 0, 0);\n\tSendMessageW(e->hwnd, EM_SETSEL, n, n);\n\tcrlf = LFtoCRLF(text);\n\twtext = toUTF16(crlf);\n\tuiprivFree(crlf);\n\tSendMessageW(e->hwnd, EM_REPLACESEL, FALSE, (LPARAM) wtext);\n\tuiprivFree(wtext);\n\te->inhibitChanged = FALSE;\n}\n\nvoid uiMultilineEntryOnChanged(uiMultilineEntry *e, void (*f)(uiMultilineEntry *, void *), void *data)\n{\n\te->onChanged = f;\n\te->onChangedData = data;\n}\n\nint uiMultilineEntryReadOnly(uiMultilineEntry *e)\n{\n\treturn (getStyle(e->hwnd) & ES_READONLY) != 0;\n}\n\nvoid uiMultilineEntrySetReadOnly(uiMultilineEntry *e, int readonly)\n{\n\tWPARAM ro;\n\n\tro = (WPARAM) FALSE;\n\tif (readonly)\n\t\tro = (WPARAM) TRUE;\n\tif (SendMessage(e->hwnd, EM_SETREADONLY, ro, 0) == 0)\n\t\tlogLastError(L\"error making uiMultilineEntry read-only\");\n}\n\nstatic uiMultilineEntry *finishMultilineEntry(DWORD style)\n{\n\tuiMultilineEntry *e;\n\n\tuiWindowsNewControl(uiMultilineEntry, e);\n\n\te->hwnd = uiWindowsEnsureCreateControlHWND(WS_EX_CLIENTEDGE,\n\t\tL\"edit\", L\"\",\n\t\tES_AUTOVSCROLL | ES_LEFT | ES_MULTILINE | ES_NOHIDESEL | ES_WANTRETURN | WS_TABSTOP | WS_VSCROLL | style,\n\t\thInstance, NULL,\n\t\tTRUE);\n\n\tuiWindowsRegisterWM_COMMANDHandler(e->hwnd, onWM_COMMAND, uiControl(e));\n\tuiMultilineEntryOnChanged(e, defaultOnChanged, NULL);\n\n\treturn e;\n}\n\nuiMultilineEntry *uiNewMultilineEntry(void)\n{\n\treturn finishMultilineEntry(0);\n}\n\nuiMultilineEntry *uiNewNonWrappingMultilineEntry(void)\n{\n\treturn finishMultilineEntry(WS_HSCROLL | ES_AUTOHSCROLL);\n}\n"
  },
  {
    "path": "windows/notes",
    "content": "DIALOGS\ndo not accelerate OK and Cancel buttons in dialogs\n\thttp://blogs.msdn.com/b/oldnewthing/archive/2008/05/08/8467905.aspx\n"
  },
  {
    "path": "windows/opentype.cpp",
    "content": "// 11 may 2017\n#include \"uipriv_windows.hpp\"\n#include \"attrstr.hpp\"\n\n// TODO pull out my decision for empty uiOpenTypeFeatures, assuming that it isn't in another file or that I even made one\n\nstatic uiForEach addToTypography(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value, void *data)\n{\n\tIDWriteTypography *dt = (IDWriteTypography *) data;\n\tDWRITE_FONT_FEATURE dff;\n\tHRESULT hr;\n\n\tZeroMemory(&dff, sizeof (DWRITE_FONT_FEATURE));\n\t// yes, the cast here is necessary (the compiler will complain otherwise)...\n\tdff.nameTag = (DWRITE_FONT_FEATURE_TAG) DWRITE_MAKE_OPENTYPE_TAG(a, b, c, d);\n\tdff.parameter = (UINT32) value;\n\thr = dt->AddFontFeature(dff);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error adding OpenType feature to IDWriteTypography\", hr);\n\treturn uiForEachContinue;\n}\n\nIDWriteTypography *uiprivOpenTypeFeaturesToIDWriteTypography(const uiOpenTypeFeatures *otf)\n{\n\tIDWriteTypography *dt;\n\tHRESULT hr;\n\n\thr = dwfactory->CreateTypography(&dt);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error creating IDWriteTypography\", hr);\n\tuiOpenTypeFeaturesForEach(otf, addToTypography, dt);\n\treturn dt;\n}\n"
  },
  {
    "path": "windows/parent.cpp",
    "content": "// 26 april 2015\n#include \"uipriv_windows.hpp\"\n\n// This contains code used by all uiControls that contain other controls.\n// It also contains the code to draw the background of a container.c container, as that is a variant of the WM_CTLCOLORxxx handler code.\n\nstatic HBRUSH parentBrush = NULL;\n\nstatic HWND parentWithBackground(HWND hwnd)\n{\n\tHWND parent;\n\tint cls;\n\n\tparent = hwnd;\n\tfor (;;) {\n\t\tparent = parentOf(parent);\n\t\t// skip groupboxes; they're (supposed to be) transparent\n\t\t// skip uiContainers; they don't draw anything\n\t\tcls = windowClassOf(parent, L\"button\", containerClass, NULL);\n\t\tif (cls != 0 && cls != 1)\n\t\t\tbreak;\n\t}\n\treturn parent;\n}\n\nstruct parentDraw {\n\tHDC cdc;\n\tHBITMAP bitmap;\n\tHBITMAP prevbitmap;\n};\n\nstatic HRESULT parentDraw(HDC dc, HWND parent, struct parentDraw *pd)\n{\n\tRECT r;\n\n\tuiWindowsEnsureGetClientRect(parent, &r);\n\tpd->cdc = CreateCompatibleDC(dc);\n\tif (pd->cdc == NULL)\n\t\treturn logLastError(L\"error creating compatible DC\");\n\tpd->bitmap = CreateCompatibleBitmap(dc, r.right - r.left, r.bottom - r.top);\n\tif (pd->bitmap == NULL)\n\t\treturn logLastError(L\"error creating compatible bitmap\");\n\tpd->prevbitmap = (HBITMAP) SelectObject(pd->cdc, pd->bitmap);\n\tif (pd->prevbitmap == NULL)\n\t\treturn logLastError(L\"error selecting bitmap into compatible DC\");\n\tSendMessageW(parent, WM_PRINTCLIENT, (WPARAM) (pd->cdc), PRF_CLIENT);\n\treturn S_OK;\n}\n\nstatic void endParentDraw(struct parentDraw *pd)\n{\n\t// continue in case of any error\n\tif (pd->prevbitmap != NULL)\n\t\tif (((HBITMAP) SelectObject(pd->cdc, pd->prevbitmap)) != pd->bitmap)\n\t\t\tlogLastError(L\"error selecting previous bitmap back into compatible DC\");\n\tif (pd->bitmap != NULL)\n\t\tif (DeleteObject(pd->bitmap) == 0)\n\t\t\tlogLastError(L\"error deleting compatible bitmap\");\n\tif (pd->cdc != NULL)\n\t\tif (DeleteDC(pd->cdc) == 0)\n\t\t\tlogLastError(L\"error deleting compatible DC\");\n}\n\n// see http://www.codeproject.com/Articles/5978/Correctly-drawn-themed-dialogs-in-WinXP\nstatic HBRUSH getControlBackgroundBrush(HWND hwnd, HDC dc)\n{\n\tHWND parent;\n\tRECT hwndScreenRect;\n\tstruct parentDraw pd;\n\tHBRUSH brush;\n\tHRESULT hr;\n\n\tparent = parentWithBackground(hwnd);\n\n\thr = parentDraw(dc, parent, &pd);\n\tif (hr != S_OK)\n\t\treturn NULL;\n\tbrush = CreatePatternBrush(pd.bitmap);\n\tif (brush == NULL) {\n\t\tlogLastError(L\"error creating pattern brush\");\n\t\tendParentDraw(&pd);\n\t\treturn NULL;\n\t}\n\tendParentDraw(&pd);\n\n\t// now figure out where the control is relative to the parent so we can align the brush properly\n\t// if anything fails, give up and return the brush as-is\n\tuiWindowsEnsureGetWindowRect(hwnd, &hwndScreenRect);\n\t// this will be in screen coordinates; convert to parent coordinates\n\tmapWindowRect(NULL, parent, &hwndScreenRect);\n\tif (SetBrushOrgEx(dc, -hwndScreenRect.left, -hwndScreenRect.top, NULL) == 0)\n\t\tlogLastError(L\"error setting brush origin\");\n\n\treturn brush;\n}\n\nvoid paintContainerBackground(HWND hwnd, HDC dc, RECT *paintRect)\n{\n\tHWND parent;\n\tRECT paintRectParent;\n\tstruct parentDraw pd;\n\tHRESULT hr;\n\n\tparent = parentWithBackground(hwnd);\n\thr = parentDraw(dc, parent, &pd);\n\tif (hr != S_OK)\t\t// we couldn't get it; draw nothing\n\t\treturn;\n\n\tpaintRectParent = *paintRect;\n\tmapWindowRect(hwnd, parent, &paintRectParent);\n\tif (BitBlt(dc, paintRect->left, paintRect->top, paintRect->right - paintRect->left, paintRect->bottom - paintRect->top,\n\t\tpd.cdc, paintRectParent.left, paintRectParent.top,\n\t\tSRCCOPY) == 0)\n\t\tlogLastError(L\"error drawing parent background over uiContainer\");\n\n\tendParentDraw(&pd);\n}\n\n// TODO make this public if we want custom containers\n// why have this to begin with? http://blogs.msdn.com/b/oldnewthing/archive/2010/03/16/9979112.aspx\nBOOL handleParentMessages(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *lResult)\n{\n\tswitch (uMsg) {\n\tcase WM_COMMAND:\n\t\treturn runWM_COMMAND(wParam, lParam, lResult);\n\tcase WM_NOTIFY:\n\t\treturn runWM_NOTIFY(wParam, lParam, lResult);\n\tcase WM_HSCROLL:\n\t\treturn runWM_HSCROLL(wParam, lParam, lResult);\n\tcase WM_CTLCOLORSTATIC:\n\tcase WM_CTLCOLORBTN:\n\t\tif (parentBrush != NULL)\n\t\t\tif (DeleteObject(parentBrush) == 0)\n\t\t\t\tlogLastError(L\"error deleting old background brush()\");\t\t// but continue anyway; we will leak a brush but whatever\n\t\tif (SetBkMode((HDC) wParam, TRANSPARENT) == 0)\n\t\t\tlogLastError(L\"error setting transparent background mode to controls\");\t\t// but continue anyway; text will be wrong\n\t\tparentBrush = getControlBackgroundBrush((HWND) lParam, (HDC) wParam);\n\t\tif (parentBrush == NULL)\t\t// failed; just do default behavior\n\t\t\treturn FALSE;\n\t\t*lResult = (LRESULT) parentBrush;\n\t\treturn TRUE;\n\t}\n\treturn FALSE;\n}\n"
  },
  {
    "path": "windows/progressbar.cpp",
    "content": "// 19 may 2015\n#include \"uipriv_windows.hpp\"\n\nstruct uiProgressBar {\n\tuiWindowsControl c;\n\tHWND hwnd;\n};\n\nuiWindowsControlAllDefaults(uiProgressBar)\n\n// via http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing\n#define pbarWidth 237\n#define pbarHeight 8\n\nstatic void uiProgressBarMinimumSize(uiWindowsControl *c, int *width, int *height)\n{\n\tuiProgressBar *p = uiProgressBar(c);\n\tuiWindowsSizing sizing;\n\tint x, y;\n\n\tx = pbarWidth;\n\ty = pbarHeight;\n\tuiWindowsGetSizing(p->hwnd, &sizing);\n\tuiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y);\n\t*width = x;\n\t*height = y;\n}\n\n#define indeterminate(p) ((getStyle(p->hwnd) & PBS_MARQUEE) != 0)\n\nint uiProgressBarValue(uiProgressBar *p)\n{\n\tif (indeterminate(p))\n\t\treturn -1;\n\treturn SendMessage(p->hwnd, PBM_GETPOS, 0, 0);\n}\n\n// unfortunately, as of Vista progress bars have a forced animation on increase\n// we have to set the progress bar to value + 1 and decrease it back to value if we want an \"instant\" change\n// see http://stackoverflow.com/questions/2217688/windows-7-aero-theme-progress-bar-bug\n// it's not ideal/perfect, but it will have to do\nvoid uiProgressBarSetValue(uiProgressBar *p, int value)\n{\n\tif (value == -1) {\n\t\tif (!indeterminate(p)) {\n\t\t\tsetStyle(p->hwnd, getStyle(p->hwnd) | PBS_MARQUEE);\n\t\t\tSendMessageW(p->hwnd, PBM_SETMARQUEE, (WPARAM) TRUE, 0);\n\t\t}\n\t\treturn;\n\t}\n\tif (indeterminate(p)) {\n\t\tSendMessageW(p->hwnd, PBM_SETMARQUEE, (WPARAM) FALSE, 0);\n\t\tsetStyle(p->hwnd, getStyle(p->hwnd) & ~PBS_MARQUEE);\n\t}\n\n\tif (value < 0 || value > 100)\n\t\tuiprivUserBug(\"Value %d is out of range for uiProgressBars.\", value);\n\n\tif (value == 100) {\t\t\t// because we can't 101\n\t\tSendMessageW(p->hwnd, PBM_SETRANGE32, 0, 101);\n\t\tSendMessageW(p->hwnd, PBM_SETPOS, 101, 0);\n\t\tSendMessageW(p->hwnd, PBM_SETPOS, 100, 0);\n\t\tSendMessageW(p->hwnd, PBM_SETRANGE32, 0, 100);\n\t\treturn;\n\t}\n\tSendMessageW(p->hwnd, PBM_SETPOS, (WPARAM) (value + 1), 0);\n\tSendMessageW(p->hwnd, PBM_SETPOS, (WPARAM) value, 0);\n}\n\nuiProgressBar *uiNewProgressBar(void)\n{\n\tuiProgressBar *p;\n\n\tuiWindowsNewControl(uiProgressBar, p);\n\n\tp->hwnd = uiWindowsEnsureCreateControlHWND(0,\n\t\tPROGRESS_CLASSW, L\"\",\n\t\tPBS_SMOOTH,\n\t\thInstance, NULL,\n\t\tFALSE);\n\n\treturn p;\n}\n"
  },
  {
    "path": "windows/radiobuttons.cpp",
    "content": "// 20 may 2015\n#include \"uipriv_windows.hpp\"\n\n// desired behavior:\n// - tab moves between the radio buttons and the adjacent controls\n// - arrow keys navigate between radio buttons\n// - arrow keys do not leave the radio buttons (this is done in control.c)\n// - arrow keys wrap around bare groups (if the previous control has WS_GROUP but the first radio button doesn't, then it doesn't; since our radio buttons are all in their own child window we can't do that)\n// - clicking on a radio button draws a focus rect (TODO)\n\nstruct uiRadioButtons {\n\tuiWindowsControl c;\n\tHWND hwnd;\t\t\t\t\t// of the container\n\tstd::vector<HWND> *hwnds;\t\t// of the buttons\n\tvoid (*onSelected)(uiRadioButtons *, void *);\n\tvoid *onSelectedData;\n};\n\nstatic BOOL onWM_COMMAND(uiControl *c, HWND clicked, WORD code, LRESULT *lResult)\n{\n\tuiRadioButtons *r = uiRadioButtons(c);\n\tWPARAM check;\n\n\tif (code != BN_CLICKED)\n\t\treturn FALSE;\n\tfor (const HWND &hwnd : *(r->hwnds)) {\n\t\tcheck = BST_UNCHECKED;\n\t\tif (clicked == hwnd)\n\t\t\tcheck = BST_CHECKED;\n\t\tSendMessage(hwnd, BM_SETCHECK, check, 0);\n\t}\n\t(*(r->onSelected))(r, r->onSelectedData);\n\t*lResult = 0;\n\treturn TRUE;\n}\n\nstatic void defaultOnSelected(uiRadioButtons *r, void *data)\n{\n\t// do nothing\n}\n\nstatic void uiRadioButtonsDestroy(uiControl *c)\n{\n\tuiRadioButtons *r = uiRadioButtons(c);\n\n\tfor (const HWND &hwnd : *(r->hwnds)) {\n\t\tuiWindowsUnregisterWM_COMMANDHandler(hwnd);\n\t\tuiWindowsEnsureDestroyWindow(hwnd);\n\t}\n\tdelete r->hwnds;\n\tuiWindowsEnsureDestroyWindow(r->hwnd);\n\tuiFreeControl(uiControl(r));\n}\n\n// TODO SyncEnableState\nuiWindowsControlAllDefaultsExceptDestroy(uiRadioButtons)\n\n// from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing\n#define radiobuttonHeight 10\n// from http://msdn.microsoft.com/en-us/library/windows/desktop/bb226818%28v=vs.85%29.aspx\n#define radiobuttonXFromLeftOfBoxToLeftOfLabel 12\n\nstatic void uiRadioButtonsMinimumSize(uiWindowsControl *c, int *width, int *height)\n{\n\tuiRadioButtons *r = uiRadioButtons(c);\n\tint wid, maxwid;\n\tuiWindowsSizing sizing;\n\tint x, y;\n\n\tif (r->hwnds->size() == 0) {\n\t\t*width = 0;\n\t\t*height = 0;\n\t\treturn;\n\t}\n\tmaxwid = 0;\n\tfor (const HWND &hwnd : *(r->hwnds)) {\n\t\twid = uiWindowsWindowTextWidth(hwnd);\n\t\tif (maxwid < wid)\n\t\t\tmaxwid = wid;\n\t}\n\n\tx = radiobuttonXFromLeftOfBoxToLeftOfLabel;\n\ty = radiobuttonHeight;\n\tuiWindowsGetSizing((*(r->hwnds))[0], &sizing);\n\tuiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y);\n\n\t*width = x + maxwid;\n\t*height = y * r->hwnds->size();\n}\n\nstatic void radiobuttonsRelayout(uiRadioButtons *r)\n{\n\tRECT client;\n\tint x, y, width, height;\n\tint height1;\n\tuiWindowsSizing sizing;\n\n\tif (r->hwnds->size() == 0)\n\t\treturn;\n\tuiWindowsEnsureGetClientRect(r->hwnd, &client);\n\tx = client.left;\n\ty = client.top;\n\twidth = client.right - client.left;\n\theight1 = radiobuttonHeight;\n\tuiWindowsGetSizing((*(r->hwnds))[0], &sizing);\n\tuiWindowsSizingDlgUnitsToPixels(&sizing, NULL, &height1);\n\theight = height1;\n\tfor (const HWND &hwnd : *(r->hwnds)) {\n\t\tuiWindowsEnsureMoveWindowDuringResize(hwnd, x, y, width, height);\n\t\ty += height;\n\t}\n}\n\nstatic void radiobuttonsArrangeChildren(uiRadioButtons *r)\n{\n\tLONG_PTR controlID;\n\tHWND insertAfter;\n\n\tcontrolID = 100;\n\tinsertAfter = NULL;\n\tfor (const HWND &hwnd : *(r->hwnds))\n\t\tuiWindowsEnsureAssignControlIDZOrder(hwnd, &controlID, &insertAfter);\n}\n\nvoid uiRadioButtonsAppend(uiRadioButtons *r, const char *text)\n{\n\tHWND hwnd;\n\tWCHAR *wtext;\n\tDWORD groupTabStop;\n\n\t// the first radio button gets both WS_GROUP and WS_TABSTOP\n\t// successive radio buttons get *neither*\n\tgroupTabStop = 0;\n\tif (r->hwnds->size() == 0)\n\t\tgroupTabStop = WS_GROUP | WS_TABSTOP;\n\n\twtext = toUTF16(text);\n\thwnd = uiWindowsEnsureCreateControlHWND(0,\n\t\tL\"button\", wtext,\n\t\tBS_RADIOBUTTON | groupTabStop,\n\t\thInstance, NULL,\n\t\tTRUE);\n\tuiprivFree(wtext);\n\tuiWindowsEnsureSetParentHWND(hwnd, r->hwnd);\n\tuiWindowsRegisterWM_COMMANDHandler(hwnd, onWM_COMMAND, uiControl(r));\n\tr->hwnds->push_back(hwnd);\n\tradiobuttonsArrangeChildren(r);\n\tuiWindowsControlMinimumSizeChanged(uiWindowsControl(r));\n}\n\nint uiRadioButtonsSelected(uiRadioButtons *r)\n{\n\tsize_t i;\n\n\tfor (i = 0; i < r->hwnds->size(); i++)\n\t\tif (SendMessage((*(r->hwnds))[i], BM_GETCHECK, 0, 0) == BST_CHECKED)\n\t\t\treturn i;\n\treturn -1;\n}\n\nvoid uiRadioButtonsSetSelected(uiRadioButtons *r, int n)\n{\n\tint m;\n\n\tm = uiRadioButtonsSelected(r);\n\tif (m != -1)\n\t\tSendMessage((*(r->hwnds))[m], BM_SETCHECK, BST_UNCHECKED, 0);\n\tif (n != -1)\n\t\tSendMessage((*(r->hwnds))[n], BM_SETCHECK, BST_CHECKED, 0);\n}\n\nvoid uiRadioButtonsOnSelected(uiRadioButtons *r, void (*f)(uiRadioButtons *, void *), void *data)\n{\n\tr->onSelected = f;\n\tr->onSelectedData = data;\n}\n\nstatic void onResize(uiWindowsControl *c)\n{\n\tradiobuttonsRelayout(uiRadioButtons(c));\n}\n\nuiRadioButtons *uiNewRadioButtons(void)\n{\n\tuiRadioButtons *r;\n\n\tuiWindowsNewControl(uiRadioButtons, r);\n\n\tr->hwnd = uiWindowsMakeContainer(uiWindowsControl(r), onResize);\n\n\tr->hwnds = new std::vector<HWND>;\n\n\tuiRadioButtonsOnSelected(r, defaultOnSelected, NULL);\n\n\treturn r;\n}\n"
  },
  {
    "path": "windows/resources.hpp",
    "content": "// 30 may 2015\n\n#define rcTabPageDialog 29000\n#define rcFontDialog 29001\n#define rcColorDialog 29002\n\n// TODO normalize these\n\n#define rcFontFamilyCombobox 1000\n#define rcFontStyleCombobox 1001\n#define rcFontSizeCombobox 1002\n#define rcFontSamplePlacement 1003\n\n#define rcColorSVChooser 1100\n#define rcColorHSlider 1101\n#define rcPreview 1102\n#define rcOpacitySlider 1103\n#define rcH 1104\n#define rcS 1105\n#define rcV 1106\n#define rcRDouble 1107\n#define rcRInt 1108\n#define rcGDouble 1109\n#define rcGInt 1110\n#define rcBDouble 1111\n#define rcBInt 1112\n#define rcADouble 1113\n#define rcAInt 1114\n#define rcHex 1115\n#define rcHLabel 1116\n#define rcSLabel 1117\n#define rcVLabel 1118\n#define rcRLabel 1119\n#define rcGLabel 1120\n#define rcBLabel 1121\n#define rcALabel 1122\n#define rcHexLabel 1123\n"
  },
  {
    "path": "windows/resources.rc",
    "content": "// 30 may 2015\n#include \"winapi.hpp\"\n#include \"resources.hpp\"\n\n// this is a UTF-8 file\n#pragma code_page(65001)\n\n// this is the Common Controls 6 manifest\n// we only define it in a shared build; static builds have to include the appropriate parts of the manifest in the output executable\n// LONGTERM set up the string values here\n#ifndef _UI_STATIC\nISOLATIONAWARE_MANIFEST_RESOURCE_ID RT_MANIFEST \"libui.manifest\"\n#endif\n"
  },
  {
    "path": "windows/separator.cpp",
    "content": "// 20 may 2015\n#include \"uipriv_windows.hpp\"\n\n// TODO\n// - font scaling issues? https://www.viksoe.dk/code/bevelline.htm\n// \t- isn't something in vista app guidelines suggesting this too? or some other microsoft doc? and what about VS itself?\n\n// references:\n// - http://stackoverflow.com/questions/2892703/how-do-i-draw-separators\n// - https://msdn.microsoft.com/en-us/library/windows/desktop/dn742405%28v=vs.85%29.aspx\n\nstruct uiSeparator {\n\tuiWindowsControl c;\n\tHWND hwnd;\n\tBOOL vertical;\n};\n\nuiWindowsControlAllDefaults(uiSeparator)\n\n// via https://msdn.microsoft.com/en-us/library/windows/desktop/bb226818%28v=vs.85%29.aspx\n#define separatorHeight 1\n\n// TODO\n#define separatorWidth 1\n\nstatic void uiSeparatorMinimumSize(uiWindowsControl *c, int *width, int *height)\n{\n\tuiSeparator *s = uiSeparator(c);\n\tuiWindowsSizing sizing;\n\tint x, y;\n\n\t*width = 1;\t\t// TODO\n\t*height = 1;\n\tx = separatorWidth;\n\ty = separatorHeight;\n\tuiWindowsGetSizing(s->hwnd, &sizing);\n\tuiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y);\n\tif (s->vertical)\n\t\t*width = x;\n\telse\n\t\t*height = y;\n}\n\nuiSeparator *uiNewHorizontalSeparator(void)\n{\n\tuiSeparator *s;\n\n\tuiWindowsNewControl(uiSeparator, s);\n\n\ts->hwnd = uiWindowsEnsureCreateControlHWND(0,\n\t\tL\"static\", L\"\",\n\t\tSS_ETCHEDHORZ,\n\t\thInstance, NULL,\n\t\tTRUE);\n\n\treturn s;\n}\n\nuiSeparator *uiNewVerticalSeparator(void)\n{\n\tuiSeparator *s;\n\n\tuiWindowsNewControl(uiSeparator, s);\n\n\ts->hwnd = uiWindowsEnsureCreateControlHWND(0,\n\t\tL\"static\", L\"\",\n\t\tSS_ETCHEDHORZ,\n\t\thInstance, NULL,\n\t\tTRUE);\n\ts->vertical = TRUE;\n\n\treturn s;\n}\n"
  },
  {
    "path": "windows/sizing.cpp",
    "content": "// 14 may 2015\n#include \"uipriv_windows.hpp\"\n\n// TODO rework the error handling\nvoid getSizing(HWND hwnd, uiWindowsSizing *sizing, HFONT font)\n{\n\tHDC dc;\n\tHFONT prevfont;\n\tTEXTMETRICW tm;\n\tSIZE size;\n\n\tdc = GetDC(hwnd);\n\tif (dc == NULL)\n\t\tlogLastError(L\"error getting DC\");\n\tprevfont = (HFONT) SelectObject(dc, font);\n\tif (prevfont == NULL)\n\t\tlogLastError(L\"error loading control font into device context\");\n\n\tZeroMemory(&tm, sizeof (TEXTMETRICW));\n\tif (GetTextMetricsW(dc, &tm) == 0)\n\t\tlogLastError(L\"error getting text metrics\");\n\tif (GetTextExtentPoint32W(dc, L\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\", 52, &size) == 0)\n\t\tlogLastError(L\"error getting text extent point\");\n\n\tsizing->BaseX = (int) ((size.cx / 26 + 1) / 2);\n\tsizing->BaseY = (int) tm.tmHeight;\n\tsizing->InternalLeading = tm.tmInternalLeading;\n\n\tif (SelectObject(dc, prevfont) != font)\n\t\tlogLastError(L\"error restoring previous font into device context\");\n\tif (ReleaseDC(hwnd, dc) == 0)\n\t\tlogLastError(L\"error releasing DC\");\n}\n\nvoid uiWindowsGetSizing(HWND hwnd, uiWindowsSizing *sizing)\n{\n\treturn getSizing(hwnd, sizing, hMessageFont);\n}\n\n#define dlgUnitsToX(dlg, baseX) MulDiv((dlg), (baseX), 4)\n#define dlgUnitsToY(dlg, baseY) MulDiv((dlg), (baseY), 8)\n\nvoid uiWindowsSizingDlgUnitsToPixels(uiWindowsSizing *sizing, int *x, int *y)\n{\n\tif (x != NULL)\n\t\t*x = dlgUnitsToX(*x, sizing->BaseX);\n\tif (y != NULL)\n\t\t*y = dlgUnitsToY(*y, sizing->BaseY);\n}\n\n// 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\n// this X value is really only for buttons but I don't see a better one :/\n#define winXPadding 4\n// TODO is this too much?\n#define winYPadding 4\n\nvoid uiWindowsSizingStandardPadding(uiWindowsSizing *sizing, int *x, int *y)\n{\n\tif (x != NULL)\n\t\t*x = dlgUnitsToX(winXPadding, sizing->BaseX);\n\tif (y != NULL)\n\t\t*y = dlgUnitsToY(winYPadding, sizing->BaseY);\n}\n"
  },
  {
    "path": "windows/slider.cpp",
    "content": "// 20 may 2015\n#include \"uipriv_windows.hpp\"\n\nstruct uiSlider {\n\tuiWindowsControl c;\n\tHWND hwnd;\n\tvoid (*onChanged)(uiSlider *, void *);\n\tvoid *onChangedData;\n};\n\nstatic BOOL onWM_HSCROLL(uiControl *c, HWND hwnd, WORD code, LRESULT *lResult)\n{\n\tuiSlider *s = uiSlider(c);\n\n\t(*(s->onChanged))(s, s->onChangedData);\n\t*lResult = 0;\n\treturn TRUE;\n}\n\nstatic void uiSliderDestroy(uiControl *c)\n{\n\tuiSlider *s = uiSlider(c);\n\n\tuiWindowsUnregisterWM_HSCROLLHandler(s->hwnd);\n\tuiWindowsEnsureDestroyWindow(s->hwnd);\n\tuiFreeControl(uiControl(s));\n}\n\nuiWindowsControlAllDefaultsExceptDestroy(uiSlider);\n\n// from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing\n#define sliderWidth 107 /* this is actually the shorter progress bar width, but Microsoft doesn't indicate a width */\n#define sliderHeight 15\n\nstatic void uiSliderMinimumSize(uiWindowsControl *c, int *width, int *height)\n{\n\tuiSlider *s = uiSlider(c);\n\tuiWindowsSizing sizing;\n\tint x, y;\n\n\tx = sliderWidth;\n\ty = sliderHeight;\n\tuiWindowsGetSizing(s->hwnd, &sizing);\n\tuiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y);\n\t*width = x;\n\t*height = y;\n}\n\nstatic void defaultOnChanged(uiSlider *s, void *data)\n{\n\t// do nothing\n}\n\nint uiSliderValue(uiSlider *s)\n{\n\treturn SendMessageW(s->hwnd, TBM_GETPOS, 0, 0);\n}\n\nvoid uiSliderSetValue(uiSlider *s, int value)\n{\n\t// don't use TBM_SETPOSNOTIFY; that triggers an event\n\tSendMessageW(s->hwnd, TBM_SETPOS, (WPARAM) TRUE, (LPARAM) value);\n}\n\nvoid uiSliderOnChanged(uiSlider *s, void (*f)(uiSlider *, void *), void *data)\n{\n\ts->onChanged = f;\n\ts->onChangedData = data;\n}\n\nuiSlider *uiNewSlider(int min, int max)\n{\n\tuiSlider *s;\n\tint temp;\n\n\tif (min >= max) {\n\t\ttemp = min;\n\t\tmin = max;\n\t\tmax = temp;\n\t}\n\n\tuiWindowsNewControl(uiSlider, s);\n\n\ts->hwnd = uiWindowsEnsureCreateControlHWND(0,\n\t\tTRACKBAR_CLASSW, L\"\",\n\t\tTBS_HORZ | TBS_TOOLTIPS | TBS_TRANSPARENTBKGND | WS_TABSTOP,\n\t\thInstance, NULL,\n\t\tTRUE);\n\n\tuiWindowsRegisterWM_HSCROLLHandler(s->hwnd, onWM_HSCROLL, uiControl(s));\n\tuiSliderOnChanged(s, defaultOnChanged, NULL);\n\n\tSendMessageW(s->hwnd, TBM_SETRANGEMIN, (WPARAM) TRUE, (LPARAM) min);\n\tSendMessageW(s->hwnd, TBM_SETRANGEMAX, (WPARAM) TRUE, (LPARAM) max);\n\tSendMessageW(s->hwnd, TBM_SETPOS, (WPARAM) TRUE, (LPARAM) min);\n\n\treturn s;\n}\n"
  },
  {
    "path": "windows/spinbox.cpp",
    "content": "// 8 april 2015\n#include \"uipriv_windows.hpp\"\n\nstruct uiSpinbox {\n\tuiWindowsControl c;\n\tHWND hwnd;\n\tHWND edit;\n\tHWND updown;\n\tvoid (*onChanged)(uiSpinbox *, void *);\n\tvoid *onChangedData;\n\tBOOL inhibitChanged;\n};\n\n// utility functions\n\nstatic int value(uiSpinbox *s)\n{\n\tBOOL neededCap = FALSE;\n\tLRESULT val;\n\n\t// This verifies the value put in, capping it automatically.\n\t// We don't need to worry about checking for an error; that flag should really be called \"did we have to cap?\".\n\t// We DO need to set the value in case of a cap though.\n\tval = SendMessageW(s->updown, UDM_GETPOS32, 0, (LPARAM) (&neededCap));\n\tif (neededCap) {\n\t\ts->inhibitChanged = TRUE;\n\t\tSendMessageW(s->updown, UDM_SETPOS32, 0, (LPARAM) val);\n\t\ts->inhibitChanged = FALSE;\n\t}\n\treturn val;\n}\n\n// control implementation\n\n// TODO assign lResult\nstatic BOOL onWM_COMMAND(uiControl *c, HWND hwnd, WORD code, LRESULT *lResult)\n{\n\tuiSpinbox *s = (uiSpinbox *) c;\n\tWCHAR *wtext;\n\n\tif (code != EN_CHANGE)\n\t\treturn FALSE;\n\tif (s->inhibitChanged)\n\t\treturn FALSE;\n\t// We want to allow typing negative numbers; the natural way to do so is to start with a -.\n\t// However, if we just have the code below, the up-down will catch the bare - and reject it.\n\t// Let's fix that.\n\t// This won't handle leading spaces, but spaces aren't allowed *anyway*.\n\twtext = windowText(s->edit);\n\tif (wcscmp(wtext, L\"-\") == 0) {\n\t\tuiprivFree(wtext);\n\t\treturn TRUE;\n\t}\n\tuiprivFree(wtext);\n\t// value() does the work for us\n\tvalue(s);\n\t(*(s->onChanged))(s, s->onChangedData);\n\treturn TRUE;\n}\n\nstatic void uiSpinboxDestroy(uiControl *c)\n{\n\tuiSpinbox *s = uiSpinbox(c);\n\n\tuiWindowsUnregisterWM_COMMANDHandler(s->edit);\n\tuiWindowsEnsureDestroyWindow(s->updown);\n\tuiWindowsEnsureDestroyWindow(s->edit);\n\tuiWindowsEnsureDestroyWindow(s->hwnd);\n\tuiFreeControl(uiControl(s));\n}\n\n// TODO SyncEnableState\nuiWindowsControlAllDefaultsExceptDestroy(uiSpinbox)\n\n// from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing\n// TODO reduce this?\n#define entryWidth 107 /* this is actually the shorter progress bar width, but Microsoft only indicates as wide as necessary */\n#define entryHeight 14\n\nstatic void uiSpinboxMinimumSize(uiWindowsControl *c, int *width, int *height)\n{\n\tuiSpinbox *s = uiSpinbox(c);\n\tuiWindowsSizing sizing;\n\tint x, y;\n\n\tx = entryWidth;\n\ty = entryHeight;\n\t// note that we go by the edit here\n\tuiWindowsGetSizing(s->edit, &sizing);\n\tuiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y);\n\t*width = x;\n\t*height = y;\n}\n\nstatic void spinboxArrangeChildren(uiSpinbox *s)\n{\n\tLONG_PTR controlID;\n\tHWND insertAfter;\n\n\tcontrolID = 100;\n\tinsertAfter = NULL;\n\tuiWindowsEnsureAssignControlIDZOrder(s->edit, &controlID, &insertAfter);\n\tuiWindowsEnsureAssignControlIDZOrder(s->updown, &controlID, &insertAfter);\n}\n\n// an up-down control will only properly position itself the first time\n// stupidly, there are no messages to force a size calculation, nor can I seem to reset the buddy window to force a new position\n// alas, we have to make a new up/down control each time :(\nstatic void recreateUpDown(uiSpinbox *s)\n{\n\tBOOL preserve = FALSE;\n\tint current;\n\t// Microsoft's commctrl.h says to use this type\n\tINT min, max;\n\n\tif (s->updown != NULL) {\n\t\tpreserve = TRUE;\n\t\tcurrent = value(s);\n\t\tSendMessageW(s->updown, UDM_GETRANGE32, (WPARAM) (&min), (LPARAM) (&max));\n\t\tuiWindowsEnsureDestroyWindow(s->updown);\n\t}\n\ts->inhibitChanged = TRUE;\n\ts->updown = CreateWindowExW(0,\n\t\tUPDOWN_CLASSW, L\"\",\n\t\t// no WS_VISIBLE; we set visibility ourselves\n\t\t// up-down control should not be a tab stop\n\t\tWS_CHILD | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_HOTTRACK | UDS_NOTHOUSANDS | UDS_SETBUDDYINT,\n\t\t// this is important; it's necessary for autosizing to work\n\t\t0, 0, 0, 0,\n\t\ts->hwnd, NULL, hInstance, NULL);\n\tif (s->updown == NULL)\n\t\tlogLastError(L\"error creating updown\");\n\tSendMessageW(s->updown, UDM_SETBUDDY, (WPARAM) (s->edit), 0);\n\tif (preserve) {\n\t\tSendMessageW(s->updown, UDM_SETRANGE32, (WPARAM) min, (LPARAM) max);\n\t\tSendMessageW(s->updown, UDM_SETPOS32, 0, (LPARAM) current);\n\t}\n\t// preserve the Z-order\n\tspinboxArrangeChildren(s);\n\t// TODO properly show/enable\n\tShowWindow(s->updown, SW_SHOW);\n\ts->inhibitChanged = FALSE;\n}\n\nstatic void spinboxRelayout(uiSpinbox *s)\n{\n\tRECT r;\n\n\t// make the edit fill the container first; the new updown will resize it\n\tuiWindowsEnsureGetClientRect(s->hwnd, &r);\n\tuiWindowsEnsureMoveWindowDuringResize(s->edit, r.left, r.top, r.right - r.left, r.bottom - r.top);\n\trecreateUpDown(s);\n}\n\nstatic void defaultOnChanged(uiSpinbox *s, void *data)\n{\n\t// do nothing\n}\n\nint uiSpinboxValue(uiSpinbox *s)\n{\n\treturn value(s);\n}\n\nvoid uiSpinboxSetValue(uiSpinbox *s, int value)\n{\n\ts->inhibitChanged = TRUE;\n\tSendMessageW(s->updown, UDM_SETPOS32, 0, (LPARAM) value);\n\ts->inhibitChanged = FALSE;\n}\n\nvoid uiSpinboxOnChanged(uiSpinbox *s, void (*f)(uiSpinbox *, void *), void *data)\n{\n\ts->onChanged = f;\n\ts->onChangedData = data;\n}\n\nstatic void onResize(uiWindowsControl *c)\n{\n\tspinboxRelayout(uiSpinbox(c));\n}\n\nuiSpinbox *uiNewSpinbox(int min, int max)\n{\n\tuiSpinbox *s;\n\tint temp;\n\n\tif (min >= max) {\n\t\ttemp = min;\n\t\tmin = max;\n\t\tmax = temp;\n\t}\n\n\tuiWindowsNewControl(uiSpinbox, s);\n\n\ts->hwnd = uiWindowsMakeContainer(uiWindowsControl(s), onResize);\n\n\ts->edit = uiWindowsEnsureCreateControlHWND(WS_EX_CLIENTEDGE,\n\t\tL\"edit\", L\"\",\n\t\t// don't use ES_NUMBER; it doesn't allow typing in a leading -\n\t\tES_AUTOHSCROLL | ES_LEFT | ES_NOHIDESEL | WS_TABSTOP,\n\t\thInstance, NULL,\n\t\tTRUE);\n\tuiWindowsEnsureSetParentHWND(s->edit, s->hwnd);\n\n\tuiWindowsRegisterWM_COMMANDHandler(s->edit, onWM_COMMAND, uiControl(s));\n\tuiSpinboxOnChanged(s, defaultOnChanged, NULL);\n\n\trecreateUpDown(s);\n\ts->inhibitChanged = TRUE;\n\tSendMessageW(s->updown, UDM_SETRANGE32, (WPARAM) min, (LPARAM) max);\n\tSendMessageW(s->updown, UDM_SETPOS32, 0, (LPARAM) min);\n\ts->inhibitChanged = FALSE;\n\n\treturn s;\n}\n"
  },
  {
    "path": "windows/stddialogs.cpp",
    "content": "// 22 may 2015\n#include \"uipriv_windows.hpp\"\n\n// TODO document all this is what we want\n// TODO do the same for font and color buttons\n\n// notes:\n// - FOS_SUPPORTSTREAMABLEITEMS doesn't seem to be supported on windows vista, or at least not with the flags we use\n// - even with FOS_NOVALIDATE the dialogs will reject invalid filenames (at least on Vista, anyway)\n// - lack of FOS_NOREADONLYRETURN doesn't seem to matter on Windows 7\n\n// TODO\n// - http://blogs.msdn.com/b/wpfsdk/archive/2006/10/26/uncommon-dialogs--font-chooser-and-color-picker-dialogs.aspx\n// - when a dialog is active, tab navigation in other windows stops working\n// - when adding uiOpenFolder(), use IFileDialog as well - https://msdn.microsoft.com/en-us/library/windows/desktop/bb762115%28v=vs.85%29.aspx\n\n#define windowHWND(w) ((HWND) uiControlHandle(uiControl(w)))\n\nchar *commonItemDialog(HWND parent, REFCLSID clsid, REFIID iid, FILEOPENDIALOGOPTIONS optsadd)\n{\n\tIFileDialog *d = NULL;\n\tFILEOPENDIALOGOPTIONS opts;\n\tIShellItem *result = NULL;\n\tWCHAR *wname = NULL;\n\tchar *name = NULL;\n\tHRESULT hr;\n\n\thr = CoCreateInstance(clsid,\n\t\tNULL, CLSCTX_INPROC_SERVER,\n\t\tiid, (LPVOID *) (&d));\n\tif (hr != S_OK) {\n\t\tlogHRESULT(L\"error creating common item dialog\", hr);\n\t\t// always return NULL on error\n\t\tgoto out;\n\t}\n\thr = d->GetOptions(&opts);\n\tif (hr != S_OK) {\n\t\tlogHRESULT(L\"error getting current options\", hr);\n\t\tgoto out;\n\t}\n\topts |= optsadd;\n\t// the other platforms don't check read-only; we won't either\n\topts &= ~FOS_NOREADONLYRETURN;\n\thr = d->SetOptions(opts);\n\tif (hr != S_OK) {\n\t\tlogHRESULT(L\"error setting options\", hr);\n\t\tgoto out;\n\t}\n\thr = d->Show(parent);\n\tif (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED))\n\t\t// cancelled; return NULL like we have ready\n\t\tgoto out;\n\tif (hr != S_OK) {\n\t\tlogHRESULT(L\"error showing dialog\", hr);\n\t\tgoto out;\n\t}\n\thr = d->GetResult(&result);\n\tif (hr != S_OK) {\n\t\tlogHRESULT(L\"error getting dialog result\", hr);\n\t\tgoto out;\n\t}\n\thr = result->GetDisplayName(SIGDN_FILESYSPATH, &wname);\n\tif (hr != S_OK) {\n\t\tlogHRESULT(L\"error getting filename\", hr);\n\t\tgoto out;\n\t}\n\tname = toUTF8(wname);\n\nout:\n\tif (wname != NULL)\n\t\tCoTaskMemFree(wname);\n\tif (result != NULL)\n\t\tresult->Release();\n\tif (d != NULL)\n\t\td->Release();\n\treturn name;\n}\n\nchar *uiOpenFile(uiWindow *parent)\n{\n\tchar *res;\n\n\tdisableAllWindowsExcept(parent);\n\tres = commonItemDialog(windowHWND(parent),\n\t\tCLSID_FileOpenDialog, IID_IFileOpenDialog,\n\t\tFOS_NOCHANGEDIR | FOS_ALLNONSTORAGEITEMS | FOS_NOVALIDATE | FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_SHAREAWARE | FOS_NOTESTFILECREATE | FOS_NODEREFERENCELINKS | FOS_FORCESHOWHIDDEN | FOS_DEFAULTNOMINIMODE);\n\tenableAllWindowsExcept(parent);\n\treturn res;\n}\n\nchar *uiSaveFile(uiWindow *parent)\n{\n\tchar *res;\n\n\tdisableAllWindowsExcept(parent);\n\tres = commonItemDialog(windowHWND(parent),\n\t\tCLSID_FileSaveDialog, IID_IFileSaveDialog,\n\t\tFOS_OVERWRITEPROMPT | FOS_NOCHANGEDIR | FOS_ALLNONSTORAGEITEMS | FOS_NOVALIDATE | FOS_SHAREAWARE | FOS_NOTESTFILECREATE | FOS_NODEREFERENCELINKS | FOS_FORCESHOWHIDDEN | FOS_DEFAULTNOMINIMODE);\n\tenableAllWindowsExcept(parent);\n\treturn res;\n}\n\n// TODO switch to TaskDialogIndirect()?\n\nstatic void msgbox(HWND parent, const char *title, const char *description, TASKDIALOG_COMMON_BUTTON_FLAGS buttons, PCWSTR icon)\n{\n\tWCHAR *wtitle, *wdescription;\n\tHRESULT hr;\n\n\twtitle = toUTF16(title);\n\twdescription = toUTF16(description);\n\n\thr = TaskDialog(parent, NULL, NULL, wtitle, wdescription, buttons, icon, NULL);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error showing task dialog\", hr);\n\n\tuiprivFree(wdescription);\n\tuiprivFree(wtitle);\n}\n\nvoid uiMsgBox(uiWindow *parent, const char *title, const char *description)\n{\n\tdisableAllWindowsExcept(parent);\n\tmsgbox(windowHWND(parent), title, description, TDCBF_OK_BUTTON, NULL);\n\tenableAllWindowsExcept(parent);\n}\n\nvoid uiMsgBoxError(uiWindow *parent, const char *title, const char *description)\n{\n\tdisableAllWindowsExcept(parent);\n\tmsgbox(windowHWND(parent), title, description, TDCBF_OK_BUTTON, TD_ERROR_ICON);\n\tenableAllWindowsExcept(parent);\n}\n"
  },
  {
    "path": "windows/tab.cpp",
    "content": "// 16 may 2015\n#include \"uipriv_windows.hpp\"\n\n// You don't add controls directly to a tab control on Windows; instead you make them siblings and swap between them on a TCN_SELCHANGING/TCN_SELCHANGE notification pair.\n// In addition, you use dialogs because they can be textured properly; other controls cannot. (Things will look wrong if the tab background in the current theme is fancy if you just use the tab background by itself; see http://stackoverflow.com/questions/30087540/why-are-my-programss-tab-controls-rendering-their-background-in-a-blocky-way-b.)\n\nstruct uiTab {\n\tuiWindowsControl c;\n\tHWND hwnd;\t\t\t// of the outer container\n\tHWND tabHWND;\t\t// of the tab control itself\n\tstd::vector<struct tabPage *> *pages;\n\tHWND parent;\n};\n\n// utility functions\n\nstatic LRESULT curpage(uiTab *t)\n{\n\treturn SendMessageW(t->tabHWND, TCM_GETCURSEL, 0, 0);\n}\n\nstatic struct tabPage *tabPage(uiTab *t, int i)\n{\n\treturn (*(t->pages))[i];\n}\n\nstatic void tabPageRect(uiTab *t, RECT *r)\n{\n\t// this rect needs to be in parent window coordinates, but TCM_ADJUSTRECT wants a window rect, which is screen coordinates\n\t// because we have each page as a sibling of the tab, use the tab's own rect as the input rect\n\tuiWindowsEnsureGetWindowRect(t->tabHWND, r);\n\tSendMessageW(t->tabHWND, TCM_ADJUSTRECT, (WPARAM) FALSE, (LPARAM) r);\n\t// and get it in terms of the container instead of the screen\n\tmapWindowRect(NULL, t->hwnd, r);\n}\n\nstatic void tabRelayout(uiTab *t)\n{\n\tstruct tabPage *page;\n\tRECT r;\n\n\t// first move the tab control itself\n\tuiWindowsEnsureGetClientRect(t->hwnd, &r);\n\tuiWindowsEnsureMoveWindowDuringResize(t->tabHWND, r.left, r.top, r.right - r.left, r.bottom - r.top);\n\n\t// then the current page\n\tif (t->pages->size() == 0)\n\t\treturn;\n\tpage = tabPage(t, curpage(t));\n\ttabPageRect(t, &r);\n\tuiWindowsEnsureMoveWindowDuringResize(page->hwnd, r.left, r.top, r.right - r.left, r.bottom - r.top);\n}\n\nstatic void showHidePage(uiTab *t, LRESULT which, int hide)\n{\n\tstruct tabPage *page;\n\n\tif (which == (LRESULT) (-1))\n\t\treturn;\n\tpage = tabPage(t, which);\n\tif (hide)\n\t\tShowWindow(page->hwnd, SW_HIDE);\n\telse {\n\t\tShowWindow(page->hwnd, SW_SHOW);\n\t\t// we only resize the current page, so we have to resize it; before we can do that, we need to make sure we are of the right size\n\t\tuiWindowsControlMinimumSizeChanged(uiWindowsControl(t));\n\t}\n}\n\n// control implementation\n\nstatic BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nm, LRESULT *lResult)\n{\n\tuiTab *t = uiTab(c);\n\n\tif (nm->code != TCN_SELCHANGING && nm->code != TCN_SELCHANGE)\n\t\treturn FALSE;\n\tshowHidePage(t, curpage(t), nm->code == TCN_SELCHANGING);\n\t*lResult = 0;\n\tif (nm->code == TCN_SELCHANGING)\n\t\t*lResult = FALSE;\n\treturn TRUE;\n}\n\nstatic void uiTabDestroy(uiControl *c)\n{\n\tuiTab *t = uiTab(c);\n\tuiControl *child;\n\n\tfor (struct tabPage *&page : *(t->pages)) {\n\t\tchild = page->child;\n\t\ttabPageDestroy(page);\n\t\tif (child != NULL) {\n\t\t\tuiControlSetParent(child, NULL);\n\t\t\tuiControlDestroy(child);\n\t\t}\n\t}\n\tdelete t->pages;\n\tuiWindowsUnregisterWM_NOTIFYHandler(t->tabHWND);\n\tuiWindowsEnsureDestroyWindow(t->tabHWND);\n\tuiWindowsEnsureDestroyWindow(t->hwnd);\n\tuiFreeControl(uiControl(t));\n}\n\nuiWindowsControlDefaultHandle(uiTab)\nuiWindowsControlDefaultParent(uiTab)\nuiWindowsControlDefaultSetParent(uiTab)\nuiWindowsControlDefaultToplevel(uiTab)\nuiWindowsControlDefaultVisible(uiTab)\nuiWindowsControlDefaultShow(uiTab)\nuiWindowsControlDefaultHide(uiTab)\nuiWindowsControlDefaultEnabled(uiTab)\nuiWindowsControlDefaultEnable(uiTab)\nuiWindowsControlDefaultDisable(uiTab)\n\nstatic void uiTabSyncEnableState(uiWindowsControl *c, int enabled)\n{\n\tuiTab *t = uiTab(c);\n\n\tif (uiWindowsShouldStopSyncEnableState(uiWindowsControl(t), enabled))\n\t\treturn;\n\tEnableWindow(t->tabHWND, enabled);\n\tfor (struct tabPage *&page : *(t->pages))\n\t\tif (page->child != NULL)\n\t\t\tuiWindowsControlSyncEnableState(uiWindowsControl(page->child), enabled);\n}\n\nuiWindowsControlDefaultSetParentHWND(uiTab)\n\nstatic void uiTabMinimumSize(uiWindowsControl *c, int *width, int *height)\n{\n\tuiTab *t = uiTab(c);\n\tint pagewid, pageht;\n\tstruct tabPage *page;\n\tRECT r;\n\n\t// only consider the current page\n\tpagewid = 0;\n\tpageht = 0;\n\tif (t->pages->size() != 0) {\n\t\tpage = tabPage(t, curpage(t));\n\t\ttabPageMinimumSize(page, &pagewid, &pageht);\n\t}\n\n\tr.left = 0;\n\tr.top = 0;\n\tr.right = pagewid;\n\tr.bottom = pageht;\n\t// this also includes the tabs themselves\n\tSendMessageW(t->tabHWND, TCM_ADJUSTRECT, (WPARAM) TRUE, (LPARAM) (&r));\n\t*width = r.right - r.left;\n\t*height = r.bottom - r.top;\n}\n\nstatic void uiTabMinimumSizeChanged(uiWindowsControl *c)\n{\n\tuiTab *t = uiTab(c);\n\n\tif (uiWindowsControlTooSmall(uiWindowsControl(t))) {\n\t\tuiWindowsControlContinueMinimumSizeChanged(uiWindowsControl(t));\n\t\treturn;\n\t}\n\ttabRelayout(t);\n}\n\nuiWindowsControlDefaultLayoutRect(uiTab)\nuiWindowsControlDefaultAssignControlIDZOrder(uiTab)\n\nstatic void uiTabChildVisibilityChanged(uiWindowsControl *c)\n{\n\t// TODO eliminate the redundancy\n\tuiWindowsControlMinimumSizeChanged(c);\n}\n\nstatic void tabArrangePages(uiTab *t)\n{\n\tLONG_PTR controlID = 100;\n\tHWND insertAfter = NULL;\n\n\t// TODO is this first or last?\n\tuiWindowsEnsureAssignControlIDZOrder(t->tabHWND, &controlID, &insertAfter);\n\tfor (struct tabPage *&page : *(t->pages))\n\t\tuiWindowsEnsureAssignControlIDZOrder(page->hwnd, &controlID, &insertAfter);\n}\n\nvoid uiTabAppend(uiTab *t, const char *name, uiControl *child)\n{\n\tuiTabInsertAt(t, name, t->pages->size(), child);\n}\n\nvoid uiTabInsertAt(uiTab *t, const char *name, int n, uiControl *child)\n{\n\tstruct tabPage *page;\n\tLRESULT hide, show;\n\tTCITEMW item;\n\tWCHAR *wname;\n\n\t// see below\n\thide = curpage(t);\n\n\tif (child != NULL)\n\t\tuiControlSetParent(child, uiControl(t));\n\n\tpage = newTabPage(child);\n\tuiWindowsEnsureSetParentHWND(page->hwnd, t->hwnd);\n\tt->pages->insert(t->pages->begin() + n, page);\n\ttabArrangePages(t);\n\n\tZeroMemory(&item, sizeof (TCITEMW));\n\titem.mask = TCIF_TEXT;\n\twname = toUTF16(name);\n\titem.pszText = wname;\n\tif (SendMessageW(t->tabHWND, TCM_INSERTITEM, (WPARAM) n, (LPARAM) (&item)) == (LRESULT) -1)\n\t\tlogLastError(L\"error adding tab to uiTab\");\n\tuiprivFree(wname);\n\n\t// we need to do this because adding the first tab doesn't send a TCN_SELCHANGE; it just shows the page\n\tshow = curpage(t);\n\tif (show != hide) {\n\t\tshowHidePage(t, hide, 1);\n\t\tshowHidePage(t, show, 0);\n\t}\n}\n\nvoid uiTabDelete(uiTab *t, int n)\n{\n\tstruct tabPage *page;\n\n\t// first delete the tab from the tab control\n\t// if this is the current tab, no tab will be selected, which is good\n\tif (SendMessageW(t->tabHWND, TCM_DELETEITEM, (WPARAM) n, 0) == FALSE)\n\t\tlogLastError(L\"error deleting uiTab tab\");\n\n\t// now delete the page itself\n\tpage = tabPage(t, n);\n\tif (page->child != NULL)\n\t\tuiControlSetParent(page->child, NULL);\n\ttabPageDestroy(page);\n\tt->pages->erase(t->pages->begin() + n);\n}\n\nint uiTabNumPages(uiTab *t)\n{\n\treturn t->pages->size();\n}\n\nint uiTabMargined(uiTab *t, int n)\n{\n\treturn tabPage(t, n)->margined;\n}\n\nvoid uiTabSetMargined(uiTab *t, int n, int margined)\n{\n\tstruct tabPage *page;\n\n\tpage = tabPage(t, n);\n\tpage->margined = margined;\n\t// even if the page doesn't have a child it might still have a new minimum size with margins; this is the easiest way to verify it\n\tuiWindowsControlMinimumSizeChanged(uiWindowsControl(t));\n}\n\nstatic void onResize(uiWindowsControl *c)\n{\n\ttabRelayout(uiTab(c));\n}\n\nuiTab *uiNewTab(void)\n{\n\tuiTab *t;\n\n\tuiWindowsNewControl(uiTab, t);\n\n\tt->hwnd = uiWindowsMakeContainer(uiWindowsControl(t), onResize);\n\n\tt->tabHWND = uiWindowsEnsureCreateControlHWND(0,\n\t\tWC_TABCONTROLW, L\"\",\n\t\tTCS_TOOLTIPS | WS_TABSTOP,\n\t\thInstance, NULL,\n\t\tTRUE);\n\tuiWindowsEnsureSetParentHWND(t->tabHWND, t->hwnd);\n\n\tuiWindowsRegisterWM_NOTIFYHandler(t->tabHWND, onWM_NOTIFY, uiControl(t));\n\n\tt->pages = new std::vector<struct tabPage *>;\n\n\treturn t;\n}\n"
  },
  {
    "path": "windows/table.cpp",
    "content": "#include \"uipriv_windows.hpp\"\n#include \"table.hpp\"\n\n// general TODOs:\n// - 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?\n// - should clicking on some other column of the same row, even one that doesn't edit, cancel editing?\n// - implement keyboard accessibility\n// - implement accessibility in general (Dynamic Annotations maybe?)\n// - if I didn't handle these already: \"drawing focus rects here, subitem navigation and activation with the keyboard\"\n\nuiTableModel *uiNewTableModel(uiTableModelHandler *mh)\n{\n\tuiTableModel *m;\n\n\tm = uiprivNew(uiTableModel);\n\tm->mh = mh;\n\tm->tables = new std::vector<uiTable *>;\n\treturn m;\n}\n\nvoid uiFreeTableModel(uiTableModel *m)\n{\n\tdelete m->tables;\n\tuiprivFree(m);\n}\n\n// TODO document that when this is called, the model must return the new row count when asked\nvoid uiTableModelRowInserted(uiTableModel *m, int newIndex)\n{\n\tLVITEMW item;\n\tint newCount;\n\n\tnewCount = uiprivTableModelNumRows(m);\n\tZeroMemory(&item, sizeof (LVITEMW));\n\titem.mask = 0;\n\titem.iItem = newIndex;\n\titem.iSubItem = 0;\n\tfor (auto t : *(m->tables)) {\n\t\t// actually insert the rows\n\t\tif (SendMessageW(t->hwnd, LVM_SETITEMCOUNT, (WPARAM) newCount, LVSICF_NOINVALIDATEALL) == 0)\n\t\t\tlogLastError(L\"error calling LVM_SETITEMCOUNT in uiTableModelRowInserted()\");\n\t\t// and redraw every row from the new row down to simulate adding it\n\t\tif (SendMessageW(t->hwnd, LVM_REDRAWITEMS, (WPARAM) newIndex, (LPARAM) (newCount - 1)) == FALSE)\n\t\t\tlogLastError(L\"error calling LVM_REDRAWITEMS in uiTableModelRowInserted()\");\n\n\t\t// update selection state\n\t\tif (SendMessageW(t->hwnd, LVM_INSERTITEM, 0, (LPARAM) (&item)) == (LRESULT) (-1))\n\t\t\tlogLastError(L\"error calling LVM_INSERTITEM in uiTableModelRowInserted() to update selection state\");\n\t}\n}\n\n// TODO compare LVM_UPDATE and LVM_REDRAWITEMS\nvoid uiTableModelRowChanged(uiTableModel *m, int index)\n{\n\tfor (auto t : *(m->tables))\n\t\tif (SendMessageW(t->hwnd, LVM_UPDATE, (WPARAM) index, 0) == (LRESULT) (-1))\n\t\t\tlogLastError(L\"error calling LVM_UPDATE in uiTableModelRowChanged()\");\n}\n\n// TODO document that when this is called, the model must return the OLD row count when asked\n// TODO for this and the above, see what GTK+ requires and adjust accordingly\nvoid uiTableModelRowDeleted(uiTableModel *m, int oldIndex)\n{\n\tint newCount;\n\n\tnewCount = uiprivTableModelNumRows(m);\n\tnewCount--;\n\tfor (auto t : *(m->tables)) {\n\t\t// update selection state\n\t\tif (SendMessageW(t->hwnd, LVM_DELETEITEM, (WPARAM) oldIndex, 0) == (LRESULT) (-1))\n\t\t\tlogLastError(L\"error calling LVM_DELETEITEM in uiTableModelRowDeleted() to update selection state\");\n\n\t\t// actually delete the rows\n\t\tif (SendMessageW(t->hwnd, LVM_SETITEMCOUNT, (WPARAM) newCount, LVSICF_NOINVALIDATEALL) == 0)\n\t\t\tlogLastError(L\"error calling LVM_SETITEMCOUNT in uiTableModelRowDeleted()\");\n\t\t// and redraw every row from the new nth row down to simulate removing the old nth row\n\t\tif (SendMessageW(t->hwnd, LVM_REDRAWITEMS, (WPARAM) oldIndex, (LPARAM) (newCount - 1)) == FALSE)\n\t\t\tlogLastError(L\"error calling LVM_REDRAWITEMS in uiTableModelRowDeleted()\");\n\t}\n}\n\nuiTableModelHandler *uiprivTableModelHandler(uiTableModel *m)\n{\n\treturn m->mh;\n}\n\n// TODO explain all this\nstatic LRESULT CALLBACK tableSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIDSubclass, DWORD_PTR dwRefData)\n{\n\tuiTable *t = (uiTable *) dwRefData;\n\tNMHDR *nmhdr = (NMHDR *) lParam;\n\tbool finishEdit, abortEdit;\n\tHWND header;\n\tLRESULT lResult;\n\tHRESULT hr;\n\n\tfinishEdit = false;\n\tabortEdit = false;\n\tswitch (uMsg) {\n\tcase WM_TIMER:\n\t\tif (wParam == (WPARAM) (&(t->inDoubleClickTimer))) {\n\t\t\tt->inDoubleClickTimer = FALSE;\n\t\t\t// TODO check errors\n\t\t\tKillTimer(hwnd, wParam);\n\t\t\treturn 0;\n\t\t}\n\t\tif (wParam != (WPARAM) t)\n\t\t\tbreak;\n\t\t// TODO only increment and update if visible?\n\t\tfor (auto &i : *(t->indeterminatePositions)) {\n\t\t\ti.second++;\n\t\t\t// TODO check errors\n\t\t\tSendMessageW(hwnd, LVM_UPDATE, (WPARAM) (i.first.first), 0);\n\t\t}\n\t\treturn 0;\n\tcase WM_LBUTTONDOWN:\n\t\tt->inLButtonDown = TRUE;\n\t\tlResult = DefSubclassProc(hwnd, uMsg, wParam, lParam);\n\t\tt->inLButtonDown = FALSE;\n\t\treturn lResult;\n\tcase WM_COMMAND:\n\t\tif (HIWORD(wParam) == EN_UPDATE) {\n\t\t\t// the real list view resizes the edit control on this notification specifically\n\t\t\thr = uiprivTableResizeWhileEditing(t);\n\t\t\tif (hr != S_OK) {\n\t\t\t\t// TODO\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\t// the real list view accepts changes in this case\n\t\tif (HIWORD(wParam) == EN_KILLFOCUS)\n\t\t\tfinishEdit = true;\n\t\tbreak;\t\t// don't override default handling\n\tcase WM_NOTIFY:\n\t\t// list view accepts changes on column resize, but does not provide such notifications :/\n\t\theader = (HWND) SendMessageW(t->hwnd, LVM_GETHEADER, 0, 0);\n\t\tif (nmhdr->hwndFrom == header) {\n\t\t\tNMHEADERW *nm = (NMHEADERW *) nmhdr;\n\n\t\t\tswitch (nmhdr->code) {\n\t\t\tcase HDN_ITEMCHANGED:\n\t\t\t\tif ((nm->pitem->mask & HDI_WIDTH) == 0)\n\t\t\t\t\tbreak;\n\t\t\t\t// fall through\n\t\t\tcase HDN_DIVIDERDBLCLICK:\n\t\t\tcase HDN_TRACK:\n\t\t\tcase HDN_ENDTRACK:\n\t\t\t\tfinishEdit = true;\n\t\t\t}\n\t\t}\n\t\t// I think this mirrors the WM_COMMAND one above... TODO\n\t\tif (nmhdr->code == NM_KILLFOCUS)\n\t\t\tfinishEdit = true;\n\t\tbreak;\t\t// don't override default handling\n\tcase LVM_CANCELEDITLABEL:\n\t\tfinishEdit = true;\n\t\t// TODO properly imitate notifiactions\n\t\tbreak;\t\t// don't override default handling\n\t// TODO finish edit on WM_WINDOWPOSCHANGING and WM_SIZE?\n\t// for the next three: this item is about to go away; don't bother keeping changes\n\tcase LVM_SETITEMCOUNT:\n\t\tif (wParam <= t->editedItem)\n\t\t\tabortEdit = true;\n\t\tbreak;\t\t// don't override default handling\n\tcase LVM_DELETEITEM:\n\t\tif (wParam == t->editedItem)\n\t\t\tabortEdit = true;\n\t\tbreak;\t\t// don't override default handling\n\tcase LVM_DELETEALLITEMS:\n\t\tabortEdit = true;\n\t\tbreak;\t\t// don't override default handling\n\tcase WM_NCDESTROY:\n\t\tif (RemoveWindowSubclass(hwnd, tableSubProc, uIDSubclass) == FALSE)\n\t\t\tlogLastError(L\"RemoveWindowSubclass()\");\n\t\t// fall through\n\t}\n\tif (finishEdit) {\n\t\thr = uiprivTableFinishEditingText(t);\n\t\tif (hr != S_OK) {\n\t\t\t// TODO\n\t\t}\n\t} else if (abortEdit) {\n\t\thr = uiprivTableAbortEditingText(t);\n\t\tif (hr != S_OK) {\n\t\t\t// TODO\n\t\t}\n\t}\n\treturn DefSubclassProc(hwnd, uMsg, wParam, lParam);\n}\n\nint uiprivTableProgress(uiTable *t, int item, int subitem, int modelColumn, LONG *pos)\n{\n\tuiTableValue *value;\n\tint progress;\n\tstd::pair<int, int> p;\n\tstd::map<std::pair<int, int>, LONG>::iterator iter;\n\tbool startTimer = false;\n\tbool stopTimer = false;\n\n\tvalue = uiprivTableModelCellValue(t->model, item, modelColumn);\n\tprogress = uiTableValueInt(value);\n\tuiFreeTableValue(value);\n\n\tp.first = item;\n\tp.second = subitem;\n\titer = t->indeterminatePositions->find(p);\n\tif (iter == t->indeterminatePositions->end()) {\n\t\tif (progress == -1) {\n\t\t\tstartTimer = t->indeterminatePositions->size() == 0;\n\t\t\t(*(t->indeterminatePositions))[p] = 0;\n\t\t\tif (pos != NULL)\n\t\t\t\t*pos = 0;\n\t\t}\n\t} else\n\t\tif (progress != -1) {\n\t\t\tt->indeterminatePositions->erase(p);\n\t\t\tstopTimer = t->indeterminatePositions->size() == 0;\n\t\t} else if (pos != NULL)\n\t\t\t*pos = iter->second;\n\n\tif (startTimer)\n\t\t// the interval shown here is PBM_SETMARQUEE's default\n\t\t// TODO should we pass a function here instead? it seems to be called by DispatchMessage(), not DefWindowProc(), but I'm still unsure\n\t\tif (SetTimer(t->hwnd, (UINT_PTR) t, 30, NULL) == 0)\n\t\t\tlogLastError(L\"SetTimer()\");\n\tif (stopTimer)\n\t\tif (KillTimer(t->hwnd, (UINT_PTR) (&t)) == 0)\n\t\t\tlogLastError(L\"KillTimer()\");\n\n\treturn progress;\n}\n\n// TODO properly integrate compound statements\nstatic BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult)\n{\n\tuiTable *t = uiTable(c);\n\tHRESULT hr;\n\n\tswitch (nmhdr->code) {\n\tcase LVN_GETDISPINFO:\n\t\thr = uiprivTableHandleLVN_GETDISPINFO(t, (NMLVDISPINFOW *) nmhdr, lResult);\n\t\tif (hr != S_OK) {\n\t\t\t// TODO\n\t\t\treturn FALSE;\n\t\t}\n\t\treturn TRUE;\n\tcase NM_CUSTOMDRAW:\n\t\thr = uiprivTableHandleNM_CUSTOMDRAW(t, (NMLVCUSTOMDRAW *) nmhdr, lResult);\n\t\tif (hr != S_OK) {\n\t\t\t// TODO\n\t\t\treturn FALSE;\n\t\t}\n\t\treturn TRUE;\n\tcase NM_CLICK:\n#if 0\n\t\t{\n\t\t\tNMITEMACTIVATE *nm = (NMITEMACTIVATE *) nmhdr;\n\t\t\tLVHITTESTINFO ht;\n\t\t\tWCHAR buf[256];\n\n\t\t\tZeroMemory(&ht, sizeof (LVHITTESTINFO));\n\t\t\tht.pt = nm->ptAction;\n\t\t\tif (SendMessageW(t->hwnd, LVM_SUBITEMHITTEST, 0, (LPARAM) (&ht)) == (LRESULT) (-1))\n\t\t\t\tMessageBoxW(GetAncestor(t->hwnd, GA_ROOT), L\"No hit\", L\"No hit\", MB_OK);\n\t\t\telse {\n\t\t\t\twsprintf(buf, L\"item %d subitem %d htflags 0x%I32X\",\n\t\t\t\t\tht.iItem, ht.iSubItem, ht.flags);\n\t\t\t\tMessageBoxW(GetAncestor(t->hwnd, GA_ROOT), buf, buf, MB_OK);\n\t\t\t}\n\t\t}\n\t\t*lResult = 0;\n\t\treturn TRUE;\n#else\n\t\thr = uiprivTableHandleNM_CLICK(t, (NMITEMACTIVATE *) nmhdr, lResult);\n\t\tif (hr != S_OK) {\n\t\t\t// TODO\n\t\t\treturn FALSE;\n\t\t}\n\t\treturn TRUE;\n#endif\n\tcase LVN_ITEMCHANGED:\n\t\t{\n\t\t\tNMLISTVIEW *nm = (NMLISTVIEW *) nmhdr;\n\t\t\tUINT oldSelected, newSelected;\n\t\t\tHRESULT hr;\n\n\t\t\t// TODO clean up these if cases\n\t\t\tif (!t->inLButtonDown && t->edit == NULL)\n\t\t\t\treturn FALSE;\n\t\t\toldSelected = nm->uOldState & LVIS_SELECTED;\n\t\t\tnewSelected = nm->uNewState & LVIS_SELECTED;\n\t\t\tif (t->inLButtonDown && oldSelected == 0 && newSelected != 0) {\n\t\t\t\tt->inDoubleClickTimer = TRUE;\n\t\t\t\t// TODO check error\n\t\t\t\tSetTimer(t->hwnd, (UINT_PTR) (&(t->inDoubleClickTimer)),\n\t\t\t\t\tGetDoubleClickTime(), NULL);\n\t\t\t\t*lResult = 0;\n\t\t\t\treturn TRUE;\n\t\t\t}\n\t\t\t// the nm->iItem == -1 case is because that is used if \"the change has been applied to all items in the list view\"\n\t\t\tif (t->edit != NULL && oldSelected != 0 && newSelected == 0 && (t->editedItem == nm->iItem || nm->iItem == -1)) {\n\t\t\t\t// TODO see if the real list view accepts or rejects changes here; Windows Explorer accepts\n\t\t\t\thr = uiprivTableFinishEditingText(t);\n\t\t\t\tif (hr != S_OK) {\n\t\t\t\t\t// TODO\n\t\t\t\t\treturn FALSE;\n\t\t\t\t}\n\t\t\t\t*lResult = 0;\n\t\t\t\treturn TRUE;\n\t\t\t}\n\t\t\treturn FALSE;\n\t\t}\n\t// the real list view accepts changes when scrolling or clicking column headers\n\tcase LVN_BEGINSCROLL:\n\tcase LVN_COLUMNCLICK:\n\t\thr = uiprivTableFinishEditingText(t);\n\t\tif (hr != S_OK) {\n\t\t\t// TODO\n\t\t\treturn FALSE;\n\t\t}\n\t\t*lResult = 0;\n\t\treturn TRUE;\n\t}\n\treturn FALSE;\n}\n\nstatic void uiTableDestroy(uiControl *c)\n{\n\tuiTable *t = uiTable(c);\n\tuiTableModel *model = t->model;\n\tstd::vector<uiTable *>::iterator it;\n\tHRESULT hr;\n\n\thr = uiprivTableAbortEditingText(t);\n\tif (hr != S_OK) {\n\t\t// TODO\n\t}\n\tuiWindowsUnregisterWM_NOTIFYHandler(t->hwnd);\n\tuiWindowsEnsureDestroyWindow(t->hwnd);\n\t// detach table from model\n\tfor (it = model->tables->begin(); it != model->tables->end(); it++) {\n\t\tif (*it == t) {\n\t\t\tmodel->tables->erase(it);\n\t\t\tbreak;\n\t\t}\n\t}\n\t// free the columns\n\tfor (auto col : *(t->columns))\n\t\tuiprivFree(col);\n\tdelete t->columns;\n\t// t->imagelist will be automatically destroyed\n\tdelete t->indeterminatePositions;\n\tuiFreeControl(uiControl(t));\n}\n\nuiWindowsControlAllDefaultsExceptDestroy(uiTable)\n\n// suggested listview sizing from http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing:\n// \"columns widths that avoid truncated data x an integral number of items\"\n// Don't think that'll cut it when some cells have overlong data (eg\n// stupidly long URLs). So for now, just hardcode a minimum.\n// TODO Investigate using LVM_GETHEADER/HDM_LAYOUT here\n// TODO investigate using LVM_APPROXIMATEVIEWRECT here\n#define tableMinWidth 107\t\t/* in line with other controls */\n#define tableMinHeight (14 * 3)\t/* header + 2 lines (roughly) */\n\nstatic void uiTableMinimumSize(uiWindowsControl *c, int *width, int *height)\n{\n\tuiTable *t = uiTable(c);\n\tuiWindowsSizing sizing;\n\tint x, y;\n\n\tx = tableMinWidth;\n\ty = tableMinHeight;\n\tuiWindowsGetSizing(t->hwnd, &sizing);\n\tuiWindowsSizingDlgUnitsToPixels(&sizing, &x, &y);\n\t*width = x;\n\t*height = y;\n}\n\nstatic uiprivTableColumnParams *appendColumn(uiTable *t, const char *name, int colfmt)\n{\n\tWCHAR *wstr;\n\tLVCOLUMNW lvc;\n\tuiprivTableColumnParams *p;\n\n\tZeroMemory(&lvc, sizeof (LVCOLUMNW));\n\tlvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;\n\tlvc.fmt = colfmt;\n\tlvc.cx = 120;\t\t\t// TODO\n\twstr = toUTF16(name);\n\tlvc.pszText = wstr;\n\tif (SendMessageW(t->hwnd, LVM_INSERTCOLUMNW, t->nColumns, (LPARAM) (&lvc)) == (LRESULT) (-1))\n\t\tlogLastError(L\"error calling LVM_INSERTCOLUMNW in appendColumn()\");\n\tuiprivFree(wstr);\n\tt->nColumns++;\n\n\tp = uiprivNew(uiprivTableColumnParams);\n\tp->textModelColumn = -1;\n\tp->textEditableModelColumn = -1;\n\tp->textParams = uiprivDefaultTextColumnOptionalParams;\n\tp->imageModelColumn = -1;\n\tp->checkboxModelColumn = -1;\n\tp->checkboxEditableModelColumn = -1;\n\tp->progressBarModelColumn = -1;\n\tp->buttonModelColumn = -1;\n\tt->columns->push_back(p);\n\treturn p;\n}\n\nvoid uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams)\n{\n\tuiprivTableColumnParams *p;\n\n\tp = appendColumn(t, name, LVCFMT_LEFT);\n\tp->textModelColumn = textModelColumn;\n\tp->textEditableModelColumn = textEditableModelColumn;\n\tif (textParams != NULL)\n\t\tp->textParams = *textParams;\n}\n\nvoid uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn)\n{\n\tuiprivTableColumnParams *p;\n\n\tp = appendColumn(t, name, LVCFMT_LEFT);\n\tp->imageModelColumn = imageModelColumn;\n}\n\nvoid uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams)\n{\n\tuiprivTableColumnParams *p;\n\n\tp = appendColumn(t, name, LVCFMT_LEFT);\n\tp->textModelColumn = textModelColumn;\n\tp->textEditableModelColumn = textEditableModelColumn;\n\tif (textParams != NULL)\n\t\tp->textParams = *textParams;\n\tp->imageModelColumn = imageModelColumn;\n}\n\nvoid uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn)\n{\n\tuiprivTableColumnParams *p;\n\n\tp = appendColumn(t, name, LVCFMT_LEFT);\n\tp->checkboxModelColumn = checkboxModelColumn;\n\tp->checkboxEditableModelColumn = checkboxEditableModelColumn;\n}\n\nvoid uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams)\n{\n\tuiprivTableColumnParams *p;\n\n\tp = appendColumn(t, name, LVCFMT_LEFT);\n\tp->textModelColumn = textModelColumn;\n\tp->textEditableModelColumn = textEditableModelColumn;\n\tif (textParams != NULL)\n\t\tp->textParams = *textParams;\n\tp->checkboxModelColumn = checkboxModelColumn;\n\tp->checkboxEditableModelColumn = checkboxEditableModelColumn;\n}\n\nvoid uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressModelColumn)\n{\n\tuiprivTableColumnParams *p;\n\n\tp = appendColumn(t, name, LVCFMT_LEFT);\n\tp->progressBarModelColumn = progressModelColumn;\n}\n\nvoid uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonModelColumn, int buttonClickableModelColumn)\n{\n\tuiprivTableColumnParams *p;\n\n\t// TODO see if we can get rid of this parameter\n\tp = appendColumn(t, name, LVCFMT_LEFT);\n\tp->buttonModelColumn = buttonModelColumn;\n\tp->buttonClickableModelColumn = buttonClickableModelColumn;\n}\n\nuiTable *uiNewTable(uiTableParams *p)\n{\n\tuiTable *t;\n\tint n;\n\tHRESULT hr;\n\n\tuiWindowsNewControl(uiTable, t);\n\n\tt->columns = new std::vector<uiprivTableColumnParams *>;\n\tt->model = p->Model;\n\tt->backgroundColumn = p->RowBackgroundColorModelColumn;\n\n\t// WS_CLIPCHILDREN is here to prevent drawing over the edit box used for editing text\n\tt->hwnd = uiWindowsEnsureCreateControlHWND(WS_EX_CLIENTEDGE,\n\t\tWC_LISTVIEW, L\"\",\n\t\tLVS_REPORT | LVS_OWNERDATA | LVS_SINGLESEL | WS_CLIPCHILDREN | WS_TABSTOP | WS_HSCROLL | WS_VSCROLL,\n\t\thInstance, NULL,\n\t\tTRUE);\n\tt->model->tables->push_back(t);\n\tuiWindowsRegisterWM_NOTIFYHandler(t->hwnd, onWM_NOTIFY, uiControl(t));\n\n\t// TODO: try LVS_EX_AUTOSIZECOLUMNS\n\t// TODO check error\n\tSendMessageW(t->hwnd, LVM_SETEXTENDEDLISTVIEWSTYLE,\n\t\t(WPARAM) (LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP | LVS_EX_SUBITEMIMAGES),\n\t\t(LPARAM) (LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP | LVS_EX_SUBITEMIMAGES));\n\tn = uiprivTableModelNumRows(t->model);\n\tif (SendMessageW(t->hwnd, LVM_SETITEMCOUNT, (WPARAM) n, 0) == 0)\n\t\tlogLastError(L\"error calling LVM_SETITEMCOUNT in uiNewTable()\");\n\n\thr = uiprivUpdateImageListSize(t);\n\tif (hr != S_OK) {\n\t\t// TODO\n\t}\n\n\tt->indeterminatePositions = new std::map<std::pair<int, int>, LONG>;\n\tif (SetWindowSubclass(t->hwnd, tableSubProc, 0, (DWORD_PTR) t) == FALSE)\n\t\tlogLastError(L\"SetWindowSubclass()\");\n\n\treturn t;\n}\n"
  },
  {
    "path": "windows/table.hpp",
    "content": "// 10 june 2018\n#include \"../common/table.h\"\n\n// table.cpp\n#define uiprivNumLVN_GETDISPINFOSkip 3\nstruct uiTableModel {\n\tuiTableModelHandler *mh;\n\tstd::vector<uiTable *> *tables;\n};\ntypedef struct uiprivTableColumnParams uiprivTableColumnParams;\nstruct uiprivTableColumnParams {\n\tint textModelColumn;\n\tint textEditableModelColumn;\n\tuiTableTextColumnOptionalParams textParams;\n\n\tint imageModelColumn;\n\n\tint checkboxModelColumn;\n\tint checkboxEditableModelColumn;\n\n\tint progressBarModelColumn;\n\n\tint buttonModelColumn;\n\tint buttonClickableModelColumn;\n};\nstruct uiTable {\n\tuiWindowsControl c;\n\tuiTableModel *model;\n\tHWND hwnd;\n\tstd::vector<uiprivTableColumnParams *> *columns;\n\tWPARAM nColumns;\n\tint backgroundColumn;\n\t// TODO make sure replacing images while selected in the listview is even allowed\n\tHIMAGELIST imagelist;\n\t// TODO document all this\n\tstd::map<std::pair<int, int>, LONG> *indeterminatePositions;\n\tBOOL inLButtonDown;\n\t// TODO is this even necessary? it seems NM_CLICK is not sent if NM_DBLCLICK or LVN_ITEMACTIVATE (one of the two) happens...\n\tBOOL inDoubleClickTimer;\n\tHWND edit;\n\tint editedItem;\n\tint editedSubitem;\n};\nextern int uiprivTableProgress(uiTable *t, int item, int subitem, int modelColumn, LONG *pos);\n\n// tabledispinfo.cpp\nextern HRESULT uiprivTableHandleLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm, LRESULT *lResult);\n\n// tabledraw.cpp\nextern HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *lResult);\nextern HRESULT uiprivUpdateImageListSize(uiTable *t);\n\n// tableediting.cpp\nextern HRESULT uiprivTableResizeWhileEditing(uiTable *t);\nextern HRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResult);\nextern HRESULT uiprivTableFinishEditingText(uiTable *t);\nextern HRESULT uiprivTableAbortEditingText(uiTable *t);\n\n// tablemetrics.cpp\ntypedef struct uiprivTableMetrics uiprivTableMetrics;\nstruct uiprivTableMetrics {\n\tBOOL hasText;\n\tBOOL hasImage;\n\tBOOL focused;\n\tBOOL selected;\n\n\tRECT itemBounds;\n\tRECT itemIcon;\n\tRECT itemLabel;\n\tRECT subitemBounds;\n\tRECT subitemIcon;\n\tRECT subitemLabel;\n\n\tLRESULT bitmapMargin;\n\tint cxIcon;\n\tint cyIcon;\n\n\tRECT realTextBackground;\n\tRECT realTextRect;\n};\nextern HRESULT uiprivTableGetMetrics(uiTable *t, int iItem, int iSubItem, uiprivTableMetrics **mout);\n"
  },
  {
    "path": "windows/tabledispinfo.cpp",
    "content": "// 13 june 2018\n#include \"uipriv_windows.hpp\"\n#include \"table.hpp\"\n\n// further reading:\n// - https://msdn.microsoft.com/en-us/library/ye4z8x58.aspx\n\nstatic HRESULT handleLVIF_TEXT(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p)\n{\n\tint strcol;\n\tuiTableValue *value;\n\tWCHAR *wstr;\n\tint progress;\n\tHRESULT hr;\n\n\tif ((nm->item.mask & LVIF_TEXT) == 0)\n\t\treturn S_OK;\n\n\tstrcol = -1;\n\tif (p->textModelColumn != -1)\n\t\tstrcol = p->textModelColumn;\n\telse if (p->buttonModelColumn != -1)\n\t\tstrcol = p->buttonModelColumn;\n\tif (strcol != -1) {\n\t\tvalue = uiprivTableModelCellValue(t->model, nm->item.iItem, strcol);\n\t\twstr = toUTF16(uiTableValueString(value));\n\t\tuiFreeTableValue(value);\n\t\t// We *could* just make pszText into a freshly allocated\n\t\t// conversion and avoid the limitation of cchTextMax.\n\t\t// But then, we would have to keep things around for some\n\t\t// amount of time (some pages on MSDN say 2 additional\n\t\t// LVN_GETDISPINFO messages). And in practice, anything\n\t\t// that results in extra LVN_GETDISPINFO messages (such\n\t\t// as LVN_GETITEMRECT with LVIR_LABEL) will break this\n\t\t// counting.\n\t\t// 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)\n\t\twcsncpy(nm->item.pszText, wstr, nm->item.cchTextMax);\n\t\tnm->item.pszText[nm->item.cchTextMax - 1] = L'\\0';\n\t\tuiprivFree(wstr);\n\t\treturn S_OK;\n\t}\n\n\tif (p->progressBarModelColumn != -1) {\n\t\tprogress = uiprivTableProgress(t, nm->item.iItem, nm->item.iSubItem, p->progressBarModelColumn, NULL);\n\n\t\tif (progress == -1) {\n\t\t\t// TODO either localize this or replace it with something that's language-neutral\n\t\t\t// TODO ensure null terminator\n\t\t\twcsncpy(nm->item.pszText, L\"Indeterminate\", nm->item.cchTextMax);\n\t\t\treturn S_OK;\n\t\t}\n\t\t// TODO ensure null terminator\n\t\t_snwprintf(nm->item.pszText, nm->item.cchTextMax, L\"%d%%\", progress);\n\t\treturn S_OK;\n\t}\n\n\treturn S_OK;\n}\n\nstatic HRESULT handleLVIF_IMAGE(uiTable *t, NMLVDISPINFOW *nm, uiprivTableColumnParams *p)\n{\n\tuiTableValue *value;\n\tHRESULT hr;\n\n\tif (nm->item.iSubItem == 0 && p->imageModelColumn == -1 && p->checkboxModelColumn == -1) {\n\t\t// Having an image list always leaves space for an image\n\t\t// on the main item :|\n\t\t// Other places on the internet imply that you should be\n\t\t// able to do this but that it shouldn't work, but it works\n\t\t// perfectly (and pixel-perfectly too) for me, so...\n\t\tnm->item.mask |= LVIF_INDENT;\n\t\tnm->item.iIndent = -1;\n\t}\n\tif ((nm->item.mask & LVIF_IMAGE) == 0)\n\t\treturn S_OK;\t\t// nothing to do here\n\n\t// TODO see if the -1 part is correct\n\t// TODO see if we should use state instead of images for checkbox value\n\tnm->item.iImage = -1;\n\tif (p->imageModelColumn != -1 || p->checkboxModelColumn != -1)\n\t\tnm->item.iImage = 0;\n\treturn S_OK;\n}\n\nHRESULT uiprivTableHandleLVN_GETDISPINFO(uiTable *t, NMLVDISPINFOW *nm, LRESULT *lResult)\n{\n\tuiprivTableColumnParams *p;\n\tHRESULT hr;\n\n\tp = (*(t->columns))[nm->item.iSubItem];\n\thr = handleLVIF_TEXT(t, nm, p);\n\tif (hr != S_OK)\n\t\treturn hr;\n\thr = handleLVIF_IMAGE(t, nm, p);\n\tif (hr != S_OK)\n\t\treturn hr;\n\t*lResult = 0;\n\treturn S_OK;\n}\n"
  },
  {
    "path": "windows/tabledraw.cpp",
    "content": "// 14 june 2018\n#include \"uipriv_windows.hpp\"\n#include \"table.hpp\"\n\n// TODOs:\n// - properly hide selection when not focused (or switch on LVS_SHOWSELALWAYS and draw that state)\n\n// TODO maybe split this into item and subitem structs?\nstruct drawState {\n\tuiTable *t;\n\tuiTableModel *model;\n\tuiprivTableColumnParams *p;\n\n\tHDC dc;\n\tint iItem;\n\tint iSubItem;\n\n\tuiprivTableMetrics *m;\n\n\tCOLORREF bgColor;\n\tHBRUSH bgBrush;\n\tBOOL freeBgBrush;\n\tCOLORREF textColor;\n\tHBRUSH textBrush;\n\tBOOL freeTextBrush;\n};\n\nstatic HRESULT drawBackgrounds(HRESULT hr, struct drawState *s)\n{\n\tif (hr != S_OK)\n\t\treturn hr;\n\tif (s->m->hasImage)\n\t\tif (FillRect(s->dc, &(s->m->subitemIcon), GetSysColorBrush(COLOR_WINDOW)) == 0) {\n\t\t\tlogLastError(L\"FillRect() icon\");\n\t\t\treturn E_FAIL;\n\t\t}\n\tif (FillRect(s->dc, &(s->m->realTextBackground), s->bgBrush) == 0) {\n\t\tlogLastError(L\"FillRect()\");\n\t\treturn E_FAIL;\n\t}\n\treturn S_OK;\n}\n\nstatic void centerImageRect(RECT *image, RECT *space)\n{\n\tLONG xoff, yoff;\n\n\t// first make sure both have the same upper-left\n\txoff = image->left - space->left;\n\tyoff = image->top - space->top;\n\timage->left -= xoff;\n\timage->top -= yoff;\n\timage->right -= xoff;\n\timage->bottom -= yoff;\n\n\t// now center\n\txoff = ((space->right - space->left) - (image->right - image->left)) / 2;\n\tyoff = ((space->bottom - space->top) - (image->bottom - image->top)) / 2;\n\timage->left += xoff;\n\timage->top += yoff;\n\timage->right += xoff;\n\timage->bottom += yoff;\n}\n\nstatic HRESULT drawImagePart(HRESULT hr, struct drawState *s)\n{\n\tuiTableValue *value;\n\tIWICBitmap *wb;\n\tHBITMAP b;\n\tRECT r;\n\tUINT fStyle;\n\n\tif (hr != S_OK)\n\t\treturn hr;\n\tif (s->p->imageModelColumn == -1)\n\t\treturn S_OK;\n\n\tvalue = uiprivTableModelCellValue(s->model, s->iItem, s->p->imageModelColumn);\n\twb = uiprivImageAppropriateForDC(uiTableValueImage(value), s->dc);\n\tuiFreeTableValue(value);\n\n\thr = uiprivWICToGDI(wb, s->dc, s->m->cxIcon, s->m->cyIcon, &b);\n\tif (hr != S_OK)\n\t\treturn hr;\n\t// TODO rewrite this condition to make more sense; possibly swap the if and else blocks too\n\t// TODO proper cleanup\n\tif (ImageList_GetImageCount(s->t->imagelist) > 1) {\n\t\tif (ImageList_Replace(s->t->imagelist, 0, b, NULL) == 0) {\n\t\t\tlogLastError(L\"ImageList_Replace()\");\n\t\t\treturn E_FAIL;\n\t\t}\n\t} else\n\t\tif (ImageList_Add(s->t->imagelist, b, NULL) == -1) {\n\t\t\tlogLastError(L\"ImageList_Add()\");\n\t\t\treturn E_FAIL;\n\t\t}\n\t// TODO error check\n\tDeleteObject(b);\n\n\tr = s->m->subitemIcon;\n\tr.right = r.left + s->m->cxIcon;\n\tr.bottom = r.top + s->m->cyIcon;\n\tcenterImageRect(&r, &(s->m->subitemIcon));\n\tfStyle = ILD_NORMAL;\n\tif (s->m->selected)\n\t\tfStyle = ILD_SELECTED;\n\tif (ImageList_Draw(s->t->imagelist, 0,\n\t\ts->dc, r.left, r.top, fStyle) == 0) {\n\t\tlogLastError(L\"ImageList_Draw()\");\n\t\treturn E_FAIL;\n\t}\n\treturn S_OK;\n}\n\n// references for checkbox drawing:\n// - https://blogs.msdn.microsoft.com/oldnewthing/20171129-00/?p=97485\n// - https://blogs.msdn.microsoft.com/oldnewthing/20171201-00/?p=97505\n\nstatic HRESULT drawUnthemedCheckbox(struct drawState *s, int checked, int enabled)\n{\n\tRECT r;\n\tUINT state;\n\n\tr = s->m->subitemIcon;\n\t// this is what the actual list view LVS_EX_CHECKBOXES code does to size the checkboxes\n\t// TODO reverify the initial size\n\tr.right = r.left + GetSystemMetrics(SM_CXSMICON);\n\tr.bottom = r.top + GetSystemMetrics(SM_CYSMICON);\n\tif (InflateRect(&r, -GetSystemMetrics(SM_CXEDGE), -GetSystemMetrics(SM_CYEDGE)) == 0) {\n\t\tlogLastError(L\"InflateRect()\");\n\t\treturn E_FAIL;\n\t}\n\tr.right++;\n\tr.bottom++;\n\n\tcenterImageRect(&r, &(s->m->subitemIcon));\n\tstate = DFCS_BUTTONCHECK | DFCS_FLAT;\n\tif (checked)\n\t\tstate |= DFCS_CHECKED;\n\tif (!enabled)\n\t\tstate |= DFCS_INACTIVE;\n\tif (DrawFrameControl(s->dc, &r, DFC_BUTTON, state) == 0) {\n\t\tlogLastError(L\"DrawFrameControl()\");\n\t\treturn E_FAIL;\n\t}\n\treturn S_OK;\n}\n\nstatic HRESULT drawThemedCheckbox(struct drawState *s, HTHEME theme, int checked, int enabled)\n{\n\tRECT r;\n\tSIZE size;\n\tint state;\n\tHRESULT hr;\n\n\thr = GetThemePartSize(theme, s->dc,\n\t\tBP_CHECKBOX, CBS_UNCHECKEDNORMAL,\n\t\tNULL, TS_DRAW, &size);\n\tif (hr != S_OK) {\n\t\tlogHRESULT(L\"GetThemePartSize()\", hr);\n\t\treturn hr;\t\t\t// TODO fall back?\n\t}\n\tr = s->m->subitemIcon;\n\tr.right = r.left + size.cx;\n\tr.bottom = r.top + size.cy;\n\n\tcenterImageRect(&r, &(s->m->subitemIcon));\n\tif (!checked && enabled)\n\t\tstate = CBS_UNCHECKEDNORMAL;\n\telse if (checked && enabled)\n\t\tstate = CBS_CHECKEDNORMAL;\n\telse if (!checked && !enabled)\n\t\tstate = CBS_UNCHECKEDDISABLED;\n\telse\n\t\tstate = CBS_CHECKEDDISABLED;\n\thr = DrawThemeBackground(theme, s->dc,\n\t\tBP_CHECKBOX, state,\n\t\t&r, NULL);\n\tif (hr != S_OK) {\n\t\tlogHRESULT(L\"DrawThemeBackground()\", hr);\n\t\treturn hr;\n\t}\n\treturn S_OK;\n}\n\nstatic HRESULT drawCheckboxPart(HRESULT hr, struct drawState *s)\n{\n\tuiTableValue *value;\n\tint checked, enabled;\n\tHTHEME theme;\n\n\tif (hr != S_OK)\n\t\treturn hr;\n\tif (s->p->checkboxModelColumn == -1)\n\t\treturn S_OK;\n\n\tvalue = uiprivTableModelCellValue(s->model, s->iItem, s->p->checkboxModelColumn);\n\tchecked = uiTableValueInt(value);\n\tuiFreeTableValue(value);\n\tenabled = uiprivTableModelCellEditable(s->model, s->iItem, s->p->checkboxEditableModelColumn);\n\n\ttheme = OpenThemeData(s->t->hwnd, L\"button\");\n\tif (theme != NULL) {\n\t\thr = drawThemedCheckbox(s, theme, checked, enabled);\n\t\tif (hr != S_OK)\n\t\t\treturn hr;\n\t\thr = CloseThemeData(theme);\n\t\tif (hr != S_OK) {\n\t\t\tlogHRESULT(L\"CloseThemeData()\", hr);\n\t\t\treturn hr;\n\t\t}\n\t} else {\n\t\thr = drawUnthemedCheckbox(s, checked, enabled);\n\t\tif (hr != S_OK)\n\t\t\treturn hr;\n\t}\n\treturn S_OK;\n}\n\nstatic HRESULT drawTextPart(HRESULT hr, struct drawState *s)\n{\n\tCOLORREF prevText;\n\tint prevMode;\n\tRECT r;\n\tuiTableValue *value;\n\tWCHAR *wstr;\n\n\tif (hr != S_OK)\n\t\treturn hr;\n\tif (!s->m->hasText)\n\t\treturn S_OK;\n\t// don't draw the text underneath an edit control\n\tif (s->t->edit != NULL && s->t->editedItem == s->iItem && s->t->editedSubitem == s->iSubItem)\n\t\treturn S_OK;\n\n\tprevText = SetTextColor(s->dc, s->textColor);\n\tif (prevText == CLR_INVALID) {\n\t\tlogLastError(L\"SetTextColor()\");\n\t\treturn E_FAIL;\n\t}\n\tprevMode = SetBkMode(s->dc, TRANSPARENT);\n\tif (prevMode == 0) {\n\t\tlogLastError(L\"SetBkMode()\");\n\t\treturn E_FAIL;\n\t}\n\n\tvalue = uiprivTableModelCellValue(s->model, s->iItem, s->p->textModelColumn);\n\twstr = toUTF16(uiTableValueString(value));\n\tuiFreeTableValue(value);\n\t// These flags are a menagerie of flags from various sources:\n\t// guessing, the Windows 2000 source leak, various custom\n\t// draw examples on the web, etc.\n\t// TODO find the real correct flags\n\tif (DrawTextW(s->dc, wstr, -1, &(s->m->realTextRect), DT_LEFT | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX | DT_EDITCONTROL) == 0) {\n\t\tuiprivFree(wstr);\n\t\tlogLastError(L\"DrawTextW()\");\n\t\treturn E_FAIL;\n\t}\n\tuiprivFree(wstr);\n\n\t// TODO decide once and for all what to compare to here and with SelectObject()\n\tif (SetBkMode(s->dc, prevMode) != TRANSPARENT) {\n\t\tlogLastError(L\"SetBkMode() prev\");\n\t\treturn E_FAIL;\n\t}\n\tif (SetTextColor(s->dc, prevText) != s->textColor) {\n\t\tlogLastError(L\"SetTextColor() prev\");\n\t\treturn E_FAIL;\n\t}\n\treturn S_OK;\n}\n\n// much of this is to imitate what shell32.dll's CDrawProgressBar does\n#define indeterminateSegments 8\n\nstatic HRESULT drawProgressBarPart(HRESULT hr, struct drawState *s)\n{\n\tint progress;\n\tLONG indeterminatePos;\n\tHTHEME theme;\n\tRECT r;\n\tRECT rBorder, rFill[2];\n\tint i, nFill;\n\tTEXTMETRICW tm;\n\tint sysColor;\n\n\tif (hr != S_OK)\n\t\treturn hr;\n\tif (s->p->progressBarModelColumn == -1)\n\t\treturn S_OK;\n\n\tprogress = uiprivTableProgress(s->t, s->iItem, s->iSubItem, s->p->progressBarModelColumn, &indeterminatePos);\n\n\ttheme = OpenThemeData(s->t->hwnd, L\"PROGRESS\");\n\n\tif (GetTextMetricsW(s->dc, &tm) == 0) {\n\t\tlogLastError(L\"GetTextMetricsW()\");\n\t\thr = E_FAIL;\n\t\tgoto fail;\n\t}\n\tr = s->m->subitemBounds;\n\t// this sets the height of the progressbar and vertically centers it in one fell swoop\n\tr.top += (r.bottom - tm.tmHeight - r.top) / 2;\n\tr.bottom = r.top + tm.tmHeight;\n\n\t// TODO check errors\n\trBorder = r;\n\tInflateRect(&rBorder, -1, -1);\n\tif (theme != NULL) {\n\t\tRECT crect;\n\n\t\thr = GetThemeBackgroundContentRect(theme, s->dc,\n\t\t\tPP_TRANSPARENTBAR, PBBS_NORMAL,\n\t\t\t&rBorder, &crect);\n\t\tif (hr != S_OK) {\n\t\t\tlogHRESULT(L\"GetThemeBackgroundContentRect()\", hr);\n\t\t\tgoto fail;\n\t\t}\n\t\thr = DrawThemeBackground(theme, s->dc,\n\t\t\tPP_TRANSPARENTBAR, PBBS_NORMAL,\n\t\t\t&crect, NULL);\n\t\tif (hr != S_OK) {\n\t\t\tlogHRESULT(L\"DrawThemeBackground() border\", hr);\n\t\t\tgoto fail;\n\t\t}\n\t} else {\n\t\tHPEN pen, prevPen;\n\t\tHBRUSH brush, prevBrush;\n\n\t\tsysColor = COLOR_HIGHLIGHT;\n\t\tif (s->m->selected)\n\t\t\tsysColor = COLOR_HIGHLIGHTTEXT;\n\n\t\t// TODO check errors everywhere\n\t\tpen = CreatePen(PS_SOLID, 1, GetSysColor(sysColor));\n\t\tprevPen = (HPEN) SelectObject(s->dc, pen);\n\t\tbrush = (HBRUSH) GetStockObject(NULL_BRUSH);\n\t\tprevBrush = (HBRUSH) SelectObject(s->dc, brush);\n\t\tRectangle(s->dc, rBorder.left, rBorder.top, rBorder.right, rBorder.bottom);\n\t\tSelectObject(s->dc, prevBrush);\n\t\tSelectObject(s->dc, prevPen);\n\t\tDeleteObject(pen);\n\t}\n\n\tnFill = 1;\n\trFill[0] = r;\n\t// TODO check error\n\tInflateRect(&rFill[0], -1, -1);\n\tif (progress != -1)\n\t\trFill[0].right -= (rFill[0].right - rFill[0].left) * (100 - progress) / 100;\n\telse {\n\t\tLONG barWidth;\n\t\tLONG pieceWidth;\n\n\t\t// TODO explain all this\n\t\t// TODO this should really start the progressbar scrolling into view instead of already on screen when first set\n\t\trFill[1] = rFill[0];\t\t// save in case we need it\n\t\tbarWidth = rFill[0].right - rFill[0].left;\n\t\tpieceWidth = barWidth / indeterminateSegments;\n\t\trFill[0].left += indeterminatePos % barWidth;\n\t\tif ((rFill[0].left + pieceWidth) >= rFill[0].right) {\n\t\t\t// make this piece wrap back around\n\t\t\tnFill++;\n\t\t\trFill[1].right = rFill[1].left + (pieceWidth - (rFill[0].right - rFill[0].left));\n\t\t} else\n\t\t\trFill[0].right = rFill[0].left + pieceWidth;\n\t}\n\tfor (i = 0; i < nFill; i++)\n\t\tif (theme != NULL) {\n\t\t\thr = DrawThemeBackground(theme, s->dc,\n\t\t\t\tPP_FILL, PBFS_NORMAL,\n\t\t\t\t&rFill[i], NULL);\n\t\t\tif (hr != S_OK) {\n\t\t\t\tlogHRESULT(L\"DrawThemeBackground() fill\", hr);\n\t\t\t\tgoto fail;\n\t\t\t}\n\t\t} else\n\t\t\t// TODO check errors\n\t\t\tFillRect(s->dc, &rFill[i], GetSysColorBrush(sysColor));\n\n\thr = S_OK;\nfail:\n\t// TODO check errors\n\tif (theme != NULL)\n\t\tCloseThemeData(theme);\n\treturn hr;\n}\n\nstatic HRESULT drawButtonPart(HRESULT hr, struct drawState *s)\n{\n\tuiTableValue *value;\n\tWCHAR *wstr;\n\tbool enabled;\n\tHTHEME theme;\n\tRECT r;\n\tTEXTMETRICW tm;\n\n\tif (hr != S_OK)\n\t\treturn hr;\n\tif (s->p->buttonModelColumn == -1)\n\t\treturn S_OK;\n\n\tvalue = uiprivTableModelCellValue(s->model, s->iItem, s->p->buttonModelColumn);\n\twstr = toUTF16(uiTableValueString(value));\n\tuiFreeTableValue(value);\n\tenabled = uiprivTableModelCellEditable(s->model, s->iItem, s->p->buttonClickableModelColumn);\n\n\ttheme = OpenThemeData(s->t->hwnd, L\"button\");\n\n\tif (GetTextMetricsW(s->dc, &tm) == 0) {\n\t\tlogLastError(L\"GetTextMetricsW()\");\n\t\thr = E_FAIL;\n\t\tgoto fail;\n\t}\n\tr = s->m->subitemBounds;\n\n\tif (theme != NULL) {\n\t\tint state;\n\n\t\tstate = PBS_NORMAL;\n\t\tif (!enabled)\n\t\t\tstate = PBS_DISABLED;\n\t\thr = DrawThemeBackground(theme, s->dc,\n\t\t\tBP_PUSHBUTTON, state,\n\t\t\t&r, NULL);\n\t\tif (hr != S_OK) {\n\t\t\tlogHRESULT(L\"DrawThemeBackground()\", hr);\n\t\t\tgoto fail;\n\t\t}\n\t\t// TODO DT_EDITCONTROL?\n\t\t// TODO DT_PATH_ELLIPSIS DT_WORD_ELLIPSIS instead of DT_END_ELLIPSIS? a middle-ellipsis option would be ideal here\n\t\t// TODO is there a theme property we can get instead of hardcoding these flags? if not, make these flags a macro\n\t\thr = DrawThemeText(theme, s->dc,\n\t\t\tBP_PUSHBUTTON, state,\n\t\t\twstr, -1,\n\t\t\tDT_CENTER | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX, 0,\n\t\t\t&r);\n\t\tif (hr != S_OK) {\n\t\t\tlogHRESULT(L\"DrawThemeText()\", hr);\n\t\t\tgoto fail;\n\t\t}\n\t} else {\n\t\tUINT state;\n\t\tHBRUSH color, prevColor;\n\t\tint prevBkMode;\n\n\t\t// TODO check errors\n\t\t// TODO explain why we're not doing this in the themed case (it has to do with extra transparent pixels)\n\t\tInflateRect(&r, -1, -1);\n\t\tstate = DFCS_BUTTONPUSH;\n\t\tif (!enabled)\n\t\t\tstate |= DFCS_INACTIVE;\n\t\tif (DrawFrameControl(s->dc, &r, DFC_BUTTON, state) == 0) {\n\t\t\tlogLastError(L\"DrawFrameControl()\");\n\t\t\thr = E_FAIL;\n\t\t\tgoto fail;\n\t\t}\n\t\tcolor = GetSysColorBrush(COLOR_BTNTEXT);\n\t\t// TODO check errors for these two\n\t\tprevColor = (HBRUSH) SelectObject(s->dc, color);\n\t\tprevBkMode = SetBkMode(s->dc, TRANSPARENT);\n\t\t// TODO DT_EDITCONTROL?\n\t\t// TODO DT_PATH_ELLIPSIS DT_WORD_ELLIPSIS instead of DT_END_ELLIPSIS? a middle-ellipsis option would be ideal here\n\t\tif (DrawTextW(s->dc, wstr, -1, &r, DT_CENTER | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX) == 0) {\n\t\t\tlogLastError(L\"DrawTextW()\");\n\t\t\thr = E_FAIL;\n\t\t\tgoto fail;\n\t\t}\n\t\t// TODO check errors for these two\n\t\tSetBkMode(s->dc, prevBkMode);\n\t\tSelectObject(s->dc, prevColor);\n\t}\n\n\thr = S_OK;\nfail:\n\t// TODO check errors\n\tif (theme != NULL)\n\t\tCloseThemeData(theme);\n\tuiprivFree(wstr);\n\treturn hr;\n}\n\nstatic HRESULT freeDrawState(struct drawState *s)\n{\n\tHRESULT hr, hrret;\n\n\thrret = S_OK;\n\tif (s->m != NULL) {\n\t\tuiprivFree(s->m);\n\t\ts->m = NULL;\n\t}\n\tif (s->freeTextBrush) {\n\t\tif (DeleteObject(s->textBrush) == 0) {\n\t\t\tlogLastError(L\"DeleteObject()\");\n\t\t\thrret = E_FAIL;\n\t\t\t// continue cleaning up anyway\n\t\t}\n\t\ts->freeTextBrush = FALSE;\n\t}\n\tif (s->freeBgBrush) {\n\t\tif (DeleteObject(s->bgBrush) == 0) {\n\t\t\tlogLastError(L\"DeleteObject()\");\n\t\t\thrret = E_FAIL;\n\t\t\t// continue cleaning up anyway\n\t\t}\n\t\ts->freeBgBrush = FALSE;\n\t}\n\treturn hrret;\n}\n\nstatic COLORREF blend(COLORREF base, double r, double g, double b, double a)\n{\n\tdouble br, bg, bb;\n\n\tbr = ((double) GetRValue(base)) / 255.0;\n\tbg = ((double) GetGValue(base)) / 255.0;\n\tbb = ((double) GetBValue(base)) / 255.0;\n\n\tbr = (r * a) + (br * (1.0 - a));\n\tbg = (g * a) + (bg * (1.0 - a));\n\tbb = (b * a) + (bb * (1.0 - a));\n\treturn RGB((BYTE) (br * 255),\n\t\t(BYTE) (bg * 255),\n\t\t(BYTE) (bb * 255));\n}\n\nstatic HRESULT fillDrawState(struct drawState *s, uiTable *t, NMLVCUSTOMDRAW *nm, uiprivTableColumnParams *p)\n{\n\tLRESULT state;\n\tHWND header;\n\tHRESULT hr;\n\n\tZeroMemory(s, sizeof (struct drawState));\n\ts->t = t;\n\ts->model = t->model;\n\ts->p = p;\n\n\ts->dc = nm->nmcd.hdc;\n\ts->iItem = nm->nmcd.dwItemSpec;\n\ts->iSubItem = nm->iSubItem;\n\n\thr = uiprivTableGetMetrics(t, s->iItem, s->iSubItem, &(s->m));\n\tif (hr != S_OK)\n\t\tgoto fail;\n\n\tif (s->m->selected) {\n\t\ts->bgColor = GetSysColor(COLOR_HIGHLIGHT);\n\t\ts->bgBrush = GetSysColorBrush(COLOR_HIGHLIGHT);\n\t\ts->textColor = GetSysColor(COLOR_HIGHLIGHTTEXT);\n\t\ts->textBrush = GetSysColorBrush(COLOR_HIGHLIGHTTEXT);\n\t} else {\n\t\tdouble r, g, b, a;\n\n\t\ts->bgColor = GetSysColor(COLOR_WINDOW);\n\t\ts->bgBrush = GetSysColorBrush(COLOR_WINDOW);\n\t\tif (uiprivTableModelColorIfProvided(s->model, s->iItem, t->backgroundColumn, &r, &g, &b, &a)) {\n\t\t\ts->bgColor = blend(s->bgColor, r, g, b, a);\n\t\t\ts->bgBrush = CreateSolidBrush(s->bgColor);\n\t\t\tif (s->bgBrush == NULL) {\n\t\t\t\tlogLastError(L\"CreateSolidBrush()\");\n\t\t\t\thr = E_FAIL;\n\t\t\t\tgoto fail;\n\t\t\t}\n\t\t\ts->freeBgBrush = TRUE;\n\t\t}\n\t\ts->textColor = GetSysColor(COLOR_WINDOWTEXT);\n\t\ts->textBrush = GetSysColorBrush(COLOR_WINDOWTEXT);\n\t\tif (uiprivTableModelColorIfProvided(s->model, s->iItem, p->textParams.ColorModelColumn, &r, &g, &b, &a)) {\n\t\t\ts->textColor = blend(s->bgColor, r, g, b, a);\n\t\t\ts->textBrush = CreateSolidBrush(s->textColor);\n\t\t\tif (s->textBrush == NULL) {\n\t\t\t\tlogLastError(L\"CreateSolidBrush()\");\n\t\t\t\thr = E_FAIL;\n\t\t\t\tgoto fail;\n\t\t\t}\n\t\t\ts->freeTextBrush = TRUE;\n\t\t}\n\t}\n\n\treturn S_OK;\nfail:\n\t// ignore the error; we need to return the one we got above\n\tfreeDrawState(s);\n\treturn hr;\n}\n\nstatic HRESULT updateAndDrawFocusRects(HRESULT hr, uiTable *t, HDC dc, int iItem, RECT *realTextBackground, RECT *focus, bool *first)\n{\n\tLRESULT state;\n\n\tif (hr != S_OK)\n\t\treturn hr;\n\tif (GetFocus() != t->hwnd)\n\t\treturn S_OK;\n\t// 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\n\t// TODO only call this once\n\tstate = SendMessageW(t->hwnd, LVM_GETITEMSTATE, (WPARAM) iItem, (LRESULT) (LVIS_FOCUSED));\n\tif ((state & LVIS_FOCUSED) == 0)\n\t\treturn S_OK;\n\n\tif (realTextBackground != NULL)\n\t\tif (*first) {\n\t\t\t*focus = *realTextBackground;\n\t\t\t*first = false;\n\t\t\treturn S_OK;\n\t\t} else if (focus->right == realTextBackground->left) {\n\t\t\tfocus->right = realTextBackground->right;\n\t\t\treturn S_OK;\n\t\t}\n\tif (DrawFocusRect(dc, focus) == 0) {\n\t\tlogLastError(L\"DrawFocusRect()\");\n\t\treturn E_FAIL;\n\t}\n\tif (realTextBackground != NULL)\n\t\t*focus = *realTextBackground;\n\treturn S_OK;\n}\n\n// normally we would only draw stuff in subitem stages\n// 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\n// so now we do everything in the item prepaint stage\n// TODO consider switching to full-on owner draw\n// TODO only compute the background brushes once?\nHRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *lResult)\n{\n\tstruct drawState s;\n\tuiprivTableColumnParams *p;\n\tNMLVCUSTOMDRAW b;\n\tsize_t i, n;\n\tRECT focus;\n\tbool focusFirst;\n\tHRESULT hr;\n\n\tswitch (nm->nmcd.dwDrawStage) {\n\tcase CDDS_PREPAINT:\n\t\t*lResult = CDRF_NOTIFYITEMDRAW;\n\t\treturn S_OK;\n\tcase CDDS_ITEMPREPAINT:\n\t\tbreak;\n\tdefault:\n\t\t*lResult = CDRF_DODEFAULT;\n\t\treturn S_OK;\n\t}\n\n\tn = t->columns->size();\n\tb = *nm;\n\tfocusFirst = true;\n\tfor (i = 0; i < n; i++) {\n\t\tb.iSubItem = i;\n\t\tp = (*(t->columns))[i];\n\t\thr = fillDrawState(&s, t, &b, p);\n\t\tif (hr != S_OK)\n\t\t\treturn hr;\n\t\thr = drawBackgrounds(S_OK, &s);\n\t\thr = drawImagePart(hr, &s);\n\t\thr = drawCheckboxPart(hr, &s);\n\t\thr = drawTextPart(hr, &s);\n\t\thr = drawProgressBarPart(hr, &s);\n\t\thr = drawButtonPart(hr, &s);\n\t\thr = updateAndDrawFocusRects(hr, s.t, s.dc, nm->nmcd.dwItemSpec, &(s.m->realTextBackground), &focus, &focusFirst);\n\t\tif (hr != S_OK)\n\t\t\tgoto fail;\n\t\thr = freeDrawState(&s);\n\t\tif (hr != S_OK)\t\t// TODO really error out here?\n\t\t\treturn hr;\n\t}\n\t// and draw the last focus rect\n\thr = updateAndDrawFocusRects(hr, t, nm->nmcd.hdc, nm->nmcd.dwItemSpec, NULL, &focus, &focusFirst);\n\tif (hr != S_OK)\n\t\treturn hr;\n\t*lResult = CDRF_SKIPDEFAULT;\n\treturn S_OK;\nfail:\n\t// ignore error here\n\t// TODO this is awkward cleanup placement for something that only really exists in a for loop\n\tfreeDrawState(&s);\n\treturn hr;\n}\n\n// TODO run again when the DPI or the theme changes\n// TODO properly clean things up here\n// TODO properly destroy the old lists here too\nHRESULT uiprivUpdateImageListSize(uiTable *t)\n{\n\tHDC dc;\n\tint cxList, cyList;\n\tHTHEME theme;\n\tSIZE sizeCheck;\n\tHRESULT hr;\n\n\tdc = GetDC(t->hwnd);\n\tif (dc == NULL) {\n\t\tlogLastError(L\"GetDC()\");\n\t\treturn E_FAIL;\n\t}\n\n\tcxList = GetSystemMetrics(SM_CXSMICON);\n\tcyList = GetSystemMetrics(SM_CYSMICON);\n\tsizeCheck.cx = cxList;\n\tsizeCheck.cy = cyList;\n\ttheme = OpenThemeData(t->hwnd, L\"button\");\n\tif (theme != NULL) {\n\t\thr = GetThemePartSize(theme, dc,\n\t\t\tBP_CHECKBOX, CBS_UNCHECKEDNORMAL,\n\t\t\tNULL, TS_DRAW, &sizeCheck);\n\t\tif (hr != S_OK) {\n\t\t\tlogHRESULT(L\"GetThemePartSize()\", hr);\n\t\t\treturn hr;\t\t\t// TODO fall back?\n\t\t}\n\t\t// make sure these checkmarks fit\n\t\t// unthemed checkmarks will by the code above be smaller than cxList/cyList here\n\t\tif (cxList < sizeCheck.cx)\n\t\t\tcxList = sizeCheck.cx;\n\t\tif (cyList < sizeCheck.cy)\n\t\t\tcyList = sizeCheck.cy;\n\t\thr = CloseThemeData(theme);\n\t\tif (hr != S_OK) {\n\t\t\tlogHRESULT(L\"CloseThemeData()\", hr);\n\t\t\treturn hr;\n\t\t}\n\t}\n\n\t// TODO handle errors\n\tt->imagelist = ImageList_Create(cxList, cyList,\n\t\tILC_COLOR32,\n\t\t1, 1);\n\tif (t->imagelist == NULL) {\n\t\tlogLastError(L\"ImageList_Create()\");\n\t\treturn E_FAIL;\n\t}\n\t// TODO will this return NULL here because it's an initial state?\n\tSendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM) (t->imagelist));\n\n\tif (ReleaseDC(t->hwnd, dc) == 0) {\n\t\tlogLastError(L\"ReleaseDC()\");\n\t\treturn E_FAIL;\n\t}\n\treturn S_OK;\n}\n"
  },
  {
    "path": "windows/tableediting.cpp",
    "content": "// 17 june 2018\n#include \"uipriv_windows.hpp\"\n#include \"table.hpp\"\n\n// TODOs\n// - clicking on the same item restarts editing instead of cancels it\n\n// 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\nstatic HRESULT resizeEdit(uiTable *t, WCHAR *wstr, int iItem, int iSubItem)\n{\n\tuiprivTableMetrics *m;\n\tRECT r;\n\tHDC dc;\n\tHFONT prevFont;\n\tTEXTMETRICW tm;\n\tSIZE textSize;\n\tRECT editRect, clientRect;\n\tHRESULT hr;\n\n\thr = uiprivTableGetMetrics(t, iItem, iSubItem, &m);\n\tif (hr != S_OK)\n\t\treturn hr;\n\tr = m->realTextRect;\n\tuiprivFree(m);\n\n\t// TODO check errors for all these\n\tdc = GetDC(t->hwnd);\t\t// use the list view DC since we're using its coordinate space\n\tprevFont = (HFONT) SelectObject(dc, hMessageFont);\n\tGetTextMetricsW(dc, &tm);\n\tGetTextExtentPoint32W(dc, wstr, wcslen(wstr), &textSize);\n\tSelectObject(dc, prevFont);\n\tReleaseDC(t->hwnd, dc);\n\n\tSendMessageW(t->edit, EM_GETRECT, 0, (LPARAM) (&editRect));\n\tr.left -= editRect.left;\n\t// find the top of the text\n\tr.top += ((r.bottom - r.top) - tm.tmHeight) / 2;\n\t// and move THAT by the right offset\n\tr.top -= editRect.top;\n\tr.right = r.left + textSize.cx;\n\t// the real listview does this to add some extra space at the end\n\t// TODO this still isn't enough space\n\tr.right += 4 * GetSystemMetrics(SM_CXEDGE) + GetSystemMetrics(SM_CYEDGE);\n\t// and make the bottom equally positioned to the top\n\tr.bottom = r.top + editRect.top + tm.tmHeight + editRect.top;\n\n\t// make sure the edit box doesn't stretch outside the listview\n\t// 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...\n\t// TODO check errors\n\tGetClientRect(t->hwnd, &clientRect);\n\tIntersectRect(&r, &r, &clientRect);\n\n\t// TODO check error or use the right function\n\tSetWindowPos(t->edit, NULL,\n\t\tr.left, r.top,\n\t\tr.right - r.left, r.bottom - r.top,\n\t\tSWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER);\n\treturn S_OK;\n}\n\n// the real list view intercepts these keys to control editing\nstatic LRESULT CALLBACK editSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIDSubclass, DWORD_PTR dwRefData)\n{\n\tuiTable *t = (uiTable *) dwRefData;\n\tHRESULT hr;\n\n\tswitch (uMsg) {\n\tcase WM_KEYDOWN:\n\t\tswitch (wParam) {\n\t\t// TODO handle VK_TAB and VK_SHIFT+VK_TAB\n\t\tcase VK_RETURN:\n\t\t\thr = uiprivTableFinishEditingText(t);\n\t\t\tif (hr != S_OK) {\n\t\t\t\t// TODO\n\t\t\t}\n\t\t\treturn 0;\t\t// yes, the real list view just returns here\n\t\tcase VK_ESCAPE:\n\t\t\thr = uiprivTableAbortEditingText(t);\n\t\t\tif (hr != S_OK) {\n\t\t\t\t// TODO\n\t\t\t}\n\t\t\treturn 0;\n\t\t}\n\t\tbreak;\n\t// the real list view also forces these flags\n\tcase WM_GETDLGCODE:\n\t\treturn DLGC_HASSETSEL | DLGC_WANTALLKEYS;\n\tcase WM_NCDESTROY:\n\t\tif (RemoveWindowSubclass(hwnd, editSubProc, uIDSubclass) == FALSE)\n\t\t\tlogLastError(L\"RemoveWindowSubclass()\");\n\t\t// fall through\n\t}\n\treturn DefSubclassProc(hwnd, uMsg, wParam, lParam);\n}\n\nstatic HRESULT openEditControl(uiTable *t, int iItem, int iSubItem, uiprivTableColumnParams *p)\n{\n\tuiTableValue *value;\n\tWCHAR *wstr;\n\tHRESULT hr;\n\n\t// the real list view accepts changes to the existing item when editing a new item\n\thr = uiprivTableFinishEditingText(t);\n\tif (hr != S_OK)\n\t\treturn hr;\n\n\t// the real list view creates the edit control with the string\n\tvalue = uiprivTableModelCellValue(t->model, iItem, p->textModelColumn);\n\twstr = toUTF16(uiTableValueString(value));\n\tuiFreeTableValue(value);\n\t// TODO copy WS_EX_RTLREADING\n\tt->edit = CreateWindowExW(0,\n\t\tL\"EDIT\", wstr,\n\t\t// these styles are what the normal listview edit uses\n\t\tWS_CHILD | WS_CLIPSIBLINGS | WS_BORDER | ES_AUTOHSCROLL,\n\t\t// as is this size\n\t\t0, 0, 16384, 16384,\n\t\t// and this control ID\n\t\tt->hwnd, (HMENU) 1, hInstance, NULL);\n\tif (t->edit == NULL) {\n\t\tlogLastError(L\"CreateWindowExW()\");\n\t\tuiprivFree(wstr);\n\t\treturn E_FAIL;\n\t}\n\tSendMessageW(t->edit, WM_SETFONT, (WPARAM) hMessageFont, (LPARAM) TRUE);\n\t// TODO check errors\n\tSetWindowSubclass(t->edit, editSubProc, 0, (DWORD_PTR) t);\n\n\thr = resizeEdit(t, wstr, iItem, iSubItem);\n\tif (hr != S_OK)\n\t\t// TODO proper cleanup\n\t\treturn hr;\n\t// TODO check errors on these two, if any\n\tSetFocus(t->edit);\n\tShowWindow(t->edit, SW_SHOW);\n\tSendMessageW(t->edit, EM_SETSEL, 0, (LPARAM) (-1));\n\n\tuiprivFree(wstr);\n\tt->editedItem = iItem;\n\tt->editedSubitem = iSubItem;\n\treturn S_OK;\n}\n\nHRESULT uiprivTableResizeWhileEditing(uiTable *t)\n{\n\tWCHAR *text;\n\tHRESULT hr;\n\n\tif (t->edit == NULL)\n\t\treturn S_OK;\n\ttext = windowText(t->edit);\n\thr = resizeEdit(t, text, t->editedItem, t->editedSubitem);\n\tuiprivFree(text);\n\treturn hr;\n}\n\nHRESULT uiprivTableFinishEditingText(uiTable *t)\n{\n\tuiprivTableColumnParams *p;\n\tuiTableValue *value;\n\tchar *text;\n\n\tif (t->edit == NULL)\n\t\treturn S_OK;\n\ttext = uiWindowsWindowText(t->edit);\n\tvalue = uiNewTableValueString(text);\n\tuiFreeText(text);\n\tp = (*(t->columns))[t->editedSubitem];\n\tuiprivTableModelSetCellValue(t->model, t->editedItem, p->textModelColumn, value);\n\tuiFreeTableValue(value);\n\t// always refresh the value in case the model rejected it\n\tif (SendMessageW(t->hwnd, LVM_UPDATE, (WPARAM) (t->editedItem), 0) == (LRESULT) (-1)) {\n\t\tlogLastError(L\"LVM_UPDATE\");\n\t\treturn E_FAIL;\n\t}\n\treturn uiprivTableAbortEditingText(t);\n}\n\nHRESULT uiprivTableAbortEditingText(uiTable *t)\n{\n\tHWND edit;\n\n\tif (t->edit == NULL)\n\t\treturn S_OK;\n\t// set t->edit to NULL now so we don't trigger commits on focus killed\n\tedit = t->edit;\n\tt->edit = NULL;\n\n\tif (DestroyWindow(edit) == 0) {\n\t\tlogLastError(L\"DestroyWindow()\");\n\t\treturn E_FAIL;\n\t}\n\treturn S_OK;\n}\n\nHRESULT uiprivTableHandleNM_CLICK(uiTable *t, NMITEMACTIVATE *nm, LRESULT *lResult)\n{\n\tLVHITTESTINFO ht;\n\tuiprivTableColumnParams *p;\n\tint modelColumn, editableColumn;\n\tbool text, checkbox;\n\tuiTableValue *value;\n\tint checked, editable;\n\tHRESULT hr;\n\n\tZeroMemory(&ht, sizeof (LVHITTESTINFO));\n\tht.pt = nm->ptAction;\n\tif (SendMessageW(t->hwnd, LVM_SUBITEMHITTEST, 0, (LPARAM) (&ht)) == (LRESULT) (-1))\n\t\tgoto done;\n\n\tmodelColumn = -1;\n\teditableColumn = -1;\n\ttext = false;\n\tcheckbox = false;\n\tp = (*(t->columns))[ht.iSubItem];\n\tif (p->textModelColumn != -1) {\n\t\tmodelColumn = p->textModelColumn;\n\t\teditableColumn = p->textEditableModelColumn;\n\t\ttext = true;\n\t} else if (p->checkboxModelColumn != -1) {\n\t\tmodelColumn = p->checkboxModelColumn;\n\t\teditableColumn = p->checkboxEditableModelColumn;\n\t\tcheckbox = true;\n\t} else if (p->buttonModelColumn != -1) {\n\t\tmodelColumn = p->buttonModelColumn;\n\t\teditableColumn = p->buttonClickableModelColumn;\n\t}\n\tif (modelColumn == -1)\n\t\tgoto done;\n\n\tif (text && t->inDoubleClickTimer)\n\t\t// don't even ask for info if it's too soon to edit text\n\t\tgoto done;\n\n\tif (!uiprivTableModelCellEditable(t->model, ht.iItem, editableColumn))\n\t\tgoto done;\n\n\tif (text) {\n\t\thr = openEditControl(t, ht.iItem, ht.iSubItem, p);\n\t\tif (hr != S_OK)\n\t\t\treturn hr;\n\t} else if (checkbox) {\n\t\tif ((ht.flags & LVHT_ONITEMICON) == 0)\n\t\t\tgoto done;\n\t\tvalue = uiprivTableModelCellValue(t->model, ht.iItem, modelColumn);\n\t\tchecked = uiTableValueInt(value);\n\t\tuiFreeTableValue(value);\n\t\tvalue = uiNewTableValueInt(!checked);\n\t\tuiprivTableModelSetCellValue(t->model, ht.iItem, modelColumn, value);\n\t\tuiFreeTableValue(value);\n\t} else\n\t\tuiprivTableModelSetCellValue(t->model, ht.iItem, modelColumn, NULL);\n\t// always refresh the value in case the model rejected it\n\tif (SendMessageW(t->hwnd, LVM_UPDATE, (WPARAM) (ht.iItem), 0) == (LRESULT) (-1)) {\n\t\tlogLastError(L\"LVM_UPDATE\");\n\t\treturn E_FAIL;\n\t}\n\ndone:\n\t*lResult = 0;\n\treturn S_OK;\n}\n"
  },
  {
    "path": "windows/tablemetrics.cpp",
    "content": "// 14 june 2018\n#include \"uipriv_windows.hpp\"\n#include \"table.hpp\"\n\nstatic HRESULT itemRect(HRESULT hr, uiTable *t, UINT uMsg, WPARAM wParam, LONG left, LONG top, LRESULT bad, RECT *r)\n{\n\tif (hr != S_OK)\n\t\treturn hr;\n\tZeroMemory(r, sizeof (RECT));\n\tr->left = left;\n\tr->top = top;\n\tif (SendMessageW(t->hwnd, uMsg, wParam, (LPARAM) r) == bad) {\n\t\tlogLastError(L\"itemRect() message\");\n\t\treturn E_FAIL;\n\t}\n\treturn S_OK;\n}\n\nHRESULT uiprivTableGetMetrics(uiTable *t, int iItem, int iSubItem, uiprivTableMetrics **mout)\n{\n\tuiprivTableMetrics *m;\n\tuiprivTableColumnParams *p;\n\tLRESULT state;\n\tHWND header;\n\tRECT r;\n\tHRESULT hr;\n\n\tif (mout == NULL)\n\t\treturn E_POINTER;\n\n\tm = uiprivNew(uiprivTableMetrics);\n\n\tp = (*(t->columns))[iSubItem];\n\tm->hasText = p->textModelColumn != -1;\n\tm->hasImage = (p->imageModelColumn != -1) || (p->checkboxModelColumn != -1);\n\tstate = SendMessageW(t->hwnd, LVM_GETITEMSTATE, iItem, LVIS_FOCUSED | LVIS_SELECTED);\n\tm->focused = (state & LVIS_FOCUSED) != 0;\n\tm->selected = (state & LVIS_SELECTED) != 0;\n\n\t// TODO check LRESULT bad parameters here\n\thr = itemRect(S_OK, t, LVM_GETITEMRECT, iItem,\n\t\tLVIR_BOUNDS, 0, FALSE, &(m->itemBounds));\n\thr = itemRect(hr, t, LVM_GETITEMRECT, iItem,\n\t\tLVIR_ICON, 0, FALSE, &(m->itemIcon));\n\thr = itemRect(hr, t, LVM_GETITEMRECT, iItem,\n\t\tLVIR_LABEL, 0, FALSE, &(m->itemLabel));\n\thr = itemRect(hr, t, LVM_GETSUBITEMRECT, iItem,\n\t\tLVIR_BOUNDS, iSubItem, 0, &(m->subitemBounds));\n\thr = itemRect(hr, t, LVM_GETSUBITEMRECT, iItem,\n\t\tLVIR_ICON, iSubItem, 0, &(m->subitemIcon));\n\tif (hr != S_OK)\n\t\tgoto fail;\n\t// LVM_GETSUBITEMRECT treats LVIR_LABEL as the same as\n\t// LVIR_BOUNDS, so we can't use that directly. Instead, let's\n\t// assume the text is immediately after the icon. The correct\n\t// rect will be determined by\n\t// computeOtherRectsAndDrawBackgrounds() above.\n\tm->subitemLabel = m->subitemBounds;\n\tm->subitemLabel.left = m->subitemIcon.right;\n\t// And on iSubItem == 0, LVIF_GETSUBITEMRECT still includes\n\t// all the subitems, which we don't want.\n\tif (iSubItem == 0) {\n\t\tm->subitemBounds.right = m->itemLabel.right;\n\t\tm->subitemLabel.right = m->itemLabel.right;\n\t}\n\n\theader = (HWND) SendMessageW(t->hwnd, LVM_GETHEADER, 0, 0);\n\tm->bitmapMargin = SendMessageW(header, HDM_GETBITMAPMARGIN, 0, 0);\n\tif (ImageList_GetIconSize(t->imagelist, &(m->cxIcon), &(m->cyIcon)) == 0) {\n\t\tlogLastError(L\"ImageList_GetIconSize()\");\n\t\thr = E_FAIL;\n\t\tgoto fail;\n\t}\n\n\tr = m->subitemLabel;\n\tif (!m->hasText && !m->hasImage)\n\t\tr = m->subitemBounds;\n\telse if (!m->hasImage && iSubItem != 0)\n\t\t// By default, this will include images; we're not drawing\n\t\t// images, so we will manually draw over the image area.\n\t\t// There's a second part to this; see below.\n\t\tr.left = m->subitemBounds.left;\n\tm->realTextBackground = r;\n\n\tm->realTextRect = r;\n\t// TODO confirm whether this really happens on column 0 as well\n\tif (m->hasImage && iSubItem != 0)\n\t\t// Normally there's this many hard-coded logical units\n\t\t// of blank space, followed by the background, followed\n\t\t// by a bitmap margin's worth of space. This looks bad,\n\t\t// so we overrule that to start the background immediately\n\t\t// and the text after the hard-coded amount.\n\t\tm->realTextRect.left += 2;\n\telse if (iSubItem != 0)\n\t\t// In the case of subitem text without an image, we draw\n\t\t// text one bitmap margin away from the left edge.\n\t\tm->realTextRect.left += m->bitmapMargin;\n\n\t*mout = m;\n\treturn S_OK;\nfail:\n\tuiprivFree(m);\n\t*mout = NULL;\n\treturn hr;\n}\n"
  },
  {
    "path": "windows/tabpage.cpp",
    "content": "// 30 may 2015\n#include \"uipriv_windows.hpp\"\n\n// TODO refine error handling\n\n// from http://msdn.microsoft.com/en-us/library/windows/desktop/bb226818%28v=vs.85%29.aspx\n#define tabMargin 7\n\nstatic void tabPageMargins(struct tabPage *tp, int *mx, int *my)\n{\n\tuiWindowsSizing sizing;\n\n\t*mx = 0;\n\t*my = 0;\n\tif (!tp->margined)\n\t\treturn;\n\tuiWindowsGetSizing(tp->hwnd, &sizing);\n\t*mx = tabMargin;\n\t*my = tabMargin;\n\tuiWindowsSizingDlgUnitsToPixels(&sizing, mx, my);\n}\n\nstatic void tabPageRelayout(struct tabPage *tp)\n{\n\tRECT r;\n\tint mx, my;\n\tHWND child;\n\n\tif (tp->child == NULL)\n\t\treturn;\n\tuiWindowsEnsureGetClientRect(tp->hwnd, &r);\n\ttabPageMargins(tp, &mx, &my);\n\tr.left += mx;\n\tr.top += my;\n\tr.right -= mx;\n\tr.bottom -= my;\n\tchild = (HWND) uiControlHandle(tp->child);\n\tuiWindowsEnsureMoveWindowDuringResize(child, r.left, r.top, r.right - r.left, r.bottom - r.top);\n}\n\n// dummy dialog procedure; see below for details\n// let's handle parent messages here to avoid needing to subclass\n// TODO do we need to handle DM_GETDEFID/DM_SETDEFID here too because of the ES_WANTRETURN stuff at http://blogs.msdn.com/b/oldnewthing/archive/2007/08/20/4470527.aspx? what about multiple default buttons (TODO)?\n// TODO we definitely need to do something about edit message handling; it does a fake close of our parent on pressing escape, causing uiWindow to stop responding to maximizes but still respond to events and then die horribly on destruction\nstatic INT_PTR CALLBACK dlgproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)\n{\n\tstruct tabPage *tp;\n\tLRESULT lResult;\n\n\tif (uMsg == WM_INITDIALOG) {\n\t\ttp = (struct tabPage *) lParam;\n\t\ttp->hwnd = hwnd;\n\t\tSetWindowLongPtrW(hwnd, DWLP_USER, (LONG_PTR) tp);\n\t\treturn TRUE;\n\t}\n\tif (handleParentMessages(hwnd, uMsg, wParam, lParam, &lResult) != FALSE) {\n\t\tSetWindowLongPtrW(hwnd, DWLP_MSGRESULT, (LONG_PTR) lResult);\n\t\treturn TRUE;\n\t}\n\tif (uMsg == WM_WINDOWPOSCHANGED) {\n\t\ttp = (struct tabPage *) GetWindowLongPtrW(hwnd, DWLP_USER);\n\t\ttabPageRelayout(tp);\n\t\t// pretend the dialog hasn't handled this just in case the system needs to do something special\n\t\treturn FALSE;\n\t}\n\n\t// unthemed dialogs don't respond to WM_PRINTCLIENT\n\t// fortunately they don't have any special painting\n\tif (uMsg == WM_PRINTCLIENT) {\n\t\t// don't worry about the return value; hopefully DefWindowProcW() caught it (if not the dialog procedure itself)\n\t\t// we COULD paint the dialog background brush ourselves but meh, it works\n\t\tSendMessageW(hwnd, WM_ERASEBKGND, wParam, lParam);\n\t\t// and pretend we did nothing just so the themed dialog can still paint its content\n\t\t// TODO see if w ecan avoid erasing the background in this case in the first place, or if we even need to\n\t\treturn FALSE;\n\t}\n\n\treturn FALSE;\n}\n\n// because Windows doesn't really support resources in static libraries, we have to embed this directly; oh well\n/*\n// this is the dialog template used by tab pages; see windows/tabpage.c for details\nrcTabPageDialog DIALOGEX 0, 0, 100, 100\nSTYLE DS_CONTROL | WS_CHILD | WS_VISIBLE\nEXSTYLE WS_EX_CONTROLPARENT\nBEGIN\n\t// nothing\nEND\n*/\nstatic const uint8_t data_rcTabPageDialog[] = {\n\t0x01, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,\n\t0x00, 0x00, 0x01, 0x00, 0x00, 0x04, 0x00, 0x50,\n\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00,\n\t0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n};\nstatic_assert(ARRAYSIZE(data_rcTabPageDialog) == 32, \"wrong size for resource rcTabPageDialog\");\n\nstruct tabPage *newTabPage(uiControl *child)\n{\n\tstruct tabPage *tp;\n\tHRESULT hr;\n\n\ttp = uiprivNew(struct tabPage);\n\n\t// unfortunately this needs to be a proper dialog for EnableThemeDialogTexture() to work; CreateWindowExW() won't suffice\n\tif (CreateDialogIndirectParamW(hInstance, (const DLGTEMPLATE *) data_rcTabPageDialog,\n\t\tutilWindow, dlgproc, (LPARAM) tp) == NULL)\n\t\tlogLastError(L\"error creating tab page\");\n\n\ttp->child = child;\n\tif (tp->child != NULL) {\n\t\tuiWindowsEnsureSetParentHWND((HWND) uiControlHandle(tp->child), tp->hwnd);\n\t\tuiWindowsControlAssignSoleControlIDZOrder(uiWindowsControl(tp->child));\n\t}\n\n\thr = EnableThemeDialogTexture(tp->hwnd, ETDT_ENABLE | ETDT_USETABTEXTURE | ETDT_ENABLETAB);\n\tif (hr != S_OK)\n\t\tlogHRESULT(L\"error setting tab page background\", hr);\n\t\t// continue anyway; it'll look wrong but eh\n\n\t// and start the tab page hidden\n\tShowWindow(tp->hwnd, SW_HIDE);\n\n\treturn tp;\n}\n\nvoid tabPageDestroy(struct tabPage *tp)\n{\n\t// don't destroy the child with the page\n\tif (tp->child != NULL)\n\t\tuiWindowsControlSetParentHWND(uiWindowsControl(tp->child), NULL);\n\t// don't call EndDialog(); that's for the DialogBox() family of functions instead of CreateDialog()\n\tuiWindowsEnsureDestroyWindow(tp->hwnd);\n\tuiprivFree(tp);\n}\n\nvoid tabPageMinimumSize(struct tabPage *tp, int *width, int *height)\n{\n\tint mx, my;\n\n\t*width = 0;\n\t*height = 0;\n\tif (tp->child != NULL)\n\t\tuiWindowsControlMinimumSize(uiWindowsControl(tp->child), width, height);\n\ttabPageMargins(tp, &mx, &my);\n\t*width += 2 * mx;\n\t*height += 2 * my;\n}\n"
  },
  {
    "path": "windows/text.cpp",
    "content": "// 9 april 2015\n#include \"uipriv_windows.hpp\"\n\nWCHAR *windowTextAndLen(HWND hwnd, LRESULT *len)\n{\n\tLRESULT n;\n\tWCHAR *text;\n\n\tn = SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0);\n\tif (len != NULL)\n\t\t*len = n;\n\t// WM_GETTEXTLENGTH does not include the null terminator\n\ttext = (WCHAR *) uiprivAlloc((n + 1) * sizeof (WCHAR), \"WCHAR[]\");\n\t// note the comparison: the size includes the null terminator, but the return does not\n\tif (GetWindowTextW(hwnd, text, n + 1) != n) {\n\t\tlogLastError(L\"error getting window text\");\n\t\t// on error, return an empty string to be safe\n\t\t*text = L'\\0';\n\t\tif (len != NULL)\n\t\t\t*len = 0;\n\t}\n\treturn text;\n}\n\nWCHAR *windowText(HWND hwnd)\n{\n\treturn windowTextAndLen(hwnd, NULL);\n}\n\nvoid setWindowText(HWND hwnd, WCHAR *wtext)\n{\n\tif (SetWindowTextW(hwnd, wtext) == 0)\n\t\tlogLastError(L\"error setting window text\");\n}\n\nvoid uiFreeText(char *text)\n{\n\tuiprivFree(text);\n}\n\nint uiWindowsWindowTextWidth(HWND hwnd)\n{\n\tLRESULT len;\n\tWCHAR *text;\n\tHDC dc;\n\tHFONT prevfont;\n\tSIZE size;\n\n\tsize.cx = 0;\n\tsize.cy = 0;\n\n\ttext = windowTextAndLen(hwnd, &len);\n\tif (len == 0)\t\t// no text; nothing to do\n\t\tgoto noTextOrError;\n\n\t// now we can do the calculations\n\tdc = GetDC(hwnd);\n\tif (dc == NULL) {\n\t\tlogLastError(L\"error getting DC\");\n\t\t// on any error, assume no text\n\t\tgoto noTextOrError;\n\t}\n\tprevfont = (HFONT) SelectObject(dc, hMessageFont);\n\tif (prevfont == NULL) {\n\t\tlogLastError(L\"error loading control font into device context\");\n\t\tReleaseDC(hwnd, dc);\n\t\tgoto noTextOrError;\n\t}\n\tif (GetTextExtentPoint32W(dc, text, len, &size) == 0) {\n\t\tlogLastError(L\"error getting text extent point\");\n\t\t// continue anyway, assuming size is 0\n\t\tsize.cx = 0;\n\t\tsize.cy = 0;\n\t}\n\t// continue on errors; we got what we want\n\tif (SelectObject(dc, prevfont) != hMessageFont)\n\t\tlogLastError(L\"error restoring previous font into device context\");\n\tif (ReleaseDC(hwnd, dc) == 0)\n\t\tlogLastError(L\"error releasing DC\");\n\n\tuiprivFree(text);\n\treturn size.cx;\n\nnoTextOrError:\n\tuiprivFree(text);\n\treturn 0;\n}\n\nchar *uiWindowsWindowText(HWND hwnd)\n{\n\tWCHAR *wtext;\n\tchar *text;\n\n\twtext = windowText(hwnd);\n\ttext = toUTF8(wtext);\n\tuiprivFree(wtext);\n\treturn text;\n}\n\nvoid uiWindowsSetWindowText(HWND hwnd, const char *text)\n{\n\tWCHAR *wtext;\n\n\twtext = toUTF16(text);\n\tsetWindowText(hwnd, wtext);\n\tuiprivFree(wtext);\n}\n\nint uiprivStricmp(const char *a, const char *b)\n{\n\tWCHAR *wa, *wb;\n\tint ret;\n\n\twa = toUTF16(a);\n\twb = toUTF16(b);\n\tret = _wcsicmp(wa, wb);\n\tuiprivFree(wb);\n\tuiprivFree(wa);\n\treturn ret;\n}\n"
  },
  {
    "path": "windows/uipriv_windows.hpp",
    "content": "// 21 april 2016\n#include \"winapi.hpp\"\n#include \"../ui.h\"\n#include \"../ui_windows.h\"\n#include \"../common/uipriv.h\"\n#include \"resources.hpp\"\n#include \"compilerver.hpp\"\n\n// ui internal window messages\n// TODO make these either not messages or WM_USER-based, so we can be sane about reserving WM_APP\nenum {\n\t// redirected WM_COMMAND and WM_NOTIFY\n\tmsgCOMMAND = WM_APP + 0x40,\t\t// start offset just to be safe\n\tmsgNOTIFY,\n\tmsgHSCROLL,\n\tmsgQueued,\n\tmsgD2DScratchPaint,\n\tmsgD2DScratchLButtonDown,\n};\n\n// alloc.cpp\nextern void initAlloc(void);\nextern void uninitAlloc(void);\n\n// events.cpp\nextern BOOL runWM_COMMAND(WPARAM wParam, LPARAM lParam, LRESULT *lResult);\nextern BOOL runWM_NOTIFY(WPARAM wParam, LPARAM lParam, LRESULT *lResult);\nextern BOOL runWM_HSCROLL(WPARAM wParam, LPARAM lParam, LRESULT *lResult);\nextern void issueWM_WININICHANGE(WPARAM wParam, LPARAM lParam);\n\n// utf16.cpp\n#define emptyUTF16() ((WCHAR *) uiprivAlloc(1 * sizeof (WCHAR), \"WCHAR[]\"))\n#define emptyUTF8() ((char *) uiprivAlloc(1 * sizeof (char), \"char[]\"))\nextern WCHAR *toUTF16(const char *str);\nextern char *toUTF8(const WCHAR *wstr);\nextern WCHAR *utf16dup(const WCHAR *orig);\nextern WCHAR *strf(const WCHAR *format, ...);\nextern WCHAR *vstrf(const WCHAR *format, va_list ap);\nextern char *LFtoCRLF(const char *lfonly);\nextern void CRLFtoLF(char *s);\nextern WCHAR *ftoutf16(double d);\nextern WCHAR *itoutf16(int i);\n\n// debug.cpp\n// see http://stackoverflow.com/questions/14421656/is-there-widely-available-wide-character-variant-of-file\n// we turn __LINE__ into a string because PRIiMAX can't be converted to a wide string in MSVC (it seems to be defined as \"ll\" \"i\" according to the compiler errors)\n// also note the use of __FUNCTION__ here; __func__ doesn't seem to work for some reason\n#define _ws2(m) L ## m\n#define _ws(m) _ws2(m)\n#define _ws2n(m) L ## #m\n#define _wsn(m) _ws2n(m)\n#define debugargs const WCHAR *file, const WCHAR *line, const WCHAR *func\nextern HRESULT _logLastError(debugargs, const WCHAR *s);\n#ifdef _MSC_VER\n#define logLastError(s) _logLastError(_ws(__FILE__), _wsn(__LINE__), _ws(__FUNCTION__), s)\n#else\n#define logLastError(s) _logLastError(_ws(__FILE__), _wsn(__LINE__), L\"TODO none of the function name macros are macros in MinGW\", s)\n#endif\nextern HRESULT _logHRESULT(debugargs, const WCHAR *s, HRESULT hr);\n#ifdef _MSC_VER\n#define logHRESULT(s, hr) _logHRESULT(_ws(__FILE__), _wsn(__LINE__), _ws(__FUNCTION__), s, hr)\n#else\n#define logHRESULT(s, hr) _logHRESULT(_ws(__FILE__), _wsn(__LINE__), L\"TODO none of the function name macros are macros in MinGW\", s, hr)\n#endif\n\n// winutil.cpp\nextern int windowClassOf(HWND hwnd, ...);\nextern void mapWindowRect(HWND from, HWND to, RECT *r);\nextern DWORD getStyle(HWND hwnd);\nextern void setStyle(HWND hwnd, DWORD style);\nextern DWORD getExStyle(HWND hwnd);\nextern void setExStyle(HWND hwnd, DWORD exstyle);\nextern void clientSizeToWindowSize(HWND hwnd, int *width, int *height, BOOL hasMenubar);\nextern HWND parentOf(HWND child);\nextern HWND parentToplevel(HWND child);\nextern void setWindowInsertAfter(HWND hwnd, HWND insertAfter);\nextern HWND getDlgItem(HWND hwnd, int id);\nextern void invalidateRect(HWND hwnd, RECT *r, BOOL erase);\n\n// text.cpp\nextern WCHAR *windowTextAndLen(HWND hwnd, LRESULT *len);\nextern WCHAR *windowText(HWND hwnd);\nextern void setWindowText(HWND hwnd, WCHAR *wtext);\n\n// init.cpp\nextern HINSTANCE hInstance;\nextern int nCmdShow;\nextern HFONT hMessageFont;\nextern HBRUSH hollowBrush;\nextern uiInitOptions options;\n\n// utilwin.cpp\nextern HWND utilWindow;\nextern const char *initUtilWindow(HICON hDefaultIcon, HCURSOR hDefaultCursor);\nextern void uninitUtilWindow(void);\n\n// main.cpp\n// TODO how the hell did MSVC accept this without the second uiprivTimer???????\ntypedef struct uiprivTimer uiprivTimer;\nstruct uiprivTimer {\n\tint (*f)(void *);\n\tvoid *data;\n};\nextern int registerMessageFilter(void);\nextern void unregisterMessageFilter(void);\nextern void uiprivFreeTimer(uiprivTimer *t);\nextern void uiprivUninitTimers(void);\n\n// parent.cpp\nextern void paintContainerBackground(HWND hwnd, HDC dc, RECT *paintRect);\nextern BOOL handleParentMessages(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *lResult);\n\n// d2dscratch.cpp\nextern ATOM registerD2DScratchClass(HICON hDefaultIcon, HCURSOR hDefaultCursor);\nextern void unregisterD2DScratchClass(void);\nextern HWND newD2DScratch(HWND parent, RECT *rect, HMENU controlID, SUBCLASSPROC subclass, DWORD_PTR subclassData);\n\n// area.cpp\n#define areaClass L\"libui_uiAreaClass\"\nextern ATOM registerAreaClass(HICON, HCURSOR);\nextern void unregisterArea(void);\n\n// areaevents.cpp\nextern BOOL areaFilter(MSG *);\n\n// window.cpp\nextern ATOM registerWindowClass(HICON, HCURSOR);\nextern void unregisterWindowClass(void);\nextern void ensureMinimumWindowSize(uiWindow *);\nextern void disableAllWindowsExcept(uiWindow *which);\nextern void enableAllWindowsExcept(uiWindow *which);\n\n// container.cpp\n#define containerClass L\"libui_uiContainerClass\"\nextern ATOM initContainer(HICON, HCURSOR);\nextern void uninitContainer(void);\n\n// tabpage.cpp\nstruct tabPage {\n\tHWND hwnd;\n\tuiControl *child;\n\tBOOL margined;\n};\nextern struct tabPage *newTabPage(uiControl *child);\nextern void tabPageDestroy(struct tabPage *tp);\nextern void tabPageMinimumSize(struct tabPage *tp, int *width, int *height);\n\n// colordialog.cpp\nstruct colorDialogRGBA {\n\tdouble r;\n\tdouble g;\n\tdouble b;\n\tdouble a;\n};\nextern BOOL showColorDialog(HWND parent, struct colorDialogRGBA *c);\n\n// sizing.cpp\nextern void getSizing(HWND hwnd, uiWindowsSizing *sizing, HFONT font);\n\n// TODO move into a dedicated file abibugs.cpp when we rewrite the drawing code\nextern D2D1_SIZE_F realGetSize(ID2D1RenderTarget *rt);\n\n// TODO\n#include \"_uipriv_migrate.hpp\"\n\n// draw.cpp\nextern ID2D1DCRenderTarget *makeHDCRenderTarget(HDC dc, RECT *r);\n\n// image.cpp\nextern IWICImagingFactory *uiprivWICFactory;\nextern HRESULT uiprivInitImage(void);\nextern void uiprivUninitImage(void);\nextern IWICBitmap *uiprivImageAppropriateForDC(uiImage *i, HDC dc);\nextern HRESULT uiprivWICToGDI(IWICBitmap *b, HDC dc, int width, int height, HBITMAP *hb);\n"
  },
  {
    "path": "windows/utf16.cpp",
    "content": "// 21 april 2016\n#include \"uipriv_windows.hpp\"\n\n// see http://stackoverflow.com/a/29556509/3408572\n\nWCHAR *toUTF16(const char *str)\n{\n\tWCHAR *wstr;\n\tWCHAR *wp;\n\tsize_t n;\n\tuint32_t rune;\n\n\tif (*str == '\\0')\t\t\t// empty string\n\t\treturn emptyUTF16();\n\tn = uiprivUTF8UTF16Count(str, 0);\n\twstr = (WCHAR *) uiprivAlloc((n + 1) * sizeof (WCHAR), \"WCHAR[]\");\n\twp = wstr;\n\twhile (*str) {\n\t\tstr = uiprivUTF8DecodeRune(str, 0, &rune);\n\t\tn = uiprivUTF16EncodeRune(rune, wp);\n\t\twp += n;\n\t}\n\treturn wstr;\n}\n\nchar *toUTF8(const WCHAR *wstr)\n{\n\tchar *str;\n\tchar *sp;\n\tsize_t n;\n\tuint32_t rune;\n\n\tif (*wstr == L'\\0')\t\t// empty string\n\t\treturn emptyUTF8();\n\tn = uiprivUTF16UTF8Count(wstr, 0);\n\tstr = (char *) uiprivAlloc((n + 1) * sizeof (char), \"char[]\");\n\tsp = str;\n\twhile (*wstr) {\n\t\twstr = uiprivUTF16DecodeRune(wstr, 0, &rune);\n\t\tn = uiprivUTF8EncodeRune(rune, sp);\n\t\tsp += n;\n\t}\n\treturn str;\n}\n\nWCHAR *utf16dup(const WCHAR *orig)\n{\n\tWCHAR *out;\n\tsize_t len;\n\n\tlen = wcslen(orig);\n\tout = (WCHAR *) uiprivAlloc((len + 1) * sizeof (WCHAR), \"WCHAR[]\");\n\twcscpy_s(out, len + 1, orig);\n\treturn out;\n}\n\nWCHAR *strf(const WCHAR *format, ...)\n{\n\tva_list ap;\n\tWCHAR *str;\n\n\tva_start(ap, format);\n\tstr = vstrf(format, ap);\n\tva_end(ap);\n\treturn str;\n}\n\nWCHAR *vstrf(const WCHAR *format, va_list ap)\n{\n\tva_list ap2;\n\tWCHAR *buf;\n\tsize_t n;\n\n\tif (*format == L'\\0')\n\t\treturn emptyUTF16();\n\n\tva_copy(ap2, ap);\n\tn = _vscwprintf(format, ap2);\n\tva_end(ap2);\n\tn++;\t\t// terminating L'\\0'\n\n\tbuf = (WCHAR *) uiprivAlloc(n * sizeof (WCHAR), \"WCHAR[]\");\n\t// includes terminating L'\\0' according to example in https://msdn.microsoft.com/en-us/library/xa1a1a6z.aspx\n\tvswprintf_s(buf, n, format, ap);\n\n\treturn buf;\n}\n\n// TODO merge the following two with the toUTF*()s?\n\n// Let's shove these utility routines here too.\n// Prerequisite: lfonly is UTF-8.\nchar *LFtoCRLF(const char *lfonly)\n{\n\tchar *crlf;\n\tsize_t i, len;\n\tchar *out;\n\n\tlen = strlen(lfonly);\n\tcrlf = (char *) uiprivAlloc((len * 2 + 1) * sizeof (char), \"char[]\");\n\tout = crlf;\n\tfor (i = 0; i < len; i++) {\n\t\tif (*lfonly == '\\n')\n\t\t\t*crlf++ = '\\r';\n\t\t*crlf++ = *lfonly++;\n\t}\n\t*crlf = '\\0';\n\treturn out;\n}\n\n// Prerequisite: s is UTF-8.\nvoid CRLFtoLF(char *s)\n{\n\tchar *t = s;\n\n\tfor (; *s != '\\0'; s++) {\n\t\t// be sure to preserve \\rs that are genuinely there\n\t\tif (*s == '\\r' && *(s + 1) == '\\n')\n\t\t\tcontinue;\n\t\t*t++ = *s;\n\t}\n\t*t = '\\0';\n\t// pad out the rest of t, just to be safe\n\twhile (t != s)\n\t\t*t++ = '\\0';\n}\n\n// std::to_string() always uses %f; we want %g\n// fortunately std::iostream seems to use %g by default so\nWCHAR *ftoutf16(double d)\n{\n\tstd::wostringstream ss;\n\tstd::wstring s;\n\n\tss << d;\n\ts = ss.str();\t\t// to be safe\n\treturn utf16dup(s.c_str());\n}\n\n// to complement the above\nWCHAR *itoutf16(int i)\n{\n\tstd::wostringstream ss;\n\tstd::wstring s;\n\n\tss << i;\n\ts = ss.str();\t\t// to be safe\n\treturn utf16dup(s.c_str());\n}\n"
  },
  {
    "path": "windows/utilwin.cpp",
    "content": "// 14 may 2015\n#include \"uipriv_windows.hpp\"\n\n// The utility window is a special window that performs certain tasks internal to libui.\n// It is not a message-only window, and it is always hidden and disabled.\n// Its roles:\n// - It is the initial parent of all controls. When a control loses its parent, it also becomes that control's parent.\n// - It handles WM_QUERYENDSESSION requests.\n// - It handles WM_WININICHANGE and forwards the message to any child windows that request it.\n// - It handles executing functions queued to run by uiQueueMain().\n// TODO explain why it isn't message-only\n\n#define utilWindowClass L\"libui_utilWindowClass\"\n\nHWND utilWindow;\n\nstatic LRESULT CALLBACK utilWindowWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)\n{\n\tvoid (*qf)(void *);\n\tLRESULT lResult;\n\tuiprivTimer *timer;\n\n\tif (handleParentMessages(hwnd, uMsg, wParam, lParam, &lResult) != FALSE)\n\t\treturn lResult;\n\tswitch (uMsg) {\n\tcase WM_QUERYENDSESSION:\n\t\t// TODO block handler (TODO figure out if this meant the Vista-style block handler or not)\n\t\tif (uiprivShouldQuit()) {\n\t\t\tuiQuit();\n\t\t\treturn TRUE;\n\t\t}\n\t\treturn FALSE;\n\tcase WM_WININICHANGE:\n\t\tissueWM_WININICHANGE(wParam, lParam);\n\t\treturn 0;\n\tcase msgQueued:\n\t\tqf = (void (*)(void *)) wParam;\n\t\t(*qf)((void *) lParam);\n\t\treturn 0;\n\tcase WM_TIMER:\n\t\ttimer = (uiprivTimer *) wParam;\n\t\tif (!(*(timer->f))(timer->data)) {\n\t\t\tif (KillTimer(utilWindow, (UINT_PTR) timer) == 0)\n\t\t\t\tlogLastError(L\"error calling KillTimer() to end uiTimer() procedure\");\n\t\t\tuiprivFreeTimer(timer);\n\t\t}\n\t\treturn 0;\n\t}\n\treturn DefWindowProcW(hwnd, uMsg, wParam, lParam);\n}\n\nconst char *initUtilWindow(HICON hDefaultIcon, HCURSOR hDefaultCursor)\n{\n\tWNDCLASSW wc;\n\n\tZeroMemory(&wc, sizeof (WNDCLASSW));\n\twc.lpszClassName = utilWindowClass;\n\twc.lpfnWndProc = utilWindowWndProc;\n\twc.hInstance = hInstance;\n\twc.hIcon = hDefaultIcon;\n\twc.hCursor = hDefaultCursor;\n\twc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);\n\tif (RegisterClass(&wc) == 0)\n\t\t// see init.cpp for an explanation of the =s\n\t\treturn \"=registering utility window class\";\n\n\tutilWindow = CreateWindowExW(0,\n\t\tutilWindowClass, L\"libui utility window\",\n\t\tWS_OVERLAPPEDWINDOW,\n\t\t0, 0, 100, 100,\n\t\tNULL, NULL, hInstance, NULL);\n\tif (utilWindow == NULL)\n\t\treturn \"=creating utility window\";\n\t// and just to be safe\n\tEnableWindow(utilWindow, FALSE);\n\n\treturn NULL;\n}\n\nvoid uninitUtilWindow(void)\n{\n\tif (DestroyWindow(utilWindow) == 0)\n\t\tlogLastError(L\"error destroying utility window\");\n\tif (UnregisterClass(utilWindowClass, hInstance) == 0)\n\t\tlogLastError(L\"error unregistering utility window class\");\n}\n"
  },
  {
    "path": "windows/winapi.hpp",
    "content": "// 31 may 2015\n#define UNICODE\n#define _UNICODE\n#define STRICT\n#define STRICT_TYPED_ITEMIDS\n\n// see https://github.com/golang/go/issues/9916#issuecomment-74812211\n// TODO get rid of this\n#define INITGUID\n\n// for the manifest\n#ifndef _UI_STATIC\n#define ISOLATION_AWARE_ENABLED 1\n#endif\n\n// get Windows version right; right now Windows Vista\n// unless otherwise stated, all values from Microsoft's sdkddkver.h\n// TODO is all of this necessary? how is NTDDI_VERSION used?\n// TODO platform update sp2\n#define WINVER\t\t\t0x0600\t/* from Microsoft's winnls.h */\n#define _WIN32_WINNT\t\t0x0600\n#define _WIN32_WINDOWS\t0x0600\t/* from Microsoft's pdh.h */\n#define _WIN32_IE\t\t\t0x0700\n#define NTDDI_VERSION\t\t0x06000000\n\n// The MinGW-w64 header has an unverified IDWriteTypography definition.\n// 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.\n// TODO change the check from _MSC_VER to a MinGW-w64-specific check\n// TODO keep track of what else is guarded by this\n#ifndef _MSC_VER\n#define __MINGW_USE_BROKEN_INTERFACE\n#endif\n\n#include <windows.h>\n\n// Microsoft's resource compiler will segfault if we feed it headers it was not designed to handle\n#ifndef RC_INVOKED\n#include <commctrl.h>\n#include <uxtheme.h>\n#include <vsstyle.h>\n#include <vssym32.h>\n#include <windowsx.h>\n#include <shobjidl.h>\n#include <d2d1.h>\n#include <d2d1helper.h>\n#include <dwrite.h>\n#include <wincodec.h>\n\n#include <stdint.h>\n#include <string.h>\n#include <wchar.h>\n#include <stdarg.h>\n#include <stdio.h>\n#include <math.h>\n#include <float.h>\n#include <inttypes.h>\n\n#include <vector>\n#include <map>\n#include <unordered_map>\n#include <sstream>\n#include <functional>\n#include <utility>\n#endif\n"
  },
  {
    "path": "windows/window.cpp",
    "content": "// 27 april 2015\n#include \"uipriv_windows.hpp\"\n\n#define windowClass L\"libui_uiWindowClass\"\n\nstruct uiWindow {\n\tuiWindowsControl c;\n\tHWND hwnd;\n\tHMENU menubar;\n\tuiControl *child;\n\tBOOL shownOnce;\n\tint visible;\n\tint (*onClosing)(uiWindow *, void *);\n\tvoid *onClosingData;\n\tint margined;\n\tBOOL hasMenubar;\n\tvoid (*onContentSizeChanged)(uiWindow *, void *);\n\tvoid *onContentSizeChangedData;\n\tBOOL changingSize;\n\tint fullscreen;\n\tWINDOWPLACEMENT fsPrevPlacement;\n\tint borderless;\n};\n\n// from https://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing\n#define windowMargin 7\n\nstatic void windowMargins(uiWindow *w, int *mx, int *my)\n{\n\tuiWindowsSizing sizing;\n\n\t*mx = 0;\n\t*my = 0;\n\tif (!w->margined)\n\t\treturn;\n\tuiWindowsGetSizing(w->hwnd, &sizing);\n\t*mx = windowMargin;\n\t*my = windowMargin;\n\tuiWindowsSizingDlgUnitsToPixels(&sizing, mx, my);\n}\n\nstatic void windowRelayout(uiWindow *w)\n{\n\tint x, y, width, height;\n\tRECT r;\n\tint mx, my;\n\tHWND child;\n\n\tif (w->child == NULL)\n\t\treturn;\n\tx = 0;\n\ty = 0;\n\tuiWindowsEnsureGetClientRect(w->hwnd, &r);\n\twidth = r.right - r.left;\n\theight = r.bottom - r.top;\n\twindowMargins(w, &mx, &my);\n\tx += mx;\n\ty += my;\n\twidth -= 2 * mx;\n\theight -= 2 * my;\n\tchild = (HWND) uiControlHandle(w->child);\n\tuiWindowsEnsureMoveWindowDuringResize(child, x, y, width, height);\n}\n\nstatic LRESULT CALLBACK windowWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)\n{\n\tLONG_PTR ww;\n\tuiWindow *w;\n\tCREATESTRUCTW *cs = (CREATESTRUCTW *) lParam;\n\tWINDOWPOS *wp = (WINDOWPOS *) lParam;\n\tMINMAXINFO *mmi = (MINMAXINFO *) lParam;\n\tint width, height;\n\tLRESULT lResult;\n\n\tww = GetWindowLongPtrW(hwnd, GWLP_USERDATA);\n\tif (ww == 0) {\n\t\tif (uMsg == WM_CREATE)\n\t\t\tSetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR) (cs->lpCreateParams));\n\t\t// fall through to DefWindowProc() anyway\n\t\treturn DefWindowProcW(hwnd, uMsg, wParam, lParam);\n\t}\n\tw = uiWindow((void *) ww);\n\tif (handleParentMessages(hwnd, uMsg, wParam, lParam, &lResult) != FALSE)\n\t\treturn lResult;\n\tswitch (uMsg) {\n\tcase WM_COMMAND:\n\t\t// not a menu\n\t\tif (lParam != 0)\n\t\t\tbreak;\n\t\t// IsDialogMessage() will also generate IDOK and IDCANCEL when pressing Enter and Escape (respectively) on some controls, like EDIT controls\n\t\t// swallow those too; they'll cause runMenuEvent() to panic\n\t\t// TODO fix the root cause somehow\n\t\tif (HIWORD(wParam) != 0 || LOWORD(wParam) <= IDCANCEL)\n\t\t\tbreak;\n\t\trunMenuEvent(LOWORD(wParam), uiWindow(w));\n\t\treturn 0;\n\tcase WM_WINDOWPOSCHANGED:\n\t\tif ((wp->flags & SWP_NOSIZE) != 0)\n\t\t\tbreak;\n\t\tif (w->onContentSizeChanged != NULL)\t\t// TODO figure out why this is happening too early\n\t\t\tif (!w->changingSize)\n\t\t\t\t(*(w->onContentSizeChanged))(w, w->onContentSizeChangedData);\n\t\twindowRelayout(w);\n\t\treturn 0;\n\tcase WM_GETMINMAXINFO:\n\t\t// ensure the user cannot resize the window smaller than its minimum size\n\t\tlResult = DefWindowProcW(hwnd, uMsg, wParam, lParam);\n\t\tuiWindowsControlMinimumSize(uiWindowsControl(w), &width, &height);\n\t\t// width and height are in client coordinates; ptMinTrackSize is in window coordinates\n\t\tclientSizeToWindowSize(w->hwnd, &width, &height, w->hasMenubar);\n\t\tmmi->ptMinTrackSize.x = width;\n\t\tmmi->ptMinTrackSize.y = height;\n\t\treturn lResult;\n\tcase WM_PRINTCLIENT:\n\t\t// we do no special painting; just erase the background\n\t\t// don't worry about the return value; we let DefWindowProcW() handle this message\n\t\tSendMessageW(hwnd, WM_ERASEBKGND, wParam, lParam);\n\t\treturn 0;\n\tcase WM_CLOSE:\n\t\tif ((*(w->onClosing))(w, w->onClosingData))\n\t\t\tuiControlDestroy(uiControl(w));\n\t\treturn 0;\t\t// we destroyed it already\n\t}\n\treturn DefWindowProcW(hwnd, uMsg, wParam, lParam);\n}\n\nATOM registerWindowClass(HICON hDefaultIcon, HCURSOR hDefaultCursor)\n{\n\tWNDCLASSW wc;\n\n\tZeroMemory(&wc, sizeof (WNDCLASSW));\n\twc.lpszClassName = windowClass;\n\twc.lpfnWndProc = windowWndProc;\n\twc.hInstance = hInstance;\n\twc.hIcon = hDefaultIcon;\n\twc.hCursor = hDefaultCursor;\n\twc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);\n\treturn RegisterClassW(&wc);\n}\n\nvoid unregisterWindowClass(void)\n{\n\tif (UnregisterClassW(windowClass, hInstance) == 0)\n\t\tlogLastError(L\"error unregistering uiWindow window class\");\n}\n\nstatic int defaultOnClosing(uiWindow *w, void *data)\n{\n\treturn 0;\n}\n\nstatic void defaultOnPositionContentSizeChanged(uiWindow *w, void *data)\n{\n\t// do nothing\n}\n\nstatic std::map<uiWindow *, bool> windows;\n\nstatic void uiWindowDestroy(uiControl *c)\n{\n\tuiWindow *w = uiWindow(c);\n\n\t// first hide ourselves\n\tShowWindow(w->hwnd, SW_HIDE);\n\t// now destroy the child\n\tif (w->child != NULL) {\n\t\tuiControlSetParent(w->child, NULL);\n\t\tuiControlDestroy(w->child);\n\t}\n\t// now free the menubar, if any\n\tif (w->menubar != NULL)\n\t\tfreeMenubar(w->menubar);\n\t// and finally free ourselves\n\twindows.erase(w);\n\tuiWindowsEnsureDestroyWindow(w->hwnd);\n\tuiFreeControl(uiControl(w));\n}\n\nuiWindowsControlDefaultHandle(uiWindow)\n\nuiControl *uiWindowParent(uiControl *c)\n{\n\treturn NULL;\n}\n\nvoid uiWindowSetParent(uiControl *c, uiControl *parent)\n{\n\tuiUserBugCannotSetParentOnToplevel(\"uiWindow\");\n}\n\nstatic int uiWindowToplevel(uiControl *c)\n{\n\treturn 1;\n}\n\n// TODO initial state of windows is hidden; ensure this here and make it so on other platforms\nstatic int uiWindowVisible(uiControl *c)\n{\n\tuiWindow *w = uiWindow(c);\n\n\treturn w->visible;\n}\n\nstatic void uiWindowShow(uiControl *c)\n{\n\tuiWindow *w = uiWindow(c);\n\n\tw->visible = 1;\n\t// just in case the window's minimum size wasn't recalculated already\n\tensureMinimumWindowSize(w);\n\tif (w->shownOnce) {\n\t\tShowWindow(w->hwnd, SW_SHOW);\n\t\treturn;\n\t}\n\tw->shownOnce = TRUE;\n\t// make sure the child is the correct size\n\tuiWindowsControlMinimumSizeChanged(uiWindowsControl(w));\n\tShowWindow(w->hwnd, nCmdShow);\n\tif (UpdateWindow(w->hwnd) == 0)\n\t\tlogLastError(L\"error calling UpdateWindow() after showing uiWindow for the first time\");\n}\n\nstatic void uiWindowHide(uiControl *c)\n{\n\tuiWindow *w = uiWindow(c);\n\n\tw->visible = 0;\n\tShowWindow(w->hwnd, SW_HIDE);\n}\n\n// TODO we don't want the window to be disabled completely; that would prevent it from being moved! ...would it?\nuiWindowsControlDefaultEnabled(uiWindow)\nuiWindowsControlDefaultEnable(uiWindow)\nuiWindowsControlDefaultDisable(uiWindow)\n// TODO we need to do something about undocumented fields in the OS control types\nuiWindowsControlDefaultSyncEnableState(uiWindow)\n// TODO\nuiWindowsControlDefaultSetParentHWND(uiWindow)\n\nstatic void uiWindowMinimumSize(uiWindowsControl *c, int *width, int *height)\n{\n\tuiWindow *w = uiWindow(c);\n\tint mx, my;\n\n\t*width = 0;\n\t*height = 0;\n\tif (w->child != NULL)\n\t\tuiWindowsControlMinimumSize(uiWindowsControl(w->child), width, height);\n\twindowMargins(w, &mx, &my);\n\t*width += 2 * mx;\n\t*height += 2 * my;\n}\n\nstatic void uiWindowMinimumSizeChanged(uiWindowsControl *c)\n{\n\tuiWindow *w = uiWindow(c);\n\n\tif (uiWindowsControlTooSmall(uiWindowsControl(w))) {\n\t\t// TODO figure out what to do with this function\n\t\t// maybe split it into two so WM_GETMINMAXINFO can use it?\n\t\tensureMinimumWindowSize(w);\n\t\treturn;\n\t}\n\t// otherwise we only need to re-layout everything\n\twindowRelayout(w);\n}\n\nstatic void uiWindowLayoutRect(uiWindowsControl *c, RECT *r)\n{\n\tuiWindow *w = uiWindow(c);\n\n\t// the layout rect is the client rect in this case\n\tuiWindowsEnsureGetClientRect(w->hwnd, r);\n}\n\nuiWindowsControlDefaultAssignControlIDZOrder(uiWindow)\n\nstatic void uiWindowChildVisibilityChanged(uiWindowsControl *c)\n{\n\t// TODO eliminate the redundancy\n\tuiWindowsControlMinimumSizeChanged(c);\n}\n\nchar *uiWindowTitle(uiWindow *w)\n{\n\treturn uiWindowsWindowText(w->hwnd);\n}\n\nvoid uiWindowSetTitle(uiWindow *w, const char *title)\n{\n\tuiWindowsSetWindowText(w->hwnd, title);\n\t// 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)\n}\n\n// this is used for both fullscreening and centering\n// see also https://blogs.msdn.microsoft.com/oldnewthing/20100412-00/?p=14353 and https://blogs.msdn.microsoft.com/oldnewthing/20050505-04/?p=35703\nstatic void windowMonitorRect(HWND hwnd, RECT *r)\n{\n\tHMONITOR monitor;\n\tMONITORINFO mi;\n\n\tmonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY);\n\tZeroMemory(&mi, sizeof (MONITORINFO));\n\tmi.cbSize = sizeof (MONITORINFO);\n\tif (GetMonitorInfoW(monitor, &mi) == 0) {\n\t\tlogLastError(L\"error getting window monitor rect\");\n\t\t// default to SM_CXSCREEN x SM_CYSCREEN to be safe\n\t\tr->left = 0;\n\t\tr->top = 0;\n\t\tr->right = GetSystemMetrics(SM_CXSCREEN);\n\t\tr->bottom = GetSystemMetrics(SM_CYSCREEN);\n\t\treturn;\n\t}\n\t*r = mi.rcMonitor;\n}\n\nvoid uiWindowContentSize(uiWindow *w, int *width, int *height)\n{\n\tRECT r;\n\n\tuiWindowsEnsureGetClientRect(w->hwnd, &r);\n\t*width = r.right - r.left;\n\t*height = r.bottom - r.top;\n}\n\n// TODO should this disallow too small?\nvoid uiWindowSetContentSize(uiWindow *w, int width, int height)\n{\n\tw->changingSize = TRUE;\n\tclientSizeToWindowSize(w->hwnd, &width, &height, w->hasMenubar);\n\tif (SetWindowPos(w->hwnd, NULL, 0, 0, width, height, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER) == 0)\n\t\tlogLastError(L\"error resizing window\");\n\tw->changingSize = FALSE;\n}\n\nint uiWindowFullscreen(uiWindow *w)\n{\n\treturn w->fullscreen;\n}\n\nvoid uiWindowSetFullscreen(uiWindow *w, int fullscreen)\n{\n\tRECT r;\n\n\tif (w->fullscreen && fullscreen)\n\t\treturn;\n\tif (!w->fullscreen && !fullscreen)\n\t\treturn;\n\tw->fullscreen = fullscreen;\n\tw->changingSize = TRUE;\n\tif (w->fullscreen) {\n\t\tZeroMemory(&(w->fsPrevPlacement), sizeof (WINDOWPLACEMENT));\n\t\tw->fsPrevPlacement.length = sizeof (WINDOWPLACEMENT);\n\t\tif (GetWindowPlacement(w->hwnd, &(w->fsPrevPlacement)) == 0)\n\t\t\tlogLastError(L\"error getting old window placement\");\n\t\twindowMonitorRect(w->hwnd, &r);\n\t\tsetStyle(w->hwnd, getStyle(w->hwnd) & ~WS_OVERLAPPEDWINDOW);\n\t\tif (SetWindowPos(w->hwnd, HWND_TOP,\n\t\t\tr.left, r.top,\n\t\t\tr.right - r.left, r.bottom - r.top,\n\t\t\tSWP_FRAMECHANGED | SWP_NOOWNERZORDER) == 0)\n\t\t\tlogLastError(L\"error making window fullscreen\");\n\t} else {\n\t\tif (!w->borderless)\t\t// keep borderless until that is turned off\n\t\t\tsetStyle(w->hwnd, getStyle(w->hwnd) | WS_OVERLAPPEDWINDOW);\n\t\tif (SetWindowPlacement(w->hwnd, &(w->fsPrevPlacement)) == 0)\n\t\t\tlogLastError(L\"error leaving fullscreen\");\n\t\tif (SetWindowPos(w->hwnd, NULL,\n\t\t\t0, 0, 0, 0,\n\t\t\tSWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER) == 0)\n\t\t\tlogLastError(L\"error restoring window border after fullscreen\");\n\t}\n\tw->changingSize = FALSE;\n}\n\nvoid uiWindowOnContentSizeChanged(uiWindow *w, void (*f)(uiWindow *, void *), void *data)\n{\n\tw->onContentSizeChanged = f;\n\tw->onContentSizeChangedData = data;\n}\n\nvoid uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *, void *), void *data)\n{\n\tw->onClosing = f;\n\tw->onClosingData = data;\n}\n\nint uiWindowBorderless(uiWindow *w)\n{\n\treturn w->borderless;\n}\n\n// TODO window should move to the old client position and should not have the extra space the borders left behind\n// TODO extract the relevant styles from WS_OVERLAPPEDWINDOW?\nvoid uiWindowSetBorderless(uiWindow *w, int borderless)\n{\n\tw->borderless = borderless;\n\tif (w->borderless)\n\t\tsetStyle(w->hwnd, getStyle(w->hwnd) & ~WS_OVERLAPPEDWINDOW);\n\telse\n\t\tif (!w->fullscreen)\t\t// keep borderless until leaving fullscreen\n\t\t\tsetStyle(w->hwnd, getStyle(w->hwnd) | WS_OVERLAPPEDWINDOW);\n}\n\nvoid uiWindowSetChild(uiWindow *w, uiControl *child)\n{\n\tif (w->child != NULL) {\n\t\tuiControlSetParent(w->child, NULL);\n\t\tuiWindowsControlSetParentHWND(uiWindowsControl(w->child), NULL);\n\t}\n\tw->child = child;\n\tif (w->child != NULL) {\n\t\tuiControlSetParent(w->child, uiControl(w));\n\t\tuiWindowsControlSetParentHWND(uiWindowsControl(w->child), w->hwnd);\n\t\tuiWindowsControlAssignSoleControlIDZOrder(uiWindowsControl(w->child));\n\t\twindowRelayout(w);\n\t}\n}\n\nint uiWindowMargined(uiWindow *w)\n{\n\treturn w->margined;\n}\n\nvoid uiWindowSetMargined(uiWindow *w, int margined)\n{\n\tw->margined = margined;\n\twindowRelayout(w);\n}\n\n// 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\n// TODO use clientSizeToWindowSize()\nstatic void setClientSize(uiWindow *w, int width, int height, BOOL hasMenubar, DWORD style, DWORD exstyle)\n{\n\tRECT window;\n\n\twindow.left = 0;\n\twindow.top = 0;\n\twindow.right = width;\n\twindow.bottom = height;\n\tif (AdjustWindowRectEx(&window, style, hasMenubar, exstyle) == 0)\n\t\tlogLastError(L\"error getting real window coordinates\");\n\tif (hasMenubar) {\n\t\tRECT temp;\n\n\t\ttemp = window;\n\t\ttemp.bottom = 0x7FFF;\t\t// infinite height\n\t\tSendMessageW(w->hwnd, WM_NCCALCSIZE, (WPARAM) FALSE, (LPARAM) (&temp));\n\t\twindow.bottom += temp.top;\n\t}\n\tif (SetWindowPos(w->hwnd, NULL, 0, 0, window.right - window.left, window.bottom - window.top, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER) == 0)\n\t\tlogLastError(L\"error resizing window\");\n}\n\nuiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar)\n{\n\tuiWindow *w;\n\tWCHAR *wtitle;\n\tBOOL hasMenubarBOOL;\n\n\tuiWindowsNewControl(uiWindow, w);\n\n\thasMenubarBOOL = FALSE;\n\tif (hasMenubar)\n\t\thasMenubarBOOL = TRUE;\n\tw->hasMenubar = hasMenubarBOOL;\n\n#define style WS_OVERLAPPEDWINDOW\n#define exstyle 0\n\n\twtitle = toUTF16(title);\n\tw->hwnd = CreateWindowExW(exstyle,\n\t\twindowClass, wtitle,\n\t\tstyle,\n\t\tCW_USEDEFAULT, CW_USEDEFAULT,\n\t\t// use the raw width and height for now\n\t\t// this will get CW_USEDEFAULT (hopefully) predicting well\n\t\t// even if it doesn't, we're adjusting it later\n\t\twidth, height,\n\t\tNULL, NULL, hInstance, w);\n\tif (w->hwnd == NULL)\n\t\tlogLastError(L\"error creating window\");\n\tuiprivFree(wtitle);\n\n\tif (hasMenubar) {\n\t\tw->menubar = makeMenubar();\n\t\tif (SetMenu(w->hwnd, w->menubar) == 0)\n\t\t\tlogLastError(L\"error giving menu to window\");\n\t}\n\n\t// and use the proper size\n\tsetClientSize(w, width, height, hasMenubarBOOL, style, exstyle);\n\n\tuiWindowOnClosing(w, defaultOnClosing, NULL);\n\tuiWindowOnContentSizeChanged(w, defaultOnPositionContentSizeChanged, NULL);\n\n\twindows[w] = true;\n\treturn w;\n}\n\n// this cannot queue a resize because it's called by the resize handler\nvoid ensureMinimumWindowSize(uiWindow *w)\n{\n\tint width, height;\n\tRECT r;\n\n\tuiWindowsControlMinimumSize(uiWindowsControl(w), &width, &height);\n\tuiWindowsEnsureGetClientRect(w->hwnd, &r);\n\tif (width < (r.right - r.left))\t\t// preserve width if larger\n\t\twidth = r.right - r.left;\n\tif (height < (r.bottom - r.top))\t\t// preserve height if larger\n\t\theight = r.bottom - r.top;\n\tclientSizeToWindowSize(w->hwnd, &width, &height, w->hasMenubar);\n\tif (SetWindowPos(w->hwnd, NULL, 0, 0, width, height, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER) == 0)\n\t\tlogLastError(L\"error resizing window\");\n}\n\nvoid disableAllWindowsExcept(uiWindow *which)\n{\n\tfor (auto &w : windows) {\n\t\tif (w.first == which)\n\t\t\tcontinue;\n\t\tEnableWindow(w.first->hwnd, FALSE);\n\t}\n}\n\nvoid enableAllWindowsExcept(uiWindow *which)\n{\n\tfor (auto &w : windows) {\n\t\tif (w.first == which)\n\t\t\tcontinue;\n\t\tif (!uiControlEnabled(uiControl(w.first)))\n\t\t\tcontinue;\n\t\tEnableWindow(w.first->hwnd, TRUE);\n\t}\n}\n"
  },
  {
    "path": "windows/winpublic.cpp",
    "content": "// 6 april 2015\n#include \"uipriv_windows.hpp\"\n\nvoid uiWindowsEnsureDestroyWindow(HWND hwnd)\n{\n\tif (DestroyWindow(hwnd) == 0)\n\t\tlogLastError(L\"error destroying window\");\n}\n\nvoid uiWindowsEnsureSetParentHWND(HWND hwnd, HWND parent)\n{\n\tif (parent == NULL)\n\t\tparent = utilWindow;\n\tif (SetParent(hwnd, parent) == 0)\n\t\tlogLastError(L\"error setting window parent\");\n}\n\nvoid uiWindowsEnsureAssignControlIDZOrder(HWND hwnd, LONG_PTR *controlID, HWND *insertAfter)\n{\n\tSetWindowLongPtrW(hwnd, GWLP_ID, *controlID);\n\t(*controlID)++;\n\tsetWindowInsertAfter(hwnd, *insertAfter);\n\t*insertAfter = hwnd;\n}\n\nvoid uiWindowsEnsureMoveWindowDuringResize(HWND hwnd, int x, int y, int width, int height)\n{\n\tRECT r;\n\n\tr.left = x;\n\tr.top = y;\n\tr.right = x + width;\n\tr.bottom = y + height;\n\tif (SetWindowPos(hwnd, NULL, r.left, r.top, r.right - r.left, r.bottom - r.top, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER) == 0)\n\t\tlogLastError(L\"error moving window\");\n}\n\n// do these function even error out in any case other than invalid parameters?! I thought all windows had rects\nvoid uiWindowsEnsureGetClientRect(HWND hwnd, RECT *r)\n{\n\tif (GetClientRect(hwnd, r) == 0) {\n\t\tlogLastError(L\"error getting window client rect\");\n\t\t// zero out the rect on error just to be safe\n\t\tr->left = 0;\n\t\tr->top = 0;\n\t\tr->right = 0;\n\t\tr->bottom = 0;\n\t}\n}\n\nvoid uiWindowsEnsureGetWindowRect(HWND hwnd, RECT *r)\n{\n\tif (GetWindowRect(hwnd, r) == 0) {\n\t\tlogLastError(L\"error getting window rect\");\n\t\t// zero out the rect on error just to be safe\n\t\tr->left = 0;\n\t\tr->top = 0;\n\t\tr->right = 0;\n\t\tr->bottom = 0;\n\t}\n}\n"
  },
  {
    "path": "windows/winutil.cpp",
    "content": "// 6 april 2015\n#include \"uipriv_windows.hpp\"\n\n// this is a helper function that takes the logic of determining window classes and puts it all in one place\n// there are a number of places where we need to know what window class an arbitrary handle has\n// theoretically we could use the class atom to avoid a _wcsicmp()\n// however, raymond chen advises against this - http://blogs.msdn.com/b/oldnewthing/archive/2004/10/11/240744.aspx (and we're not in control of the Tab class, before you say anything)\n// usage: windowClassOf(hwnd, L\"class 1\", L\"class 2\", ..., NULL)\nint windowClassOf(HWND hwnd, ...)\n{\n// MSDN says 256 is the maximum length of a class name; add a few characters just to be safe (because it doesn't say whether this includes the terminating null character)\n#define maxClassName 260\n\tWCHAR classname[maxClassName + 1];\n\tva_list ap;\n\tWCHAR *curname;\n\tint i;\n\n\tif (GetClassNameW(hwnd, classname, maxClassName) == 0) {\n\t\tlogLastError(L\"error getting name of window class\");\n\t\t// assume no match on error, just to be safe\n\t\treturn -1;\n\t}\n\tva_start(ap, hwnd);\n\ti = 0;\n\tfor (;;) {\n\t\tcurname = va_arg(ap, WCHAR *);\n\t\tif (curname == NULL)\n\t\t\tbreak;\n\t\tif (_wcsicmp(classname, curname) == 0) {\n\t\t\tva_end(ap);\n\t\t\treturn i;\n\t\t}\n\t\ti++;\n\t}\n\t// no match\n\tva_end(ap);\n\treturn -1;\n}\n\n// wrapper around MapWindowRect() that handles the complex error handling\nvoid mapWindowRect(HWND from, HWND to, RECT *r)\n{\n\tRECT prevr;\n\tDWORD le;\n\n\tprevr = *r;\n\tSetLastError(0);\n\tif (MapWindowRect(from, to, r) == 0) {\n\t\tle = GetLastError();\n\t\tSetLastError(le);\t\t// just to be safe\n\t\tif (le != 0) {\n\t\t\tlogLastError(L\"error calling MapWindowRect()\");\n\t\t\t// restore original rect on error, just in case\n\t\t\t*r = prevr;\n\t\t}\n\t}\n}\n\nDWORD getStyle(HWND hwnd)\n{\n\treturn (DWORD) GetWindowLongPtrW(hwnd, GWL_STYLE);\n}\n\nvoid setStyle(HWND hwnd, DWORD style)\n{\n\tSetWindowLongPtrW(hwnd, GWL_STYLE, (LONG_PTR) style);\n}\n\nDWORD getExStyle(HWND hwnd)\n{\n\treturn (DWORD) GetWindowLongPtrW(hwnd, GWL_EXSTYLE);\n}\n\nvoid setExStyle(HWND hwnd, DWORD exstyle)\n{\n\tSetWindowLongPtrW(hwnd, GWL_EXSTYLE, (LONG_PTR) exstyle);\n}\n\n// 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\nvoid clientSizeToWindowSize(HWND hwnd, int *width, int *height, BOOL hasMenubar)\n{\n\tRECT window;\n\n\twindow.left = 0;\n\twindow.top = 0;\n\twindow.right = *width;\n\twindow.bottom = *height;\n\tif (AdjustWindowRectEx(&window, getStyle(hwnd), hasMenubar, getExStyle(hwnd)) == 0) {\n\t\tlogLastError(L\"error getting adjusted window rect\");\n\t\t// on error, don't give up; the window will be smaller but whatever\n\t\twindow.left = 0;\n\t\twindow.top = 0;\n\t\twindow.right = *width;\n\t\twindow.bottom = *height;\n\t}\n\tif (hasMenubar) {\n\t\tRECT temp;\n\n\t\ttemp = window;\n\t\ttemp.bottom = 0x7FFF;\t\t// infinite height\n\t\tSendMessageW(hwnd, WM_NCCALCSIZE, (WPARAM) FALSE, (LPARAM) (&temp));\n\t\twindow.bottom += temp.top;\n\t}\n\t*width = window.right - window.left;\n\t*height = window.bottom - window.top;\n}\n\nHWND parentOf(HWND child)\n{\n\treturn GetAncestor(child, GA_PARENT);\n}\n\nHWND parentToplevel(HWND child)\n{\n\treturn GetAncestor(child, GA_ROOT);\n}\n\nvoid setWindowInsertAfter(HWND hwnd, HWND insertAfter)\n{\n\tif (SetWindowPos(hwnd, insertAfter, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOSIZE) == 0)\n\t\tlogLastError(L\"error reordering window\");\n}\n\nHWND getDlgItem(HWND hwnd, int id)\n{\n\tHWND out;\n\n\tout = GetDlgItem(hwnd, id);\n\tif (out == NULL)\n\t\tlogLastError(L\"error getting dialog item handle\");\n\treturn out;\n}\n\nvoid invalidateRect(HWND hwnd, RECT *r, BOOL erase)\n{\n\tif (InvalidateRect(hwnd, r, erase) == 0)\n\t\tlogLastError(L\"error invalidating window rect\");\n}\n\n// that damn ABI bug is never going to escape me is it\nD2D1_SIZE_F realGetSize(ID2D1RenderTarget *rt)\n{\n#ifdef _MSC_VER\n\treturn rt->GetSize();\n#else\n\tD2D1_SIZE_F size;\n\ttypedef D2D1_SIZE_F *(__stdcall ID2D1RenderTarget::* GetSizeF)(D2D1_SIZE_F *) const;\n\tGetSizeF gs;\n\n\tgs = (GetSizeF) (&(rt->GetSize));\n\t(rt->*gs)(&size);\n\treturn size;\n#endif\n}\n"
  }
]