|
| 1 | +classdef unitConverterComponent < matlab.ui.componentcontainer.ComponentContainer |
| 2 | + % unitConverterComponent converts input into a standardized unit |
| 3 | + % |
| 4 | + % c = unitConverterComponent(parent) creates a unit converter in the |
| 5 | + % specified parent container. The parent can be a figure or one of its |
| 6 | + % child containers. |
| 7 | + % |
| 8 | + % c = unitConverterComponent(____, 'TargetUnit', Unit) specifies the |
| 9 | + % target unit that the input values are converted to, containing a |
| 10 | + % dropdown and numeric edit field. Use this option with any of the |
| 11 | + % input argument combinations in the previous syntaxes. |
| 12 | + % |
| 13 | + % c = unitConverterComponent(____, Name, Value) creates a unit |
| 14 | + % converter with properties specified by one or more name-value |
| 15 | + % arguments. |
| 16 | + |
| 17 | + % Copyright 2022 The MathWorks, Inc. |
| 18 | + |
| 19 | + properties |
| 20 | + DisplayValue (1,1) {mustBeNumeric} = 0 |
| 21 | + FontSize (1,:) {mustBePositive} = 14 |
| 22 | + FontName (1,:) {mustBeValidFont} = 'Arial' |
| 23 | + FontColor {validatecolor} = [0 0 0] |
| 24 | + TextFieldLayout (1,:) {mustBeMember(TextFieldLayout, {'side-by-side', 'stacked'})} = 'side-by-side' |
| 25 | + end |
| 26 | + |
| 27 | + properties (Dependent) |
| 28 | + Value |
| 29 | + ConversionTable (:,1) {mustBeValidConversionTable} |
| 30 | + DisplayUnit (1,1) string {mustBeTextScalar} |
| 31 | + TargetUnit (1,1) string {mustBeTextScalar} |
| 32 | + end |
| 33 | + |
| 34 | + properties (Access = protected) |
| 35 | + % Use an internal conversion table to store the conversion table, |
| 36 | + % display unit, and target unit. It allows the user to freely |
| 37 | + % change the dependent properties while storing the most recent |
| 38 | + % state of the component. |
| 39 | + InternalConversionTable (:,3) = makeDefaultInternalConversionTable() |
| 40 | + end |
| 41 | + |
| 42 | + events (HasCallbackProperty, NotifyAccess = private) |
| 43 | + ValueChanged |
| 44 | + end |
| 45 | + |
| 46 | + properties (Access = private, Transient, NonCopyable) |
| 47 | + Grid matlab.ui.container.GridLayout |
| 48 | + EditField matlab.ui.control.NumericEditField |
| 49 | + DropDown matlab.ui.control.DropDown |
| 50 | + end |
| 51 | + |
| 52 | + methods(Access = private) |
| 53 | + function valChanged(obj) |
| 54 | + notify(obj, "ValueChanged"); |
| 55 | + end |
| 56 | + end |
| 57 | + |
| 58 | + methods(Static) |
| 59 | + function tbl = makeDefaultConversionTable() |
| 60 | + tbl = makeDefaultConversionTable(); |
| 61 | + end |
| 62 | + end |
| 63 | + |
| 64 | + methods |
| 65 | + function tbl = get.ConversionTable(obj) |
| 66 | + % Conversion table is contained within the row names and first |
| 67 | + % column of the internal table |
| 68 | + tbl = obj.InternalConversionTable(:,1); |
| 69 | + end |
| 70 | + |
| 71 | + function set.ConversionTable(obj, tbl) |
| 72 | + % Input validation |
| 73 | + mustBeValidConversionTable(tbl); |
| 74 | + currentDisplayUnit = obj.DisplayUnit; |
| 75 | + currentTargetUnit = obj.TargetUnit; |
| 76 | + |
| 77 | + % When DisplayUnit isn't present, set it to the first unit |
| 78 | + if sum(strcmp(tbl.Properties.RowNames, currentDisplayUnit)) ~= 1 |
| 79 | + currentDisplayUnit = tbl.Properties.RowNames(1); |
| 80 | + end |
| 81 | + |
| 82 | + % Validate target unit is in the table - if not, set it to the |
| 83 | + % unit with a conversion factor of 1 |
| 84 | + if sum(strcmp(tbl.Properties.RowNames, currentTargetUnit)) ~= 1 |
| 85 | + currentTargetUnit = tbl.Properties.RowNames(tbl{:,1} == 1); |
| 86 | + end |
| 87 | + |
| 88 | + % Update conversion table while also storing the display and |
| 89 | + % target unit |
| 90 | + tbl.DisplayUnit = strcmp(tbl.Properties.RowNames, currentDisplayUnit); |
| 91 | + tbl.TargetUnit = strcmp(tbl.Properties.RowNames, currentTargetUnit); |
| 92 | + obj.InternalConversionTable = tbl; |
| 93 | + |
| 94 | + % Update dropdown with the new units and the display unit |
| 95 | + obj.DropDown.Items = tbl.Properties.RowNames; |
| 96 | + obj.DropDown.Value = currentDisplayUnit; |
| 97 | + end |
| 98 | + |
| 99 | + function set.FontColor(obj, color) |
| 100 | + obj.FontColor = validatecolor(color); |
| 101 | + end |
| 102 | + |
| 103 | + function set.FontName(obj, font) |
| 104 | + obj.FontName = mustBeValidFont(font); |
| 105 | + end |
| 106 | + |
| 107 | + function tbl = get.InternalConversionTable(obj) |
| 108 | + tbl = obj.InternalConversionTable; |
| 109 | + |
| 110 | + % Update internal table with the display unit |
| 111 | + tbl.DisplayUnit = ... |
| 112 | + strcmp(tbl.Properties.RowNames, obj.DisplayUnit); |
| 113 | + end |
| 114 | + |
| 115 | + function set.DisplayUnit(obj, val) |
| 116 | + mustBeValidUnit(val, obj.ConversionTable); |
| 117 | + obj.DropDown.Value = val; |
| 118 | + end |
| 119 | + |
| 120 | + function val = get.DisplayUnit(obj) |
| 121 | + val = obj.DropDown.Value; |
| 122 | + end |
| 123 | + |
| 124 | + function set.TargetUnit(obj, val) |
| 125 | + % Allow uppercase target units to be accepted |
| 126 | + val = lower(val); |
| 127 | + mustBeValidUnit(val, obj.ConversionTable); |
| 128 | + |
| 129 | + % Update internal table with the new target unit |
| 130 | + obj.InternalConversionTable.TargetUnit = ... |
| 131 | + strcmp(obj.InternalConversionTable.Properties.RowNames, val); |
| 132 | + end |
| 133 | + |
| 134 | + function val = get.TargetUnit(obj) |
| 135 | + val = obj.InternalConversionTable.Properties.RowNames(... |
| 136 | + obj.InternalConversionTable.TargetUnit); |
| 137 | + end |
| 138 | + |
| 139 | + function set.DisplayValue(obj, val) |
| 140 | + obj.EditField.Value = val; %#ok<MCSUP> |
| 141 | + end |
| 142 | + |
| 143 | + function val = get.DisplayValue(obj) |
| 144 | + val = obj.EditField.Value; |
| 145 | + end |
| 146 | + |
| 147 | + function set.Value(obj, val) |
| 148 | + % Convert from the target unit to the display unit |
| 149 | + obj.DisplayValue = convertUnit(obj.ConversionTable, val, obj.TargetUnit, obj.DisplayUnit); |
| 150 | + end |
| 151 | + |
| 152 | + function val = get.Value(obj) |
| 153 | + % Convert to the standard unit (default is meters), then to the target unit |
| 154 | + val = convertUnit(obj.ConversionTable, obj.DisplayValue, obj.DisplayUnit, obj.TargetUnit); |
| 155 | + end |
| 156 | + end |
| 157 | + |
| 158 | + methods(Access = protected) |
| 159 | + function setup(obj) |
| 160 | + % Create UI components |
| 161 | + obj.Grid = uigridlayout(obj, 'Padding', 0); |
| 162 | + obj.EditField = uieditfield(obj.Grid,'numeric', ... |
| 163 | + 'HorizontalAlignment', 'center'); |
| 164 | + obj.EditField.ValueChangedFcn = @(~, ~) obj.valChanged; |
| 165 | + |
| 166 | + tbl = makeDefaultConversionTable(); |
| 167 | + obj.DropDown = uidropdown(obj.Grid, ... |
| 168 | + 'Editable', 'on', ... |
| 169 | + 'Items', tbl.Properties.RowNames); |
| 170 | + obj.DropDown.ValueChangedFcn = @(~, ~) obj.valChanged; |
| 171 | + end |
| 172 | + |
| 173 | + function update(obj) |
| 174 | + % Update the stylistic properties for the UI components |
| 175 | + obj.Grid.BackgroundColor = obj.BackgroundColor; |
| 176 | + obj.DropDown.FontName = obj.FontName; |
| 177 | + obj.EditField.FontName = obj.FontName; |
| 178 | + obj.DropDown.FontColor = obj.FontColor; |
| 179 | + obj.EditField.FontColor = obj.FontColor; |
| 180 | + obj.DropDown.FontSize = obj.FontSize; |
| 181 | + obj.EditField.FontSize = obj.FontSize; |
| 182 | + |
| 183 | + % Update layout of UI components |
| 184 | + if strcmp(obj.TextFieldLayout, 'side-by-side') |
| 185 | + obj.Position(3:4) = [150 25]; |
| 186 | + obj.Grid.RowHeight = {'fit'}; |
| 187 | + obj.Grid.ColumnWidth = {'1x','fit'}; |
| 188 | + obj.DropDown.Layout.Row = 1; |
| 189 | + obj.DropDown.Layout.Column = 2; |
| 190 | + elseif strcmp(obj.TextFieldLayout, 'stacked') |
| 191 | + obj.Position(3:4) = [100 60]; |
| 192 | + obj.Grid.RowHeight = {'fit','fit'}; |
| 193 | + obj.Grid.ColumnWidth = {'1x'}; |
| 194 | + obj.DropDown.Layout.Row = 2; |
| 195 | + obj.DropDown.Layout.Column = 1; |
| 196 | + end |
| 197 | + end |
| 198 | + end |
| 199 | +end |
| 200 | + |
| 201 | +function unitTable = makeDefaultConversionTable() |
| 202 | + unit = [ |
| 203 | + "mile"; |
| 204 | + "foot"; |
| 205 | + "inch"; |
| 206 | + "meter"; |
| 207 | + "yard" |
| 208 | + ]; |
| 209 | + |
| 210 | + % Taken from wikipedia: |
| 211 | + % https://en.wikipedia.org/wiki/Conversion_of_units#Length |
| 212 | + ConversionFactors = [ |
| 213 | + 1609.344; |
| 214 | + 0.3048; |
| 215 | + 0.0254; |
| 216 | + 1; |
| 217 | + 0.9144; |
| 218 | + ]; |
| 219 | + |
| 220 | + unitTable = table(ConversionFactors, 'RowNames', unit); |
| 221 | +end |
| 222 | + |
| 223 | +function tbl = makeDefaultInternalConversionTable() |
| 224 | + t = makeDefaultConversionTable(); |
| 225 | + t.DisplayUnit = strcmp(t.Properties.RowNames, 'mile'); |
| 226 | + t.TargetUnit = t.ConversionFactors == 1; |
| 227 | + tbl = t; |
| 228 | +end |
| 229 | + |
| 230 | + |
| 231 | +function mustBeValidConversionTable(tbl) |
| 232 | + if isempty(tbl.Properties.RowNames) |
| 233 | + error("Conversion table must have row names."); |
| 234 | + elseif width(tbl) ~= 1 |
| 235 | + error("Conversion table must have one column containing the conversion factors.") |
| 236 | + elseif ~isnumeric(tbl{:,1}) |
| 237 | + error("Conversion factors must all be numeric."); |
| 238 | + elseif ~any(tbl{:,1} == 1) |
| 239 | + error("Standard unit must be in conversion table with a conversion factor of 1.") |
| 240 | + end |
| 241 | +end |
| 242 | + |
| 243 | +function font = mustBeValidFont(font) |
| 244 | + font = validatestring(font, listfonts); |
| 245 | +end |
| 246 | + |
| 247 | +function mustBeValidUnit(unit, tbl) |
| 248 | + mustBeMember(unit, tbl.Properties.RowNames); |
| 249 | +end |
| 250 | + |
| 251 | +function val = convertUnit(tbl, fromValue, fromUnit, toUnit) |
| 252 | + factor = tbl{fromUnit, :}; |
| 253 | + defaultVal = fromValue * factor; |
| 254 | + val = defaultVal / (tbl{toUnit, :}); |
| 255 | +end |
0 commit comments