Repository navigation
The History tab for 0.9.6: a row's menu, Export take, Restored, Original, UTF-8 - #162
Merged
Merged
Conversation
…r the list Either mouse button (or Ctrl-click, or Return on the selected row) selects the row and offers Restore this state, Export take..., and, after a separator, Clear history of this slot... The items and when each can be chosen are historymenu::forRow, a function without a window: the same four lines on every row, grey when the row cannot do it, and all grey while the pedal is busy. The pane draws what it returns and asks the rule again when a line is chosen, so a menu left open across a re-read acts on nothing stale. Export take is offered here; its wiring follows.
Play loaded the take into the Audio tab's player without starting it or showing it, and read as nothing happening. A click on a row now opens its menu, so a double-click never reaches the row either. The tab's only caller of playFromHistory is gone with it; the History window keeps its own Play through playArchivedTake.
The save dialog proposes the take's own name, the one the row shows, with .wav added only when the name does not already end so (the pedal's own recordings are upper case); a take without a name would be proposed as "slot N take.wav". The write is history::exportTake, the History window's: the store's bytes, whole or absent, and a take the history no longer keeps is its sentence rather than silence. It runs as a worker job that needs no card and records no operation: nothing is read from the pedal or sent to it. The chosen file crosses into the core as UTF-8 (fsPath), so a folder named outside the Windows code page is still found.
The row read only the restore's note, and the job writes none, so it was
empty. A restore now records the operation it goes back to in a column of
its own (store version 11, ops.restores, nullable, a foreign key to ops),
named before the card is touched, from the tab and the window alike. The
row then reads like Pushed - the take, its length, its tempo - and adds
"back to 17:52", the time of the row it went back to; on another day than
the restore, with the date too ("back to 11 Oct 17:52"). A state that held
no take says "empty slot". A restore older than version 11, or whose row was
forgotten (forgetting clears the reference, as it does for reverts), says
what came back and claims no time.
The tab's time column and that "back to" now come from one function, so a
row names another row's time the way that row shows it - and a date a year
apart is no longer read as today.
What the app found in a slot the first time it met the card is the
Original, named by its take. A slot that was empty then has no Original row
at all: there was nothing, and a row saying so read as something that
happened. A first sighting that failed for a slot keeps its row - not seen
is not empty. The words around it move too: the status line while it runs
("Saving the original: N of 99 slots"), the storage panel's label
("Original - <take>"), and the toast when it is cut off ("Saving the
original stopped; it will finish next time you connect").
…d under story::tell had no words for the rhythm and playstop operations and fell back to the raw kind, so a row read "rhythm" or "playstop". It now says "Rhythm" and "Start & Stop", and kindWords - which also names an operation in the restore and undo words - says "Rhythm change" and "Start & Stop change". The tabs that set them are hidden; their rows stay.
JUCE reads a bare const char* as ASCII, and `String + const char*` reads
the right side as UTF-8: a literal holding a dash, an arrow or a curly quote
on the LEFT of `+`, or alone, became Latin-1 mojibake. That was every
connect line in operations.log ("refused, nothing sent â..."), and the
job description "Rename the pedal to ..." on screen. Those literals now go
through fromUTF8. The storage-mode request returned e.what() into a
juce::String the same way, so a MIDI device named outside ASCII came back
garbled in the refusal; it goes through utf8() now, on both backends.
The comment that blamed std::string for the mojibake had it backwards:
juce::String(std::string) decodes UTF-8; const char* is what reads ASCII.
…CE are ASCII tools/check_utf8_literals.py, registered in ctest: a narrow string literal holding a byte above 0x7f - raw, or as a \x, octal or \u escape - must be the direct argument of fromUTF8(...) or utf8(...), wherever it sits, the right side of `+` included; one rule a reader can check by eye. Bytes that are data rather than words (a FAT name with an accent, a sequence a parser must refuse) carry a `// utf8-data: <why>` comment on their line. The lint's own suite plants each way of writing such a byte and checks it is caught, and that comments, wide and character literals are not. To meet it, the words the core and the JUCE-free app headers make - error sentences, the banner and status lines, the pedal-presence hints - are ASCII now: a dash is " - ", an arrow "->", an ellipsis "...". JUCE-side text keeps its typography through fromUTF8.
Tests written from the spec of feat/history-row-menu that try to break it, not to confirm it: the rows layer against the restore primitive's own gate, the Original row of an interrupted first sighting and of a slot whose body claims a loop, a stale menu after the slot changed, "Restore this state" of a Cleared row end to end, a restore reference the store cannot resolve, chained restores forgotten in either order, the v11 migration step failing last from v7 and from v10, the clock at midnight, at the year's edge, on DST days and before the epoch, the month's name under another locale, the export job without a card, and the UTF-8 lint against spliced escapes, CRLF, preprocessor apostrophes and other source suffixes. A failing check is a finding; nothing in app/, core/ or tools/ is changed except the new attack script and its ctest entry.
A row was restorable when it had a body and any kept take, and the restore
then handed that take to commands::restore beside the body. For a Cleared
row, or an undo that emptied the slot, the take is the one the operation
ARCHIVED, so the enabled "Restore this state" always ended in the
primitive's refusal ("a body and a take that disagree") and a failed
restore in the history. A trim cut off before its take landed, and a stray
file beside a body that says the slot is empty, were offered the same way.
A state is the body an operation left and the take it left beside it (the
'after' side). It is offered when the body's WavStat agrees with that take
- a loop needs its take kept in the store, an empty slot needs none - which
is the primitive's own gate. Both restores, the tab's and the window's,
hand over only that take. Test fixtures now write bodies the way the card
does: a slot with a loop says so in WavStat.
The first sighting commits slot by slot, so a slot it photographed is whole while the sighting runs and after it was cut off at another slot. Its row read as the interruption instead - the sighting's note in place of the take's name, "interrupted" in the warning colour - until the next connect. That slot's row is now its Original, finished; the card's row still says the sighting was interrupted, and only a slot the sighting failed to read says so on its own row. A slot counts as empty at first sight only when its body says it holds no loop too: a body that claims one beside an empty folder was not seen empty, and keeps its Original row.
The menu kept the row's operation, and a swap's operation is a row of both its slots: with the menu open on slot 4, selecting slot 9 turned "Clear history of this slot..." onto slot 9 and "Restore this state" onto slot 9's row. The menu now carries its slot. A choice made once the pane shows another slot does nothing, and the restore and the export say which slot they were about, so the app acts only while that slot is still selected.
clockWords used strftime's %b, which answers in the process locale: under
another one the History tab's column and a Restored row's "back to" would
change alphabet, out of a JUCE-free header whose words are ASCII. It names
the month itself now. A moment before the epoch is rounded down to its
second, not toward zero. And a restore whose target the store cannot
resolve - a reference planted past the foreign key - no longer reads the
missing time as 0 ("back to 01:00"): no row, no time.
…aracters The partial file was built from path::string() - the narrow code page on Windows, where a folder named outside it gets another path or a throw - and a write that failed came back as "cannot write take.wav.part", the name the player never chose. The partial is the chosen path with ".part" appended in its own characters, a refusal names the chosen file in UTF-8, and the partial is removed whatever the write threw.
Lines are joined before any token is read - CRLF and CR end a line like LF, and a backslash at the end of a line joins it to the next - so an escape split by a continuation (\x\<newline>e2) is caught, and a // comment continued under CRLF hides the next line as it does under LF. A quote with no partner on its line is not a literal the compiler reads (an apostrophe in #error text, prose under #if 0), and no longer makes a valid file uncheckable. .cxx, .hh, .hxx and .m files are read too.
…page The Windows lane failed export_take_job_tests: the test's folder and file names were typed into u8"..." literals, and MSVC, which reads a source in the system's code page unless told otherwise, compiled other bytes than clang did - the file landed under that other name and the job's words named it. The same holds for any literal: inside fromUTF8 a typed dash survives only because cp1252 happens to map its three bytes back. The lint now requires an escape for every byte above 0x7f in every literal - narrow or wide, u8, a character, inside fromUTF8 or marked as data - and the three typed ones in the tree are escapes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The History tab for 0.9.6: a menu on each row instead of three buttons, Export take, a Restored row that says what came back, the Original, the app's own words for Rhythm and Start & Stop, and UTF-8 that stays UTF-8 on its way into JUCE. One commit per item, so any one of them can be dropped without the rest.
app/HistoryRowMenu.h,app/HistoryPane.*): a click on a row with either button (Ctrl-click on a Mac, or Return on the selected row) selects it and offers Restore this state, Export take..., a separator, Clear history of this slot.... What can be chosen ishistorymenu::forRow, a function without a window: the same four lines on every row, grey when the row cannot do it, all grey while the pedal is busy. The pane asks the rule again when a line is chosen, so a menu left open across a re-read acts on nothing stale.app/history/ExportTakeJob.h): the save dialog proposes the take's own name (.wavadded only when missing; a nameless take would beslot N take.wav); the write ishistory::exportTake, the window's - whole or absent, and a take no longer kept is a sentence. A worker job that needs no card and records no operation. The chosen file crosses into the core as UTF-8 (fsPath), so a folder outside the Windows code page is found.ops.restores(nullable, a foreign key toops), named before the card is touched, from the tab and the window. The row reads like Pushed - take, length, tempo - and adds "back to 17:52", the time of that row; on another day than the restore, with its date ("back to 11 Oct 17:52"). An older restore, or one whose row was forgotten (forgetting clears the reference), says what came back and claims no time. The tab's time column and that "back to" now come from one function.rhythmandplaystop.const char*as ASCII, andString + const char*reads the right side as UTF-8, so a literal holding a dash or a curly quote on the left of+, or alone, became Latin-1 mojibake - every connect line inoperations.log, and the "Rename the pedal to ..." job on screen. Those go throughfromUTF8;e.what()into ajuce::Stringgoes throughutf8()on both storage-mode backends.tools/check_utf8_literals.py, in ctest, holds the tree to one rule: a narrow literal with a byte above 0x7f is the direct argument offromUTF8(...)/utf8(...), or carries// utf8-data: <why>when it is data (a FAT name, bytes a parser must refuse). Text made in the core and in JUCE-free headers is ASCII now: " - " for a dash, "->" for an arrow, "..." for an ellipsis.After an adversarial review (its attacking tests are the commit "Attacking tests for the History row menu review"; each fix after it turns some of them green):
commands::restore- for a Cleared row, or an undo that emptied the slot, the take it had archived, which the primitive refuses beside an empty body. This was there before the menu; the menu made it a line you can click. A state is now the body and the take left beside it, offered when the body's WavStat agrees with that take (the primitive's own gate); both restores hand over only that take.%bfollows the process locale), rounds down before the epoch, and a restore whose target the store cannot resolve claims no time.take.wav.part, and the partial path keeps its characters on Windows.#error/#if 0text is not a literal,.cxx/.hh/.hxx/.mread too.u8"..."in a test compiled to other bytes on Windows. The lint now requires an escape in every literal - narrow or wide, u8, a character, insidefromUTF8or marked as data.Tests:
history_row_menu_tests(new: the menu over the rows a real store makes, free and busy),history_pane_tests(rewritten for the menu),clear_confirmation_tests(export and restore through the menu, the Restored row's words),card_timeline_tests(the proposed name),slot_story_tests(Restored, Original, the clock, Rhythm),slot_rows_tests(Original kept or dropped),history_store_tests(setRestores, a restore of a restore, forgetting the target),history_schema_tests(v7 and v10 files to v11: rows unchanged, the column empty, a second migrate a no-op),undo_run_tests,first_seen_tests,operations_log_tests(new: a log line with a non-ASCII reason, byte for byte),pedal_link_tests(a non-ASCII endpoint name through the refusal),card_rows_tests,history_window_tests,export_take_job_tests(new, from the review),utf8_literals_lint,utf8_literals_lint_testsandutf8_literals_lint_attack_tests(new).Locally on macOS: a clean build has the 16 warnings main has (same files and lines), 0 errors; ctest 80/80, the six GUI harnesses included. The v11 step was run by this build on copies of three real stores (two at v10, one at v7): every table's row count unchanged, integrity_check ok, no foreign-key violation,
restoresempty on every old row.