Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 22 additions & 9 deletions Src/xWorks/Avalonia/Composer/DetailComposer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,10 @@ private sealed class CompilerSources

public static ComposedDetail Compose(ILexEntry entry, LcmCache cache, bool showHiddenFields = false,
SlicePluginRegistry plugins = null,
ViewDefinitionOverrideResolver overrides = null)
=> Compose((ICmObject)entry, cache, "Normal", showHiddenFields, plugins, overrides);
ViewDefinitionOverrideResolver overrides = null,
ISet<string> showAllWritingSystemsFields = null)
=> Compose((ICmObject)entry, cache, "Normal", showHiddenFields, plugins, overrides,
showAllWritingSystemsFields: showAllWritingSystemsFields);

/// <summary>
/// Compose the structured detail view for ANY record root + starting layout -- the
Expand All @@ -127,10 +129,14 @@ public static ComposedDetail Compose(ILexEntry entry, LcmCache cache, bool showH
/// object and the starting layout instead of hardcoding LexEntry/"Normal", so wiring a new tool onto
/// the Avalonia side needs only its registration + (when its layout uses one) a layoutChoiceField.
/// </summary>
/// <param name="showAllWritingSystemsFields">Template StableIds of parts under a
/// transient "Show all right now" reveal: every row of those parts composes with its
/// full writing-system set, ignoring per-field visibility restrictions.</param>
public static ComposedDetail Compose(ICmObject obj, LcmCache cache, string layoutName = "Normal",
bool showHiddenFields = false, SlicePluginRegistry plugins = null,
ViewDefinitionOverrideResolver overrides = null,
string layoutChoiceField = null)
string layoutChoiceField = null,
ISet<string> showAllWritingSystemsFields = null)
{
if (obj == null) throw new ArgumentNullException(nameof(obj));
if (cache == null) throw new ArgumentNullException(nameof(cache));
Expand All @@ -150,7 +156,8 @@ public static ComposedDetail Compose(ICmObject obj, LcmCache cache, string layou
// bridges the gap (plugin factories run at render time, not compose).
IDetailEditContext composedContext = null;
var state = new ComposeState(cache, showHiddenFields,
plugins ?? SlicePluginRegistry.Default, () => composedContext, overrides);
plugins ?? SlicePluginRegistry.Default, () => composedContext, overrides,
showAllWritingSystemsFields);
state.EnterModel(root);
foreach (var node in root.Roots)
state.Walk(node, obj, 0);
Expand Down Expand Up @@ -294,6 +301,9 @@ public FieldEditHandler HandlerFor(string stableId)
// receive (resolved when the factory runs, after Compose has built the context).
private readonly SlicePluginRegistry _plugins;
private readonly Func<IDetailEditContext> _editContextAccessor;
// Parts under the host's transient "Show all right now" reveal (template StableIds);
// every row of those parts composes with its full writing-system set.
private readonly ISet<string> _showAllWsFields;
// Per-compose memos -- the morph-type option list is identical for every
// IMoForm, and an item layout's menu/hotlinks binding is identical per (class, layout).
private List<DetailChoiceOption> _morphTypeOptions;
Expand Down Expand Up @@ -327,13 +337,15 @@ public FieldEditHandler HandlerFor(string stableId)

public ComposeState(LcmCache cache, bool showHiddenFields,
SlicePluginRegistry plugins, Func<IDetailEditContext> editContextAccessor,
ViewDefinitionOverrideResolver overrides = null)
ViewDefinitionOverrideResolver overrides = null,
ISet<string> showAllWritingSystemsFields = null)
{
_cache = cache;
_showHidden = showHiddenFields;
_plugins = plugins;
_editContextAccessor = editContextAccessor;
_overrides = overrides;
_showAllWsFields = showAllWritingSystemsFields;
_sda = cache.DomainDataByFlid;
_mdc = (IFwMetaDataCacheManaged)cache.DomainDataByFlid.MetaDataCache;
}
Expand Down Expand Up @@ -1057,9 +1069,9 @@ private void WalkTextField(ViewNode node, ICmObject obj, int depth)
RegisterTextRowEditHandler(stableId, hvo, flid, type, systems);
}

// The writing systems of a text row: the layout set restricted by the field's per-field
// visibleWritingSystems override, then collapsed to a single derived row ws for a
// single-alternative (String/Unicode) property. Split out of WalkTextField unchanged.
// A text row's writing systems: the layout set, restricted by visibleWritingSystems
// unless the row's part is under the Show-all reveal, then collapsed to one ws for
// String/Unicode props.
private IReadOnlyList<CoreWritingSystemDefinition> ResolveTextRowWritingSystems(int hvo, int flid,
CellarPropertyType type, ViewNode node)
{
Expand All @@ -1069,7 +1081,8 @@ private IReadOnlyList<CoreWritingSystemDefinition> ResolveTextRowWritingSystems(
// field's valid writing systems. An empty intersection keeps the full set rather than hiding
// the field entirely (defensive -- a stale override must never blank a real
// field).
systems = ApplyVisibleWritingSystems(systems, node.VisibleWritingSystems);
if (_showAllWsFields == null || !_showAllWsFields.Contains(node.StableId))
systems = ApplyVisibleWritingSystems(systems, node.VisibleWritingSystems);
if ((type == CellarPropertyType.String || type == CellarPropertyType.Unicode)
&& systems.Count > 0)
{
Expand Down
83 changes: 68 additions & 15 deletions Src/xWorks/Avalonia/Hosting/RecordEditView.Avalonia.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2003-2017 SIL International
// Copyright (c) 2003-2017 SIL International
// This software is licensed under the LGPL, version 2.1 or later
// (http://www.gnu.org/licenses/lgpl-2.1.html)

Expand Down Expand Up @@ -73,6 +73,13 @@ public partial class RecordEditView
// projects/windows for the app lifetime.
private readonly Dictionary<string, bool> m_expansionStates = new Dictionary<string, bool>();

// The transient "Show all right now" reveal: template StableIds of parts whose rows
// show every writing-system option. Cleared when the shown record changes; never
// persisted.
private readonly HashSet<string> m_showAllWsFields = new HashSet<string>(StringComparer.Ordinal);
// The record m_showAllWsFields belongs to; a different record expires the reveal.
private int m_showAllWsRecordHvo;

private bool ShouldUseAvaloniaLexiconEdit
{
get { return m_activeUIFramework == UIFramework.Avalonia; }
Expand Down Expand Up @@ -142,6 +149,10 @@ private void TearDownAvaloniaEntryForm()
SettleDetailEdits();
m_detailEditContext.Clear();
m_avaloniaEntryForm?.Dispose();
// Transient view state dies with the view: rebuilding the host must not
// resurrect a stale reveal.
m_showAllWsFields.Clear();
m_showAllWsRecordHvo = 0;
// Null the host + refresh controller after disposing them. The recreation guards
// (EnsureAvaloniaEntryFormInitialized / EnsureAvaloniaRefreshController) key on `== null`, so a
// runtime flip New->Legacy->New rebuilds a fresh entry form instead of re-showing a disposed one.
Expand Down Expand Up @@ -297,6 +308,12 @@ private void ShowAvaloniaEntry(ICmObject obj)
// cancel-on-displace remains the safety net.
SettleDetailEdits();

// Record-navigation expiry: the transient reveal belongs to ONE record -- it
// survives focus changes within the record and dies when the record changes.
if (obj == null || obj.Hvo != m_showAllWsRecordHvo)
m_showAllWsFields.Clear();
m_showAllWsRecordHvo = obj?.Hvo ?? 0;

// Adapter hygiene: the hidden command-routing DataTree must never answer mediator
// commands for a PREVIOUS record -- reset it whenever the shown record changes; the
// next
Expand Down Expand Up @@ -333,14 +350,16 @@ private void ShowAvaloniaEntry(ICmObject obj)
{
composed = lexEntry != null
? DetailComposer.Compose(lexEntry, Cache, showHidden,
overrides: ResolveViewOverride)
overrides: ResolveViewOverride,
showAllWritingSystemsFields: m_showAllWsFields)
// Non-entry roots compose against the tool's configured layout
// (m_layoutName, default "Normal"); a type-selected layout (m_layoutChoiceField, e.g.
// Notebook RnGenericRec keyed on "Type") resolves to the right variant inside Compose.
: DetailComposer.Compose(obj, Cache,
string.IsNullOrEmpty(m_layoutName) ? "Normal" : m_layoutName, showHidden,
overrides: ResolveViewOverride,
layoutChoiceField: m_layoutChoiceField);
layoutChoiceField: m_layoutChoiceField,
showAllWritingSystemsFields: m_showAllWsFields);
if (composed != null)
{
detail = composed.Model;
Expand Down Expand Up @@ -584,6 +603,10 @@ private Func<ChoiceBase, UIItemDisplayProperties, DetailMenuItem> BuildOverrideC
// even when locating fails.
var registry = new OverrideCommandRegistry();
registry.Add(IsWritingSystemVisibilityChoice, (c, d) => WritingSystemItem(c, d, field));
// Show all right now never dispatches or persists: it only marks the row for the
// host's transient reveal.
registry.Add("CmdDataTree-WritingSystemMenu-ShowAllRightNow",
(c, d) => ShowAllWritingSystemsItem(d, field));

var templateId = ViewDefinitionOverrideEditor.StripRuntimeSuffix(field.StableId);
// Locate the clicked node in the field's OWN compiled model (with any current override
Expand All @@ -601,8 +624,8 @@ private Func<ChoiceBase, UIItemDisplayProperties, DetailMenuItem> BuildOverrideC
}
catch (Exception e)
{
Logger.WriteError("Resolving the field's override target failed; the gear-menu field "
+ "commands fall back to the legacy path for this row.", e);
Logger.WriteError("Resolving the field's override target failed; this row's "
+ "menu-button commands fall back to ordinary command dispatch.", e);
return registry.TryBuild;
}

Expand Down Expand Up @@ -647,12 +670,26 @@ private DetailMenuItem MoveItem(UIItemDisplayProperties display, DetailField fie
execute: canMove ? (Action)(() => ApplyMoveField(field, location, up)) : null);
}

/// <summary>
/// The "Show all right now" item: marks the row's part for the transient reveal (every
/// row of the part shows every writing-system option until the user navigates to another
/// record) and recomposes. The reveal is view state, not a command, so the item
/// dispatches nothing and never writes the override.
/// </summary>
private DetailMenuItem ShowAllWritingSystemsItem(UIItemDisplayProperties display, DetailField field)
=> new DetailMenuItem(XCoreMenuBridge.StripAccelerator(display.Text), isEnabled: true,
isChecked: false, children: null, execute: () =>
{
m_showAllWsFields.Add(ViewDefinitionOverrideEditor.StripRuntimeSuffix(field.StableId));
RefreshAvaloniaDetail();
});

/// <summary>
/// Whether this menu item makes a persistent change to which writing systems a
/// multi-writing-system field shows: a per-writing-system toggle (recognized by the
/// property its group drives -- the toggles carry no command id) or the Configure
/// dialog. Show all right now is excluded: it is a transient reveal on the slice,
/// not a configuration change, so persisting it would wrongly pin the full set.
/// dialog. Show all right now is not one of these: it is the transient reveal
/// (<see cref="ShowAllWritingSystemsItem"/>), not a configuration change to persist.
/// </summary>
private static bool IsWritingSystemVisibilityChoice(ChoiceBase choice)
{
Expand Down Expand Up @@ -742,10 +779,16 @@ private void CopyWritingSystemSelectionToOverride(DetailField field, bool fromLi
if (selected == null || selected.Count == 0)
return;

var templateId = ViewDefinitionOverrideEditor.StripRuntimeSuffix(field.StableId);
var op = new ViewOverrideOperation(ViewOverrideOperationKind.SetVisibleWritingSystems,
ViewDefinitionOverrideEditor.StripRuntimeSuffix(field.StableId),
writingSystems: selected);
MutateOverrideAndRefresh(field, op);
templateId, writingSystems: selected);
if (!TryMutateOverride(field, op))
return;

// A successful configuration write replaces any transient reveal on the part:
// a newly persisted display set supersedes the reveal on every row sharing it.
m_showAllWsFields.Remove(templateId);
RefreshAvaloniaDetail();
}
catch (Exception e)
{
Expand Down Expand Up @@ -781,22 +824,31 @@ private void ApplyMoveField(DetailField field, ViewNodeLocation location, bool u
// Loads-or-creates the (class, layout) override, folds the op in, saves it, and recomposes the
// Avalonia detail view so the change is visible immediately. The legacy DataTree/Inventory is untouched.
private void MutateOverrideAndRefresh(DetailField field, ViewOverrideOperation op)
{
if (TryMutateOverride(field, op))
RefreshAvaloniaDetail();
}

// Folds the op into the (class, layout) override and saves it, WITHOUT recomposing.
// Returns whether the save succeeded, so a failed save can leave view state untouched.
private bool TryMutateOverride(DetailField field, ViewOverrideOperation op)
{
try
{
var store = ViewOverrideStore;
if (store == null)
return;
return false;

var existing = store.TryGet(field.ClassName, field.LayoutName)
?? new ViewDefinitionOverride(field.ClassName, field.LayoutName, "detail", null, null);
var merged = ViewDefinitionOverrideEditor.MergeOperation(existing, op);
store.Save(merged);
RefreshAvaloniaDetail();
return true;
Comment thread
papeh marked this conversation as resolved.
}
catch (Exception e)
{
Logger.WriteError("Applying the field override failed.", e);
return false;
}
}

Expand Down Expand Up @@ -1056,13 +1108,14 @@ private void PersistLabelColumnWidth(double width)
m_propertyTable.SetPropertyPersistence(key, true, PropertyTable.SettingsGroup.LocalSettings);
}

// Re-resolves and re-shows the detail view for the current record from current domain state
// (after an external edit or this view's commit/cancel).
// Re-shows the detail view for the current record (external edit, commit/cancel).
// Resolves the shown record like ShowRecord, so a showDescendantInRoot tool recomposes
// the root, not the subrecord.
private void RefreshAvaloniaDetail()
{
if (m_avaloniaEntryForm == null || !ShouldUseAvaloniaLexiconEdit)
return;
var current = Clerk?.CurrentObject;
var current = ResolveShownRecord(Clerk?.CurrentObject);
if (current == null)
return;

Expand Down
20 changes: 12 additions & 8 deletions Src/xWorks/RecordEditView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,17 @@ protected override void ShowRecord()
ShowRecord(new RecordNavigationInfo(Clerk, Clerk.SuppressSaveOnChangeRecord, false, false));
}

// The record the view shows for a clerk object: a showDescendantInRoot tool displays
// the subrecord's owning root, so every show/refresh path resolves through this.
private ICmObject ResolveShownRecord(ICmObject obj)
{
if (obj == null || !m_showDescendantInRoot)
return obj;
while (obj.Owner != Clerk.OwningObject)
obj = obj.Owner;
return obj;
}

/// <summary>
/// Shows the record on idle. This is where the record is actually shown.
/// </summary>
Expand Down Expand Up @@ -421,14 +432,7 @@ bool ShowRecordOnIdle(object parameter)
}

// Enhance: Maybe do something here to allow changing the templates without the starting the application.
ICmObject obj = Clerk.CurrentObject;

if (m_showDescendantInRoot)
{
// find the root object of the current object
while (obj.Owner != Clerk.OwningObject)
obj = obj.Owner;
}
ICmObject obj = ResolveShownRecord(Clerk.CurrentObject);

if (ShouldUseAvaloniaLexiconEdit && m_avaloniaEntryForm != null)
{
Expand Down
Loading
Loading