summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-08-20 06:36:07 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-08-20 06:36:07 +0300
commit4e62a21cea5781fc91ec5ef22710e0fe19badb3c (patch)
treedc688c51fcec9f990796b4e58f0422dc25935954
parentc4fa62d07dac1c3855dae4ed5108ab0f3eb43ceb (diff)
parentd358811295527f705defbeea31b38fa2ee90a12e (diff)
Merge branch 'dev' into work/v1.7
# Conflicts: # CMakeLists.txt
-rw-r--r--CMakeLists.txt10
m---------lib/the_Foundation0
-rw-r--r--res/about/version.gmi10
-rw-r--r--res/fi.skyjake.Lagrange.appdata.xml32
-rw-r--r--src/app.c2
-rw-r--r--src/gmutil.c41
-rw-r--r--src/ui/documentwidget.c11
-rw-r--r--src/ui/inputwidget.c87
-rw-r--r--src/ui/text.c7
-rw-r--r--src/ui/uploadwidget.c2
-rw-r--r--src/ui/window.c34
11 files changed, 162 insertions, 74 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index fc0835f1..73e2b8d4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -28,6 +28,12 @@ if (IOS)
28 set (IOS_BUNDLE_VERSION 21.6.15) 28 set (IOS_BUNDLE_VERSION 21.6.15)
29endif () 29endif ()
30 30
31# Default that depend on environment.
32set (DEFAULT_RESIZE_DRAW ON)
33if (HAIKU)
34 set (DEFAULT_RESIZE_DRAW OFF)
35endif ()
36
31# Build configuration. 37# Build configuration.
32option (ENABLE_CUSTOM_FRAME "Draw a custom window frame (Windows)" OFF) 38option (ENABLE_CUSTOM_FRAME "Draw a custom window frame (Windows)" OFF)
33option (ENABLE_DOWNLOAD_EDIT "Allow changing the Downloads directory" ON) 39option (ENABLE_DOWNLOAD_EDIT "Allow changing the Downloads directory" ON)
@@ -40,6 +46,7 @@ option (ENABLE_IPC "Use IPC to communicate between running instance
40option (ENABLE_KERNING "Enable kerning in font renderer (slower)" ON) 46option (ENABLE_KERNING "Enable kerning in font renderer (slower)" ON)
41option (ENABLE_MPG123 "Use mpg123 for decoding MPEG audio" ON) 47option (ENABLE_MPG123 "Use mpg123 for decoding MPEG audio" ON)
42option (ENABLE_RELATIVE_EMBED "Resources should always be found via relative path" OFF) 48option (ENABLE_RELATIVE_EMBED "Resources should always be found via relative path" OFF)
49option (ENABLE_RESIZE_DRAW "Force window to redraw during resizing" ${DEFAULT_RESIZE_DRAW})
43option (ENABLE_RESOURCE_EMBED "Embed resources inside the executable" OFF) 50option (ENABLE_RESOURCE_EMBED "Embed resources inside the executable" OFF)
44option (ENABLE_WINDOWPOS_FIX "Set position after showing window (workaround for SDL bug)" OFF) 51option (ENABLE_WINDOWPOS_FIX "Set position after showing window (workaround for SDL bug)" OFF)
45option (ENABLE_X11_SWRENDER "Use software rendering under X11" OFF) 52option (ENABLE_X11_SWRENDER "Use software rendering under X11" OFF)
@@ -317,6 +324,9 @@ endif ()
317if (ENABLE_CUSTOM_FRAME AND MSYS) 324if (ENABLE_CUSTOM_FRAME AND MSYS)
318 target_compile_definitions (app PUBLIC LAGRANGE_ENABLE_CUSTOM_FRAME=1) 325 target_compile_definitions (app PUBLIC LAGRANGE_ENABLE_CUSTOM_FRAME=1)
319endif () 326endif ()
327if (ENABLE_RESIZE_DRAW)
328 target_compile_definitions (app PUBLIC LAGRANGE_ENABLE_RESIZE_DRAW=1)
329endif ()
320target_link_libraries (app PUBLIC the_Foundation::the_Foundation) 330target_link_libraries (app PUBLIC the_Foundation::the_Foundation)
321target_link_libraries (app PUBLIC ${SDL2_LDFLAGS}) 331target_link_libraries (app PUBLIC ${SDL2_LDFLAGS})
322if (ENABLE_HARFBUZZ AND HARFBUZZ_FOUND) 332if (ENABLE_HARFBUZZ AND HARFBUZZ_FOUND)
diff --git a/lib/the_Foundation b/lib/the_Foundation
Subproject 4a2cd8896bf7fabbe7345c245a052083c6c1c3e Subproject 7ff1adf809c047a349243e6e142a08d52dd6971
diff --git a/res/about/version.gmi b/res/about/version.gmi
index 1fce4188..89e81047 100644
--- a/res/about/version.gmi
+++ b/res/about/version.gmi
@@ -6,6 +6,16 @@
6``` 6```
7# Release notes 7# Release notes
8 8
9## 1.6.4
10* Local files containing UTF-8 text can be viewed regardless of their file extension.
11* Fixed input field cursor positioning and insertion problems around Emoji variation selectors.
12* Fixed "Unknown Status Code" shown in Page Information for valid status codes.
13* Fixed an issue with network requests that would make it appear the server was not responding, but the request would not time out.
14* Fixed a potential invalid memory access when clicking on sidebar items.
15* OpenBSD: Fixed a freeze after a network request is cancelled.
16* Fixed page contents not reflowing during a window resize.
17* Added build option ENABLE_RESIZE_DRAW. SDL doesn't redraw window contents on all platforms during resizing, so this can be used to force it.
18
9## 1.6.3 19## 1.6.3
10* Select all text in an input field using Shift+Ctrl+A (macOS: ⌘A). 20* Select all text in an input field using Shift+Ctrl+A (macOS: ⌘A).
11* Input fields do not lose focus when the window becomes inactive, making it easier to resume input afterwards. 21* Input fields do not lose focus when the window becomes inactive, making it easier to resume input afterwards.
diff --git a/res/fi.skyjake.Lagrange.appdata.xml b/res/fi.skyjake.Lagrange.appdata.xml
index 0259aa1e..4202a9b6 100644
--- a/res/fi.skyjake.Lagrange.appdata.xml
+++ b/res/fi.skyjake.Lagrange.appdata.xml
@@ -45,6 +45,36 @@
45 <update_contact>jaakko.keranen@iki.fi</update_contact> 45 <update_contact>jaakko.keranen@iki.fi</update_contact>
46 46
47 <releases> 47 <releases>
48 <release version="1.6.4" date="2021-08-22">
49 <description>
50 <p>Version 1.6 adds support for bidirectional text and complex scripts,
51 right-to-left paragraph layout, uploads using the Titan protocol,
52 and has an improved mechanism for tracking trust in server
53 certificates. Page contents can be fully cached in memory for more
54 efficient backward navigation. There are also UI improvements like
55 a reorganized Preferences and a setting for smooth scrolling
56 speed.</p>
57 <p>Changes and fixes in v1.6.4:</p>
58 <ul>
59 <li>Local files containing UTF-8 text can be viewed regardless of
60 their file extension.</li>
61 <li>Fixed input field cursor positioning and insertion problems
62 around Emoji variation selectors.</li>
63 <li>Fixed "Unknown Status Code" shown in Page Information for
64 valid status codes.</li>
65 <li>Fixed an issue with network requests that would make it appear
66 the server was not responding, but the request would not time
67 out.</li>
68 <li>Fixed a potential invalid memory access when clicking on
69 sidebar items.</li>
70 <li>Fixed a potential freeze after a network request is
71 cancelled.</li>
72 </ul>
73 <p>The full release notes can be viewed inside the app by opening
74 the "about:version" page.</p>
75 </description>
76 <url>https://github.com/skyjake/lagrange/releases/tag/v1.6.4</url>
77 </release>
48 <release version="1.6.3" date="2021-08-15"> 78 <release version="1.6.3" date="2021-08-15">
49 <description> 79 <description>
50 <p>Version 1.6 adds support for bidirectional text and complex scripts, 80 <p>Version 1.6 adds support for bidirectional text and complex scripts,
@@ -78,7 +108,7 @@
78 the "about:version" page.</p> 108 the "about:version" page.</p>
79 </description> 109 </description>
80 <url>https://github.com/skyjake/lagrange/releases/tag/v1.6.3</url> 110 <url>https://github.com/skyjake/lagrange/releases/tag/v1.6.3</url>
81 </release> 111 </release>
82 <release version="1.6.2" date="2021-08-03"> 112 <release version="1.6.2" date="2021-08-03">
83 <description> 113 <description>
84 <p>Version 1.6 adds support for bidirectional text and complex scripts, 114 <p>Version 1.6 adds support for bidirectional text and complex scripts,
diff --git a/src/app.c b/src/app.c
index bcd06722..bf781c03 100644
--- a/src/app.c
+++ b/src/app.c
@@ -1351,7 +1351,7 @@ static int run_App_(iApp *d) {
1351 } 1351 }
1352 d->isRunning = iTrue; 1352 d->isRunning = iTrue;
1353 SDL_EventState(SDL_DROPFILE, SDL_ENABLE); /* open files via drag'n'drop */ 1353 SDL_EventState(SDL_DROPFILE, SDL_ENABLE); /* open files via drag'n'drop */
1354#if defined (iPlatformDesktop) 1354#if defined (LAGRANGE_ENABLE_RESIZE_DRAW)
1355 SDL_AddEventWatch(resizeWatcher_, d); /* redraw window during resizing */ 1355 SDL_AddEventWatch(resizeWatcher_, d); /* redraw window during resizing */
1356#endif 1356#endif
1357 while (d->isRunning) { 1357 while (d->isRunning) {
diff --git a/src/gmutil.c b/src/gmutil.c
index fda8489b..9bd74ee0 100644
--- a/src/gmutil.c
+++ b/src/gmutil.c
@@ -22,10 +22,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 22
23#include "gmutil.h" 23#include "gmutil.h"
24 24
25#include <the_Foundation/regexp.h> 25#include <the_Foundation/file.h>
26#include <the_Foundation/fileinfo.h>
26#include <the_Foundation/object.h> 27#include <the_Foundation/object.h>
27#include <the_Foundation/path.h> 28#include <the_Foundation/path.h>
28#include <the_Foundation/regexp.h> 29#include <the_Foundation/regexp.h>
30#include <the_Foundation/regexp.h>
29 31
30iRegExp *newGemtextLink_RegExp(void) { 32iRegExp *newGemtextLink_RegExp(void) {
31 return new_RegExp("=>\\s*([^\\s]+)(\\s.*)?", 0); 33 return new_RegExp("=>\\s*([^\\s]+)(\\s.*)?", 0);
@@ -513,18 +515,6 @@ const char *mediaType_Path(const iString *path) {
513 if (endsWithCase_String(path, ".gmi") || endsWithCase_String(path, ".gemini")) { 515 if (endsWithCase_String(path, ".gmi") || endsWithCase_String(path, ".gemini")) {
514 return "text/gemini; charset=utf-8"; 516 return "text/gemini; charset=utf-8";
515 } 517 }
516 /* TODO: It would be better to default to text/plain, but switch to
517 application/octet-stream if the contents fail to parse as UTF-8. */
518 else if (endsWithCase_String(path, ".txt") ||
519 endsWithCase_String(path, ".md") ||
520 endsWithCase_String(path, ".c") ||
521 endsWithCase_String(path, ".h") ||
522 endsWithCase_String(path, ".cc") ||
523 endsWithCase_String(path, ".hh") ||
524 endsWithCase_String(path, ".cpp") ||
525 endsWithCase_String(path, ".hpp")) {
526 return "text/plain";
527 }
528 else if (endsWithCase_String(path, ".pem")) { 518 else if (endsWithCase_String(path, ".pem")) {
529 return "application/x-pem-file"; 519 return "application/x-pem-file";
530 } 520 }
@@ -558,7 +548,30 @@ const char *mediaType_Path(const iString *path) {
558 else if (endsWithCase_String(path, ".mid")) { 548 else if (endsWithCase_String(path, ".mid")) {
559 return "audio/midi"; 549 return "audio/midi";
560 } 550 }
561 return "application/octet-stream"; 551 else if (endsWithCase_String(path, ".txt") ||
552 endsWithCase_String(path, ".md") ||
553 endsWithCase_String(path, ".c") ||
554 endsWithCase_String(path, ".h") ||
555 endsWithCase_String(path, ".cc") ||
556 endsWithCase_String(path, ".hh") ||
557 endsWithCase_String(path, ".cpp") ||
558 endsWithCase_String(path, ".hpp")) {
559 return "text/plain";
560 }
561 const char *mtype = "application/octet-stream";
562 /* If the file is reasonably small and looks like UTF-8, we'll display it as text/plain. */
563 if (fileExists_FileInfo(path) && fileSize_FileInfo(path) <= 5000000) {
564 iFile *f = new_File(path);
565 if (open_File(f, readOnly_FileMode)) {
566 iBlock *content = readAll_File(f);
567 if (isUtf8_Rangecc(range_Block(content))) {
568 mtype = "text/plain; charset=utf-8";
569 }
570 delete_Block(content);
571 }
572 iRelease(f);
573 }
574 return mtype;
562} 575}
563 576
564static void replaceAllChars_String_(iString *d, char c, const char *replacement) { 577static void replaceAllChars_String_(iString *d, char c, const char *replacement) {
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index a89eb0eb..3f655db5 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -1863,7 +1863,12 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
1863 updateTrust_DocumentWidget_(d, resp); 1863 updateTrust_DocumentWidget_(d, resp);
1864 init_Anim(&d->sideOpacity, 0); 1864 init_Anim(&d->sideOpacity, 0);
1865 init_Anim(&d->altTextOpacity, 0); 1865 init_Anim(&d->altTextOpacity, 0);
1866 format_String(&d->sourceHeader, "%d %s", statusCode, get_GmError(statusCode)->title); 1866 format_String(&d->sourceHeader,
1867 "%d %s",
1868 statusCode,
1869 isEmpty_String(&resp->meta) && !isSuccess_GmStatusCode(statusCode)
1870 ? get_GmError(statusCode)->title
1871 : cstr_String(&resp->meta));
1867 d->sourceStatus = statusCode; 1872 d->sourceStatus = statusCode;
1868 switch (category_GmStatusCode(statusCode)) { 1873 switch (category_GmStatusCode(statusCode)) {
1869 case categoryInput_GmStatusCode: { 1874 case categoryInput_GmStatusCode: {
@@ -2773,14 +2778,12 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2773 id_GmRequest(d->request) == argU32Label_Command(cmd, "reqid")) { 2778 id_GmRequest(d->request) == argU32Label_Command(cmd, "reqid")) {
2774 set_Block(&d->sourceContent, body_GmRequest(d->request)); 2779 set_Block(&d->sourceContent, body_GmRequest(d->request));
2775 if (!isSuccess_GmStatusCode(status_GmRequest(d->request))) { 2780 if (!isSuccess_GmStatusCode(status_GmRequest(d->request))) {
2781 /* TODO: Why is this here? Can it be removed? */
2776 format_String(&d->sourceHeader, 2782 format_String(&d->sourceHeader,
2777 "%d %s", 2783 "%d %s",
2778 status_GmRequest(d->request), 2784 status_GmRequest(d->request),
2779 cstr_String(meta_GmRequest(d->request))); 2785 cstr_String(meta_GmRequest(d->request)));
2780 } 2786 }
2781 else {
2782 clear_String(&d->sourceHeader);
2783 }
2784 updateFetchProgress_DocumentWidget_(d); 2787 updateFetchProgress_DocumentWidget_(d);
2785 checkResponse_DocumentWidget_(d); 2788 checkResponse_DocumentWidget_(d);
2786 if (category_GmStatusCode(status_GmRequest(d->request)) == categorySuccess_GmStatusCode) { 2789 if (category_GmStatusCode(status_GmRequest(d->request)) == categorySuccess_GmStatusCode) {
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index 9f233345..2f0cabbb 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -360,35 +360,64 @@ static int endX_InputWidget_(const iInputWidget *d, int y) {
360 return line->range.end - (isLastLine_InputWidget_(d, line) ? 0 : 1) - line->range.start; 360 return line->range.end - (isLastLine_InputWidget_(d, line) ? 0 : 1) - line->range.start;
361} 361}
362 362
363static iBool isCursorFocusable_Char_(iChar c) {
364 return !isDefaultIgnorable_Char(c) &&
365 !isVariationSelector_Char(c) &&
366 !isFitzpatrickType_Char(c);
367}
368
369static iChar at_InputWidget_(const iInputWidget *d, iInt2 pos) {
370 if (pos.y >= 0 && pos.y < size_Array(&d->lines) &&
371 pos.x >= 0 && pos.x <= endX_InputWidget_(d, pos.y)) {
372 iChar ch = 0;
373 decodeBytes_MultibyteChar(charPos_InputWidget_(d, pos),
374 constEnd_String(lineString_InputWidget_(d, pos.y)),
375 &ch);
376 return ch;
377 }
378 return ' ';
379}
380
363static iInt2 movedCursor_InputWidget_(const iInputWidget *d, iInt2 pos, int xDir, int yDir) { 381static iInt2 movedCursor_InputWidget_(const iInputWidget *d, iInt2 pos, int xDir, int yDir) {
364 iChar ch; 382 iChar ch = 0;
365 if (xDir < 0) { 383 int n = 0;
366 if (pos.x == 0) { 384 /* TODO: The cursor should never land on any combining codepoints either. */
367 if (pos.y > 0) { 385 for (;;) {
368 pos.x = endX_InputWidget_(d, --pos.y); 386 if (xDir < 0) {
387 if (pos.x == 0) {
388 if (pos.y > 0) {
389 pos.x = endX_InputWidget_(d, --pos.y);
390 }
369 } 391 }
370 } 392 else {
371 else { 393 iAssert(pos.x > 0);
372 iAssert(pos.x > 0); 394 n = decodePrecedingBytes_MultibyteChar(charPos_InputWidget_(d, pos),
373 int n = decodePrecedingBytes_MultibyteChar(charPos_InputWidget_(d, pos),
374 cstr_String(lineString_InputWidget_(d, pos.y)), 395 cstr_String(lineString_InputWidget_(d, pos.y)),
375 &ch); 396 &ch);
376 pos.x -= n; 397 pos.x -= n;
377 } 398 if (!isCursorFocusable_Char_(at_InputWidget_(d, pos))) {
378 } 399 continue;
379 else if (xDir > 0) { 400 }
380 if (pos.x == endX_InputWidget_(d, pos.y)) {
381 if (pos.y < size_Array(&d->lines) - 1) {
382 pos.y++;
383 pos.x = 0;
384 } 401 }
385 } 402 }
386 else { 403 else if (xDir > 0) {
387 int n = decodeBytes_MultibyteChar(charPos_InputWidget_(d, pos), 404 if (pos.x == endX_InputWidget_(d, pos.y)) {
405 if (pos.y < size_Array(&d->lines) - 1) {
406 pos.y++;
407 pos.x = 0;
408 }
409 }
410 else {
411 n = decodeBytes_MultibyteChar(charPos_InputWidget_(d, pos),
388 constEnd_String(lineString_InputWidget_(d, pos.y)), 412 constEnd_String(lineString_InputWidget_(d, pos.y)),
389 &ch); 413 &ch);
390 pos.x += n; 414 pos.x += n;
415 if (!isCursorFocusable_Char_(at_InputWidget_(d, pos))) {
416 continue;
417 }
418 }
391 } 419 }
420 break;
392 } 421 }
393 return pos; 422 return pos;
394} 423}
@@ -1026,7 +1055,9 @@ static void insertRange_InputWidget_(iInputWidget *d, iRangecc range) {
1026 cstr_String(&line->text) + d->cursor.x, constEnd_String(&line->text) 1055 cstr_String(&line->text) + d->cursor.x, constEnd_String(&line->text)
1027 }); 1056 });
1028 truncate_String(&line->text, d->cursor.x); 1057 truncate_String(&line->text, d->cursor.x);
1029 appendCStr_String(&line->text, "\n"); 1058 if (!endsWith_String(&line->text, "\n")) {
1059 appendCStr_String(&line->text, "\n");
1060 }
1030 insert_Array(&d->lines, ++d->cursor.y, &split); 1061 insert_Array(&d->lines, ++d->cursor.y, &split);
1031 d->cursor.x = 0; 1062 d->cursor.x = 0;
1032 } 1063 }
@@ -1059,6 +1090,8 @@ void setCursor_InputWidget(iInputWidget *d, iInt2 pos) {
1059 iAssert(!isEmpty_Array(&d->lines)); 1090 iAssert(!isEmpty_Array(&d->lines));
1060 pos.x = iClamp(pos.x, 0, endX_InputWidget_(d, pos.y)); 1091 pos.x = iClamp(pos.x, 0, endX_InputWidget_(d, pos.y));
1061 d->cursor = pos; 1092 d->cursor = pos;
1093 iChar ch = at_InputWidget_(d, pos);
1094 printf("cursor x:%d ch:%08x (%lc)\n", pos.x, ch, (int)ch);
1062 /* Update selection. */ 1095 /* Update selection. */
1063 if (isMarking_()) { 1096 if (isMarking_()) {
1064 if (isEmpty_Range(&d->mark)) { 1097 if (isEmpty_Range(&d->mark)) {
@@ -1221,18 +1254,6 @@ static iBool deleteMarked_InputWidget_(iInputWidget *d) {
1221 return iFalse; 1254 return iFalse;
1222} 1255}
1223 1256
1224static iChar at_InputWidget_(const iInputWidget *d, iInt2 pos) {
1225 if (pos.y >= 0 && pos.y < size_Array(&d->lines) &&
1226 pos.x >= 0 && pos.x <= endX_InputWidget_(d, pos.y)) {
1227 iChar ch = 0;
1228 decodeBytes_MultibyteChar(charPos_InputWidget_(d, pos),
1229 constEnd_String(lineString_InputWidget_(d, pos.y)),
1230 &ch);
1231 return ch;
1232 }
1233 return ' ';
1234}
1235
1236static iBool isWordChar_InputWidget_(const iInputWidget *d, iInt2 pos) { 1257static iBool isWordChar_InputWidget_(const iInputWidget *d, iInt2 pos) {
1237 return isAlphaNumeric_Char(at_InputWidget_(d, pos)); 1258 return isAlphaNumeric_Char(at_InputWidget_(d, pos));
1238} 1259}
diff --git a/src/ui/text.c b/src/ui/text.c
index 0a8386e4..639d8f13 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -1433,6 +1433,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1433 iBool isFirst = iTrue; 1433 iBool isFirst = iTrue;
1434 const iBool checkHitPoint = wrap && !isEqual_I2(wrap->hitPoint, zero_I2()); 1434 const iBool checkHitPoint = wrap && !isEqual_I2(wrap->hitPoint, zero_I2());
1435 const iBool checkHitChar = wrap && wrap->hitChar; 1435 const iBool checkHitChar = wrap && wrap->hitChar;
1436 iBool wasCharHit = iFalse;
1436 while (!isEmpty_Range(&wrapRuns)) { 1437 while (!isEmpty_Range(&wrapRuns)) {
1437 if (isFirst) { 1438 if (isFirst) {
1438 isFirst = iFalse; 1439 isFirst = iFalse;
@@ -1452,9 +1453,10 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1452 for (size_t runIndex = wrapRuns.start; runIndex < wrapRuns.end; runIndex++) { 1453 for (size_t runIndex = wrapRuns.start; runIndex < wrapRuns.end; runIndex++) {
1453 const iAttributedRun *run = at_Array(&attrText.runs, runIndex); 1454 const iAttributedRun *run = at_Array(&attrText.runs, runIndex);
1454 if (run->flags.isLineBreak) { 1455 if (run->flags.isLineBreak) {
1455 if (checkHitChar) { 1456 if (checkHitChar && !wasCharHit) {
1456 if (wrap->hitChar == sourcePtr_AttributedText_(&attrText, run->logical.start)) { 1457 if (wrap->hitChar == sourcePtr_AttributedText_(&attrText, run->logical.start)) {
1457 wrap->hitAdvance_out = init_I2(wrapAdvance, yCursor); 1458 wrap->hitAdvance_out = init_I2(wrapAdvance, yCursor);
1459 wasCharHit = iTrue;
1458 } 1460 }
1459 } 1461 }
1460 wrapPosRange.end = run->logical.start; 1462 wrapPosRange.end = run->logical.start;
@@ -1479,9 +1481,10 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1479 if (logPos < wrapPosRange.start || logPos >= wrapPosRange.end) { 1481 if (logPos < wrapPosRange.start || logPos >= wrapPosRange.end) {
1480 continue; 1482 continue;
1481 } 1483 }
1482 if (checkHitChar) { 1484 if (checkHitChar && !wasCharHit) {
1483 if (wrap->hitChar == sourcePtr_AttributedText_(&attrText, logPos)) { 1485 if (wrap->hitChar == sourcePtr_AttributedText_(&attrText, logPos)) {
1484 wrap->hitAdvance_out = init_I2(wrapAdvance, yCursor); 1486 wrap->hitAdvance_out = init_I2(wrapAdvance, yCursor);
1487 wasCharHit = iTrue;
1485 } 1488 }
1486 } 1489 }
1487 /* Check if the hit point is on the left side of this line. */ 1490 /* Check if the hit point is on the left side of this line. */
diff --git a/src/ui/uploadwidget.c b/src/ui/uploadwidget.c
index 57b6b6b7..5e1ee493 100644
--- a/src/ui/uploadwidget.c
+++ b/src/ui/uploadwidget.c
@@ -149,7 +149,7 @@ void init_UploadWidget(iUploadWidget *d) {
149 iWidget *buttons = 149 iWidget *buttons =
150 makeDialogButtons_Widget((iMenuItem[]){ { "${upload.port}", 0, 0, "upload.setport" }, 150 makeDialogButtons_Widget((iMenuItem[]){ { "${upload.port}", 0, 0, "upload.setport" },
151 { "---", 0, 0, NULL }, 151 { "---", 0, 0, NULL },
152 { "${cancel}", SDLK_ESCAPE, 0, "upload.cancel" }, 152 { "${close}", SDLK_ESCAPE, 0, "upload.cancel" },
153 { uiTextAction_ColorEscape "${dlg.upload.send}", 153 { uiTextAction_ColorEscape "${dlg.upload.send}",
154 SDLK_RETURN, 154 SDLK_RETURN,
155 KMOD_PRIMARY, 155 KMOD_PRIMARY,
diff --git a/src/ui/window.c b/src/ui/window.c
index 86d22b1c..1727b988 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -219,25 +219,23 @@ static void updateSize_Window_(iWindow *d, iBool notifyAlways) {
219 size->y -= d->keyboardHeight; 219 size->y -= d->keyboardHeight;
220 if (notifyAlways || !isEqual_I2(oldSize, *size)) { 220 if (notifyAlways || !isEqual_I2(oldSize, *size)) {
221 windowSizeChanged_Window_(d); 221 windowSizeChanged_Window_(d);
222 const iBool isHoriz = (d->place.lastNotifiedSize.x != size->x); 222 if (!isEqual_I2(*size, d->place.lastNotifiedSize)) {
223 const iBool isVert = (d->place.lastNotifiedSize.y != size->y); 223 const iBool isHoriz = (d->place.lastNotifiedSize.x != size->x);
224 postCommandf_App("window.resized width:%d height:%d horiz:%d vert:%d", 224 const iBool isVert = (d->place.lastNotifiedSize.y != size->y);
225 size->x, 225 postCommandf_App("window.resized width:%d height:%d horiz:%d vert:%d",
226 size->y, 226 size->x,
227 isHoriz, 227 size->y,
228 isVert); 228 isHoriz,
229 postCommand_App("widget.overflow"); /* check bounds with updated sizes */ 229 isVert);
230 postCommand_App("widget.overflow"); /* check bounds with updated sizes */
231 }
230 postRefresh_App(); 232 postRefresh_App();
231 d->place.lastNotifiedSize = *size; 233 d->place.lastNotifiedSize = *size;
232 } 234 }
233} 235}
234 236
235void drawWhileResizing_Window(iWindow *d, int w, int h) { 237void drawWhileResizing_Window(iWindow *d, int w, int h) {
236 /* This is called while a window resize is in progress, so we can be pretty confident 238 draw_Window(d);
237 the size has actually changed. */
238 d->size = coord_Window(d, w, h);
239 windowSizeChanged_Window_(d);
240 draw_Window(d);
241} 239}
242 240
243static float pixelRatio_Window_(const iWindow *d) { 241static float pixelRatio_Window_(const iWindow *d) {
@@ -489,7 +487,7 @@ void init_Window(iWindow *d, iRect rect) {
489 SDL_GetRendererOutputSize(d->render, &d->size.x, &d->size.y); 487 SDL_GetRendererOutputSize(d->render, &d->size.x, &d->size.y);
490 setupUserInterface_Window(d); 488 setupUserInterface_Window(d);
491 postCommand_App("~bindings.changed"); /* update from bindings */ 489 postCommand_App("~bindings.changed"); /* update from bindings */
492 updateSize_Window_(d, iFalse); 490 //updateSize_Window_(d, iFalse);
493 /* Load the border shadow texture. */ { 491 /* Load the border shadow texture. */ {
494 SDL_Surface *surf = loadImage_(&imageShadow_Embedded, 0); 492 SDL_Surface *surf = loadImage_(&imageShadow_Embedded, 0);
495 d->borderShadow = SDL_CreateTextureFromSurface(d->render, surf); 493 d->borderShadow = SDL_CreateTextureFromSurface(d->render, surf);
@@ -728,7 +726,7 @@ static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) {
728 } 726 }
729 case SDL_WINDOWEVENT_RESIZED: 727 case SDL_WINDOWEVENT_RESIZED:
730 if (d->isMinimized) { 728 if (d->isMinimized) {
731 updateSize_Window_(d, iTrue); 729 //updateSize_Window_(d, iTrue);
732 return iTrue; 730 return iTrue;
733 } 731 }
734 if (unsnap_Window_(d, NULL)) { 732 if (unsnap_Window_(d, NULL)) {
@@ -739,7 +737,7 @@ static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) {
739 //printf("normal rect set (resize)\n"); fflush(stdout); 737 //printf("normal rect set (resize)\n"); fflush(stdout);
740 } 738 }
741 checkPixelRatioChange_Window_(d); 739 checkPixelRatioChange_Window_(d);
742 updateSize_Window_(d, iTrue /* we were already redrawing during the resize */); 740 //updateSize_Window_(d, iTrue /* we were already redrawing during the resize */);
743 postRefresh_App(); 741 postRefresh_App();
744 return iTrue; 742 return iTrue;
745 case SDL_WINDOWEVENT_RESTORED: 743 case SDL_WINDOWEVENT_RESTORED:
@@ -1014,7 +1012,7 @@ void draw_Window(iWindow *d) {
1014 if (d->isDrawFrozen) { 1012 if (d->isDrawFrozen) {
1015 return; 1013 return;
1016 } 1014 }
1017#if defined (iPlatformMobile) 1015//#if defined (iPlatformMobile)
1018 /* Check if root needs resizing. */ { 1016 /* Check if root needs resizing. */ {
1019 iInt2 renderSize; 1017 iInt2 renderSize;
1020 SDL_GetRendererOutputSize(d->render, &renderSize.x, &renderSize.y); 1018 SDL_GetRendererOutputSize(d->render, &renderSize.x, &renderSize.y);
@@ -1023,7 +1021,7 @@ void draw_Window(iWindow *d) {
1023 processEvents_App(postedEventsOnly_AppEventMode); 1021 processEvents_App(postedEventsOnly_AppEventMode);
1024 } 1022 }
1025 } 1023 }
1026#endif 1024//#endif
1027 const int winFlags = SDL_GetWindowFlags(d->win); 1025 const int winFlags = SDL_GetWindowFlags(d->win);
1028 const iBool gotFocus = (winFlags & SDL_WINDOW_INPUT_FOCUS) != 0; 1026 const iBool gotFocus = (winFlags & SDL_WINDOW_INPUT_FOCUS) != 0;
1029 iPaint p; 1027 iPaint p;