]> granicus.if.org Git - handbrake/commitdiff
MacGui: rewrote the subtitles tab to use bindings, view-based table view and to suppo...
authorDamiano Galassi <damiog@gmail.com>
Thu, 22 Oct 2015 16:52:18 +0000 (18:52 +0200)
committerDamiano Galassi <damiog@gmail.com>
Thu, 22 Oct 2015 16:52:18 +0000 (18:52 +0200)
12 files changed:
macosx/English.lproj/Subtitles.xib
macosx/HBJob+HBJobConversion.m
macosx/HBJob+UIAdditions.m
macosx/HBJob.m
macosx/HBSubtitles.h
macosx/HBSubtitles.m
macosx/HBSubtitlesController.m
macosx/HBSubtitlesTrack.h [new file with mode: 0644]
macosx/HBSubtitlesTrack.m [new file with mode: 0644]
macosx/HBTitle.m
macosx/HandBrake Tests/HBMockTitle.m
macosx/HandBrake.xcodeproj/project.pbxproj

index 5d2f05802e19c4638380f30a6ebac96eddd7d099..bbc7f7275f5ec164237aab2fd6db9d0cf02bf684 100644 (file)
@@ -1,14 +1,13 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="8164.2" systemVersion="15A225f" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9059" systemVersion="15B42" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
     <dependencies>
         <deployment identifier="macosx"/>
         <development version="6300" identifier="xcode"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="8164.2"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9059"/>
     </dependencies>
     <objects>
         <customObject id="-2" userLabel="File's Owner" customClass="HBSubtitlesController">
             <connections>
-                <outlet property="fTableView" destination="0yM-wE-D2x" id="0vq-y5-Ubi"/>
                 <outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
             </connections>
         </customObject>
             <rect key="frame" x="0.0" y="0.0" width="926" height="322"/>
             <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
             <subviews>
+                <popUpButton verticalHuggingPriority="750" id="2Tb-KC-Ugi">
+                    <rect key="frame" x="17" y="291" width="88" height="22"/>
+                    <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+                    <animations/>
+                    <popUpButtonCell key="cell" type="push" title="Track" bezelStyle="rounded" alignment="left" controlSize="small" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" pullsDown="YES" id="8ZD-D6-TLA">
+                        <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
+                        <font key="font" metaFont="smallSystem"/>
+                        <menu key="menu" title="OtherViews" id="Fr7-eG-NuL">
+                            <items>
+                                <menuItem title="Track" state="on" hidden="YES" id="TJO-RZ-jgb"/>
+                                <menuItem title="Add All" id="4PX-In-DpF">
+                                    <connections>
+                                        <action selector="addAll:" target="-2" id="tuS-uF-dje"/>
+                                    </connections>
+                                </menuItem>
+                                <menuItem title="Add External SRT…" toolTip="Add new SRT subtitle to the list." id="HW0-PS-t0U">
+                                    <connections>
+                                        <action selector="browseImportSrtFile:" target="-2" id="dog-BP-my4"/>
+                                    </connections>
+                                </menuItem>
+                                <menuItem isSeparatorItem="YES" id="2hO-bG-5qB"/>
+                                <menuItem title="Remove All" id="mVi-zH-KUq">
+                                    <connections>
+                                        <action selector="removeAll:" target="-2" id="QsA-lb-0rD"/>
+                                    </connections>
+                                </menuItem>
+                            </items>
+                        </menu>
+                    </popUpButtonCell>
+                    <connections>
+                        <binding destination="-2" name="enabled" keyPath="self.subtitles" id="khN-Sn-dnI">
+                            <dictionary key="options">
+                                <string key="NSValueTransformerName">NSIsNotNil</string>
+                            </dictionary>
+                        </binding>
+                    </connections>
+                </popUpButton>
+                <button verticalHuggingPriority="750" id="QsM-28-Pya">
+                    <rect key="frame" x="110" y="288" width="140" height="28"/>
+                    <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+                    <animations/>
+                    <buttonCell key="cell" type="push" title="Configure Defaults…" bezelStyle="rounded" alignment="center" controlSize="small" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="oxg-bs-1si">
+                        <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+                        <font key="font" metaFont="smallSystem"/>
+                    </buttonCell>
+                    <connections>
+                        <action selector="showSettingsSheet:" target="-2" id="OAA-S8-tfS"/>
+                        <binding destination="-2" name="enabled" keyPath="self.subtitles" id="dpe-kM-iMF">
+                            <dictionary key="options">
+                                <string key="NSValueTransformerName">NSIsNotNil</string>
+                            </dictionary>
+                        </binding>
+                    </connections>
+                </button>
+                <button verticalHuggingPriority="750" id="Vxx-gk-9kY">
+                    <rect key="frame" x="248" y="288" width="116" height="28"/>
+                    <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+                    <animations/>
+                    <buttonCell key="cell" type="push" title="Reload Defaults" bezelStyle="rounded" alignment="center" controlSize="small" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="jG8-uo-1tv">
+                        <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+                        <font key="font" metaFont="smallSystem"/>
+                    </buttonCell>
+                    <connections>
+                        <action selector="addTracksFromDefaults:" target="-2" id="GOz-FT-Atg"/>
+                        <binding destination="-2" name="enabled" keyPath="self.subtitles" id="CSa-3s-GD6">
+                            <dictionary key="options">
+                                <string key="NSValueTransformerName">NSIsNotNil</string>
+                            </dictionary>
+                        </binding>
+                    </connections>
+                </button>
                 <scrollView autohidesScrollers="YES" horizontalLineScroll="27" horizontalPageScroll="10" verticalLineScroll="27" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" id="Syo-rH-vof">
                     <rect key="frame" x="20" y="20" width="886" height="266"/>
                     <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@@ -25,7 +95,7 @@
                         <rect key="frame" x="1" y="17" width="884" height="248"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <subviews>
-                            <tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="firstColumnOnly" columnReordering="NO" multipleSelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="25" headerView="IiW-3a-Drv" id="0yM-wE-D2x">
+                            <tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="firstColumnOnly" columnReordering="NO" multipleSelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="25" rowSizeStyle="automatic" headerView="IiW-3a-Drv" viewBased="YES" id="0yM-wE-D2x">
                                 <rect key="frame" x="0.0" y="0.0" width="884" height="248"/>
                                 <autoresizingMask key="autoresizingMask"/>
                                 <animations/>
                                             </menu>
                                         </popUpButtonCell>
                                         <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
+                                        <prototypeCellViews>
+                                            <tableCellView id="EZp-rc-7mo">
+                                                <rect key="frame" x="1" y="1" width="322" height="25"/>
+                                                <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                                                <subviews>
+                                                    <popUpButton verticalHuggingPriority="750" id="Tf3-cP-TGw">
+                                                        <rect key="frame" x="0.0" y="1" width="322" height="22"/>
+                                                        <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
+                                                        <animations/>
+                                                        <popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" controlSize="small" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="JWk-Ab-hdY">
+                                                            <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
+                                                            <font key="font" metaFont="smallSystem"/>
+                                                            <menu key="menu" id="0GR-vf-sZq">
+                                                                <items>
+                                                                    <menuItem title="Item 1" id="yDg-DK-z5D"/>
+                                                                    <menuItem title="Item 2" id="g4b-FM-qX4"/>
+                                                                    <menuItem title="Item 3" id="fAW-To-EeP"/>
+                                                                </items>
+                                                            </menu>
+                                                        </popUpButtonCell>
+                                                        <connections>
+                                                            <binding destination="EZp-rc-7mo" name="selectedIndex" keyPath="objectValue.sourceTrackIdx" previousBinding="LeD-lR-MOf" id="zso-1b-OrL"/>
+                                                            <binding destination="EZp-rc-7mo" name="content" keyPath="objectValue.sourceTracksArray" id="LeD-lR-MOf"/>
+                                                        </connections>
+                                                    </popUpButton>
+                                                </subviews>
+                                                <animations/>
+                                            </tableCellView>
+                                        </prototypeCellViews>
                                     </tableColumn>
                                     <tableColumn identifier="forced" width="76" minWidth="10" maxWidth="3.4028229999999999e+38" id="klV-Gy-igk">
                                         <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="center" title="Forced Only">
                                             <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
                                             <font key="font" metaFont="smallSystem"/>
                                         </buttonCell>
+                                        <prototypeCellViews>
+                                            <tableCellView id="zKg-by-KFV">
+                                                <rect key="frame" x="326" y="1" width="76" height="25"/>
+                                                <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                                                <subviews>
+                                                    <button id="HC5-ql-Vcr">
+                                                        <rect key="frame" x="26" y="3" width="23" height="20"/>
+                                                        <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+                                                        <animations/>
+                                                        <buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" controlSize="small" state="on" inset="2" id="SPR-9q-XBK">
+                                                            <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+                                                            <font key="font" metaFont="smallSystem"/>
+                                                        </buttonCell>
+                                                        <connections>
+                                                            <binding destination="zKg-by-KFV" name="value" keyPath="objectValue.forcedOnly" id="iFe-SC-lu3"/>
+                                                            <binding destination="zKg-by-KFV" name="enabled" keyPath="objectValue.isEnabled" id="wpB-JZ-Upd"/>
+                                                        </connections>
+                                                    </button>
+                                                </subviews>
+                                                <animations/>
+                                            </tableCellView>
+                                        </prototypeCellViews>
                                     </tableColumn>
                                     <tableColumn identifier="burned" width="64" minWidth="10" maxWidth="3.4028229999999999e+38" id="fIe-Fg-ufj">
                                         <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="center" title="Burned In">
                                             <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
                                             <font key="font" metaFont="smallSystem"/>
                                         </buttonCell>
+                                        <prototypeCellViews>
+                                            <tableCellView id="gxH-yz-YRK">
+                                                <rect key="frame" x="405" y="1" width="64" height="25"/>
+                                                <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                                                <subviews>
+                                                    <button id="zpm-9Z-Hsq">
+                                                        <rect key="frame" x="20" y="3" width="23" height="20"/>
+                                                        <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+                                                        <animations/>
+                                                        <buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" controlSize="small" state="on" inset="2" id="s2E-1o-mGs">
+                                                            <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+                                                            <font key="font" metaFont="smallSystem"/>
+                                                        </buttonCell>
+                                                        <connections>
+                                                            <binding destination="gxH-yz-YRK" name="value" keyPath="objectValue.burnedIn" id="reR-Z6-yBf">
+                                                                <dictionary key="options">
+                                                                    <bool key="NSValidatesImmediately" value="YES"/>
+                                                                </dictionary>
+                                                            </binding>
+                                                            <binding destination="gxH-yz-YRK" name="enabled" keyPath="objectValue.canPassthru" id="xVE-oI-pP8"/>
+                                                        </connections>
+                                                    </button>
+                                                </subviews>
+                                                <animations/>
+                                            </tableCellView>
+                                        </prototypeCellViews>
                                     </tableColumn>
                                     <tableColumn identifier="default" width="51" minWidth="10" maxWidth="3.4028229999999999e+38" id="fvq-pE-sOC">
                                         <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="center" title="Default">
                                             <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
                                             <font key="font" metaFont="smallSystem"/>
                                         </buttonCell>
+                                        <prototypeCellViews>
+                                            <tableCellView id="mQ6-s6-Zm2">
+                                                <rect key="frame" x="472" y="1" width="51" height="25"/>
+                                                <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                                                <subviews>
+                                                    <button id="mdO-Qu-3Pb">
+                                                        <rect key="frame" x="14" y="4" width="22" height="18"/>
+                                                        <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+                                                        <animations/>
+                                                        <buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="left" controlSize="small" state="on" inset="2" id="kwl-qH-Dgo">
+                                                            <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+                                                            <font key="font" metaFont="smallSystem"/>
+                                                        </buttonCell>
+                                                        <connections>
+                                                            <binding destination="mQ6-s6-Zm2" name="enabled" keyPath="objectValue.canPassthru" id="VIq-6A-MlG"/>
+                                                            <binding destination="mQ6-s6-Zm2" name="value" keyPath="objectValue.def" id="6sg-s1-xTn"/>
+                                                        </connections>
+                                                    </button>
+                                                </subviews>
+                                                <animations/>
+                                            </tableCellView>
+                                        </prototypeCellViews>
                                     </tableColumn>
                                     <tableColumn identifier="srt_lang" width="173" minWidth="10" maxWidth="3.4028229999999999e+38" id="9ka-9O-WDj">
                                         <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Srt Language">
                                             </menu>
                                         </popUpButtonCell>
                                         <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
+                                        <prototypeCellViews>
+                                            <tableCellView id="4bh-my-GeM">
+                                                <rect key="frame" x="526" y="1" width="173" height="25"/>
+                                                <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                                                <subviews>
+                                                    <popUpButton verticalHuggingPriority="750" id="Inz-O5-B8g">
+                                                        <rect key="frame" x="0.0" y="0.0" width="173" height="22"/>
+                                                        <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
+                                                        <animations/>
+                                                        <popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" controlSize="small" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="zIn-sc-xak">
+                                                            <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
+                                                            <font key="font" metaFont="smallSystem"/>
+                                                            <menu key="menu" id="YpS-Pf-abd">
+                                                                <items>
+                                                                    <menuItem title="Item 1" id="Rj8-KI-k4L"/>
+                                                                    <menuItem title="Item 2" id="HQq-Mp-4sI"/>
+                                                                    <menuItem title="Item 3" id="yEC-jb-q8b"/>
+                                                                </items>
+                                                            </menu>
+                                                        </popUpButtonCell>
+                                                        <connections>
+                                                            <binding destination="4bh-my-GeM" name="enabled" keyPath="objectValue.isSrt" id="p69-lt-dgE"/>
+                                                            <binding destination="4bh-my-GeM" name="selectedValue" keyPath="objectValue.isoLanguage" previousBinding="OIo-fF-3vU" id="Ubv-kn-nf5">
+                                                                <dictionary key="options">
+                                                                    <string key="NSValueTransformerName">HBIsoLanguageTrasformer</string>
+                                                                </dictionary>
+                                                            </binding>
+                                                            <binding destination="4bh-my-GeM" name="content" keyPath="objectValue.languages" id="OIo-fF-3vU"/>
+                                                        </connections>
+                                                    </popUpButton>
+                                                </subviews>
+                                                <animations/>
+                                            </tableCellView>
+                                        </prototypeCellViews>
                                     </tableColumn>
                                     <tableColumn identifier="srt_charcode" width="113" minWidth="10" maxWidth="3.4028229999999999e+38" id="1Qg-We-ltR">
                                         <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Srt Char Code">
                                             <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
                                             <color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
                                         </tableHeaderCell>
-                                        <popUpButtonCell key="dataCell" type="bevel" title="Pop Up" bezelStyle="rounded" alignment="left" controlSize="small" lineBreakMode="truncatingTail" continuous="YES" state="on" borderStyle="bezel" imageScaling="proportionallyDown" inset="2" arrowPosition="arrowAtCenter" preferredEdge="maxY" selectedItem="xt1-a1-JhF" id="AdJ-Mv-JI2">
+                                        <popUpButtonCell key="dataCell" type="bevel" bezelStyle="rounded" alignment="left" controlSize="small" lineBreakMode="truncatingTail" continuous="YES" borderStyle="bezel" imageScaling="proportionallyDown" inset="2" arrowPosition="arrowAtCenter" preferredEdge="maxY" id="AdJ-Mv-JI2">
                                             <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
                                             <font key="font" metaFont="smallSystem"/>
-                                            <menu key="menu" title="OtherViews" id="ABd-Ec-K2L">
-                                                <items>
-                                                    <menuItem title="Pop Up" state="on" id="xt1-a1-JhF"/>
-                                                </items>
-                                            </menu>
+                                            <menu key="menu" title="OtherViews" id="ABd-Ec-K2L"/>
                                         </popUpButtonCell>
                                         <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
+                                        <prototypeCellViews>
+                                            <tableCellView id="ZBK-cc-m0z">
+                                                <rect key="frame" x="702" y="1" width="113" height="25"/>
+                                                <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                                                <subviews>
+                                                    <popUpButton verticalHuggingPriority="750" id="QV0-kE-4yR">
+                                                        <rect key="frame" x="0.0" y="0.0" width="113" height="22"/>
+                                                        <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
+                                                        <animations/>
+                                                        <popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" controlSize="small" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="jFr-2c-3Vv">
+                                                            <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
+                                                            <font key="font" metaFont="smallSystem"/>
+                                                            <menu key="menu" id="u4x-JX-Hma">
+                                                                <items>
+                                                                    <menuItem title="Item 1" id="dap-ee-kor"/>
+                                                                    <menuItem title="Item 2" id="gHY-6m-ASb"/>
+                                                                    <menuItem title="Item 3" id="ThR-Cn-Vfr"/>
+                                                                </items>
+                                                            </menu>
+                                                        </popUpButtonCell>
+                                                        <connections>
+                                                            <binding destination="ZBK-cc-m0z" name="selectedValue" keyPath="objectValue.charCode" previousBinding="NGt-sB-Sg2" id="eBc-20-eSF"/>
+                                                            <binding destination="ZBK-cc-m0z" name="content" keyPath="objectValue.encodings" id="NGt-sB-Sg2"/>
+                                                            <binding destination="ZBK-cc-m0z" name="enabled" keyPath="objectValue.isSrt" id="ZIt-gt-iqy"/>
+                                                        </connections>
+                                                    </popUpButton>
+                                                </subviews>
+                                                <animations/>
+                                            </tableCellView>
+                                        </prototypeCellViews>
                                     </tableColumn>
                                     <tableColumn identifier="srt_offset" width="64" minWidth="10" maxWidth="3.4028229999999999e+38" id="Fgh-pZ-6uu">
                                         <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Srt Offset">
                                             <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
                                         </textFieldCell>
                                         <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
+                                        <prototypeCellViews>
+                                            <tableCellView id="9tV-iL-oaF">
+                                                <rect key="frame" x="818" y="1" width="64" height="17"/>
+                                                <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                                                <subviews>
+                                                    <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" id="aJi-zQ-0cg">
+                                                        <rect key="frame" x="0.0" y="-3" width="64" height="14"/>
+                                                        <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
+                                                        <animations/>
+                                                        <textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingTail" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" alignment="center" title="0" id="hhH-c3-gD0">
+                                                            <numberFormatter key="formatter" formatterBehavior="default10_4" numberStyle="decimal" minimumIntegerDigits="1" maximumIntegerDigits="2000000000" maximumFractionDigits="3" id="Jui-oB-oVO"/>
+                                                            <font key="font" metaFont="smallSystem"/>
+                                                            <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
+                                                            <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+                                                        </textFieldCell>
+                                                        <connections>
+                                                            <binding destination="9tV-iL-oaF" name="enabled" keyPath="objectValue.isSrt" id="noe-Kh-7pa"/>
+                                                            <binding destination="9tV-iL-oaF" name="value" keyPath="objectValue.offset" id="NlM-1C-Udf">
+                                                                <dictionary key="options">
+                                                                    <integer key="NSNullPlaceholder" value="0"/>
+                                                                </dictionary>
+                                                            </binding>
+                                                        </connections>
+                                                    </textField>
+                                                </subviews>
+                                                <animations/>
+                                                <connections>
+                                                    <outlet property="textField" destination="aJi-zQ-0cg" id="WSP-oS-jCs"/>
+                                                </connections>
+                                            </tableCellView>
+                                        </prototypeCellViews>
                                     </tableColumn>
                                 </tableColumns>
                                 <connections>
+                                    <binding destination="ssY-jQ-Nev" name="content" keyPath="arrangedObjects" id="Oj3-29-oCf"/>
                                     <binding destination="-2" name="enabled" keyPath="self.subtitles" id="pmZ-qP-DnG">
                                         <dictionary key="options">
                                             <string key="NSValueTransformerName">NSIsNotNil</string>
                                         </dictionary>
                                     </binding>
-                                    <outlet property="dataSource" destination="-2" id="DH5-v0-2ba"/>
-                                    <outlet property="delegate" destination="-2" id="Dfm-tw-89m"/>
                                     <outlet property="menu" destination="KgC-dn-Hq2" id="Iff-7t-kdg"/>
                                 </connections>
                             </tableView>
                         <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
                     </clipView>
                     <animations/>
-                    <scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="toe-s0-pVk">
+                    <scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="toe-s0-pVk">
                         <rect key="frame" x="-100" y="-100" width="685" height="15"/>
                         <autoresizingMask key="autoresizingMask"/>
                         <animations/>
                     </scroller>
-                    <scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="GfM-TU-Tmr">
+                    <scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="GfM-TU-Tmr">
                         <autoresizingMask key="autoresizingMask"/>
                         <animations/>
                     </scroller>
                         <animations/>
                     </tableHeaderView>
                 </scrollView>
-                <popUpButton verticalHuggingPriority="750" id="2Tb-KC-Ugi">
-                    <rect key="frame" x="17" y="291" width="88" height="22"/>
-                    <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
-                    <animations/>
-                    <popUpButtonCell key="cell" type="push" title="Track" bezelStyle="rounded" alignment="left" controlSize="small" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" pullsDown="YES" id="8ZD-D6-TLA">
-                        <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
-                        <font key="font" metaFont="smallSystem"/>
-                        <menu key="menu" title="OtherViews" id="Fr7-eG-NuL">
-                            <items>
-                                <menuItem title="Track" state="on" hidden="YES" id="TJO-RZ-jgb"/>
-                                <menuItem title="Add All" id="4PX-In-DpF">
-                                    <connections>
-                                        <action selector="addAll:" target="-2" id="tuS-uF-dje"/>
-                                    </connections>
-                                </menuItem>
-                                <menuItem title="Add External SRT…" toolTip="Add new SRT subtitle to the list." id="HW0-PS-t0U">
-                                    <connections>
-                                        <action selector="browseImportSrtFile:" target="-2" id="dog-BP-my4"/>
-                                    </connections>
-                                </menuItem>
-                                <menuItem isSeparatorItem="YES" id="2hO-bG-5qB"/>
-                                <menuItem title="Remove All" id="mVi-zH-KUq">
-                                    <connections>
-                                        <action selector="removeAll:" target="-2" id="QsA-lb-0rD"/>
-                                    </connections>
-                                </menuItem>
-                            </items>
-                        </menu>
-                    </popUpButtonCell>
-                    <connections>
-                        <binding destination="-2" name="enabled" keyPath="self.subtitles" id="khN-Sn-dnI">
-                            <dictionary key="options">
-                                <string key="NSValueTransformerName">NSIsNotNil</string>
-                            </dictionary>
-                        </binding>
-                    </connections>
-                </popUpButton>
-                <button verticalHuggingPriority="750" id="QsM-28-Pya">
-                    <rect key="frame" x="110" y="288" width="140" height="28"/>
-                    <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
-                    <animations/>
-                    <buttonCell key="cell" type="push" title="Configure Defaults…" bezelStyle="rounded" alignment="center" controlSize="small" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="oxg-bs-1si">
-                        <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
-                        <font key="font" metaFont="smallSystem"/>
-                    </buttonCell>
-                    <connections>
-                        <action selector="showSettingsSheet:" target="-2" id="OAA-S8-tfS"/>
-                        <binding destination="-2" name="enabled" keyPath="self.subtitles" id="dpe-kM-iMF">
-                            <dictionary key="options">
-                                <string key="NSValueTransformerName">NSIsNotNil</string>
-                            </dictionary>
-                        </binding>
-                    </connections>
-                </button>
-                <button verticalHuggingPriority="750" id="Vxx-gk-9kY">
-                    <rect key="frame" x="248" y="288" width="116" height="28"/>
-                    <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
-                    <animations/>
-                    <buttonCell key="cell" type="push" title="Reload Defaults" bezelStyle="rounded" alignment="center" controlSize="small" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="jG8-uo-1tv">
-                        <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
-                        <font key="font" metaFont="smallSystem"/>
-                    </buttonCell>
-                    <connections>
-                        <action selector="addTracksFromDefaults:" target="-2" id="GOz-FT-Atg"/>
-                        <binding destination="-2" name="enabled" keyPath="self.subtitles" id="CSa-3s-GD6">
-                            <dictionary key="options">
-                                <string key="NSValueTransformerName">NSIsNotNil</string>
-                            </dictionary>
-                        </binding>
-                    </connections>
-                </button>
             </subviews>
             <animations/>
-            <point key="canvasLocation" x="373" y="444"/>
+            <point key="canvasLocation" x="532" y="403"/>
         </customView>
+        <arrayController objectClassName="HBSubtitlesTrack" id="ssY-jQ-Nev">
+            <connections>
+                <binding destination="-2" name="contentArray" keyPath="self.subtitles.tracks" id="mlB-F3-p89"/>
+            </connections>
+        </arrayController>
         <menu id="KgC-dn-Hq2">
             <items>
                 <menuItem title="Add All" id="S2I-Jd-Lyg">
index b194e86d85e1e29b7fed063d3809c1912ff8d0c6..4ed4e4390de758a03538c52cfa0d357bc36a5bda 100644 (file)
@@ -9,6 +9,8 @@
 #import "HBAudioDefaults.h"
 #import "HBAudioTrack.h"
 
+#import "HBSubtitlesTrack.h"
+
 #import "HBChapter.h"
 
 #import "HBTitlePrivate.h"
 
     // Map the settings in the dictionaries for the SubtitleList array to match title->list_subtitle
     BOOL one_burned = NO;
-    for (NSDictionary *subtitleDict in self.subtitles.tracks)
+    for (HBSubtitlesTrack *subTrack in self.subtitles.tracks)
     {
-        int subtitle = [subtitleDict[keySubTrackIndex] intValue];
-        BOOL force = [subtitleDict[keySubTrackForced] boolValue];
-        BOOL burned = [subtitleDict[keySubTrackBurned] boolValue];
-        BOOL def = [subtitleDict[keySubTrackDefault] boolValue];
-
-        // Skip the "None" track.
-        if (subtitle == -2)
-        {
-            continue;
-        }
-
-        // we need to check for the "Foreign Audio Search" which would be keySubTrackIndex of -1
-        if (subtitle == -1)
+        if (subTrack.isEnabled)
         {
-            job->indepth_scan = 1;
+            // Shift the source index by 2 to componsate
+            // for the none and foreign audio search tracks.
+            int sourceIdx = ((int)subTrack.sourceTrackIdx) - 2;
 
-            if (burned != 1)
+            // we need to check for the "Foreign Audio Search" which would be have an index of -1
+            if (sourceIdx == -1)
             {
-                job->select_subtitle_config.dest = PASSTHRUSUB;
-            }
-            else
-            {
-                job->select_subtitle_config.dest = RENDERSUB;
-            }
+                job->indepth_scan = 1;
 
-            job->select_subtitle_config.force = force;
-            job->select_subtitle_config.default_track = def;
-        }
-        else
-        {
-            // if we are getting the subtitles from an external srt file
-            if ([subtitleDict[keySubTrackType] intValue] == SRTSUB)
-            {
-                hb_subtitle_config_t sub_config;
-
-                sub_config.offset = [subtitleDict[keySubTrackSrtOffset] intValue];
-
-                // we need to strncpy file name and codeset
-                strncpy(sub_config.src_filename, [subtitleDict[keySubTrackSrtFilePath] UTF8String], 255);
-                sub_config.src_filename[255] = 0;
-                strncpy(sub_config.src_codeset, [subtitleDict[keySubTrackSrtCharCode] UTF8String], 39);
-                sub_config.src_codeset[39] = 0;
-
-                if (!burned && hb_subtitle_can_pass(SRTSUB, job->mux))
+                if (subTrack.burnedIn)
                 {
-                    sub_config.dest = PASSTHRUSUB;
+                    job->select_subtitle_config.dest = RENDERSUB;
                 }
-                else if (hb_subtitle_can_burn(SRTSUB))
+                else
                 {
-                    // Only allow one subtitle to be burned into the video
-                    if (one_burned)
-                        continue;
-                    one_burned = YES;
-                    sub_config.dest = RENDERSUB;
+                    job->select_subtitle_config.dest = PASSTHRUSUB;
                 }
 
-                sub_config.force = 0;
-                sub_config.default_track = def;
-                hb_srt_add( job, &sub_config, [subtitleDict[keySubTrackLanguageIsoCode] UTF8String]);
-                continue;
+                job->select_subtitle_config.force = subTrack.forcedOnly;
+                job->select_subtitle_config.default_track = subTrack.def;
             }
-
-            // We are setting a source subtitle so access the source subtitle info
-            hb_subtitle_t * subt = (hb_subtitle_t *) hb_list_item(title->list_subtitle, subtitle);
-
-            if (subt != NULL)
+            else
             {
-                hb_subtitle_config_t sub_config = subt->config;
-
-                if (!burned && hb_subtitle_can_pass(subt->source, job->mux))
+                // if we are getting the subtitles from an external srt file
+                if (subTrack.type == SRTSUB)
                 {
-                    sub_config.dest = PASSTHRUSUB;
+                    hb_subtitle_config_t sub_config;
+
+                    sub_config.offset = subTrack.offset;
+
+                    // we need to strncpy file name and codeset
+                    strncpy(sub_config.src_filename, subTrack.fileURL.path.fileSystemRepresentation, 255);
+                    sub_config.src_filename[255] = 0;
+                    strncpy(sub_config.src_codeset, subTrack.charCode.UTF8String, 39);
+                    sub_config.src_codeset[39] = 0;
+
+                    if (!subTrack.burnedIn && hb_subtitle_can_pass(SRTSUB, job->mux))
+                    {
+                        sub_config.dest = PASSTHRUSUB;
+                    }
+                    else if (hb_subtitle_can_burn(SRTSUB))
+                    {
+                        one_burned = YES;
+                        sub_config.dest = RENDERSUB;
+                    }
+
+                    sub_config.force = 0;
+                    sub_config.default_track = subTrack.def;
+                    hb_srt_add( job, &sub_config, subTrack.isoLanguage.UTF8String);
                 }
-                else if (hb_subtitle_can_burn(subt->source))
+                else
                 {
-                    // Only allow one subtitle to be burned into the video
-                    if (one_burned)
-                        continue;
-                    one_burned = YES;
-                    sub_config.dest = RENDERSUB;
+                    // We are setting a source subtitle so access the source subtitle info
+                    hb_subtitle_t * subt = (hb_subtitle_t *) hb_list_item(title->list_subtitle, sourceIdx);
+
+                    if (subt != NULL)
+                    {
+                        hb_subtitle_config_t sub_config = subt->config;
+
+                        if (!subTrack.burnedIn && hb_subtitle_can_pass(subt->source, job->mux))
+                        {
+                            sub_config.dest = PASSTHRUSUB;
+                        }
+                        else if (hb_subtitle_can_burn(subt->source))
+                        {
+                            one_burned = YES;
+                            sub_config.dest = RENDERSUB;
+                        }
+
+                        sub_config.force = subTrack.forcedOnly;
+                        sub_config.default_track = subTrack.def;
+                        hb_subtitle_add(job, &sub_config, sourceIdx);
+                    }
                 }
-
-                sub_config.force = force;
-                sub_config.default_track = def;
-                hb_subtitle_add(job, &sub_config, subtitle);
             }
         }
     }
index c7a7fec1fc0f62fef64529641bae16623c60dbda..0515ec99ba2f1edbc847580e623165fe5f658fd6 100644 (file)
@@ -9,9 +9,12 @@
 #import "HBAttributedStringAdditions.h"
 #import "HBTitle.h"
 #import "HBJob.h"
+
 #import "HBAudioTrack.h"
 #import "HBAudioDefaults.h"
 
+#import "HBSubtitlesTrack.h"
+
 #import "HBPicture+UIAdditions.h"
 #import "HBFilters+UIAdditions.h"
 
@@ -131,7 +134,7 @@ static NSDictionary            *shortHeightAttr;
         }
         NSString *passesString = @"";
         // check to see if our first subtitle track is Foreign Language Search, in which case there is an in depth scan
-        if (self.subtitles.tracks.count && [self.subtitles.tracks[0][@"keySubTrackIndex"] intValue] == -1)
+        if (self.subtitles.tracks.firstObject.sourceTrackIdx == 1)
         {
             passesString = [passesString stringByAppendingString:@"1 Foreign Language Search Pass - "];
         }
@@ -447,7 +450,7 @@ static NSDictionary            *shortHeightAttr;
         
         // Ninth Line Subtitle Details
         int i = 0;
-        for (NSDictionary *track in self.subtitles.tracks)
+        for (HBSubtitlesTrack *track in self.subtitles.tracks)
         {
             // Ignore the none track.
             if (i == self.subtitles.tracks.count - 1)
@@ -457,16 +460,16 @@ static NSDictionary            *shortHeightAttr;
             
             /* remember that index 0 of Subtitles can contain "Foreign Audio Search*/
             [finalString appendString: @"Subtitle: " withAttributes:detailBoldAttr];
-            [finalString appendString: track[@"keySubTrackName"] withAttributes:detailAttr];
-            if ([track[@"keySubTrackForced"] intValue] == 1)
+            [finalString appendString: self.subtitles.sourceTracks[track.sourceTrackIdx][@"keySubTrackName"] withAttributes:detailAttr];
+            if (track.forcedOnly)
             {
                 [finalString appendString: @" - Forced Only" withAttributes:detailAttr];
             }
-            if ([track[@"keySubTrackBurned"] intValue] == 1)
+            if (track.burnedIn)
             {
                 [finalString appendString: @" - Burned In" withAttributes:detailAttr];
             }
-            if ([track[@"keySubTrackDefault"] intValue] == 1)
+            if (track.def)
             {
                 [finalString appendString: @" - Default" withAttributes:detailAttr];
             }
index f03520bacc66f57c1dc0acc31cf5fcb1ea8a2e2f..8c7b2dc6375333b083607bbde217a4f6404c7199 100644 (file)
@@ -95,7 +95,7 @@ NSString *HBChaptersChangedNotification  = @"HBChaptersChangedNotification";
 - (void)setUndo:(NSUndoManager *)undo
 {
     _undo = undo;
-    [@[self.video, self.range, self.filters, self.picture, /*self.audio, self.subtitles*/] makeObjectsPerformSelector:@selector(setUndo:)
+    [@[self.video, self.range, self.filters, self.picture, /*self.audio,*/ self.subtitles] makeObjectsPerformSelector:@selector(setUndo:)
                                                                                                        withObject:_undo];
     [self.chapterTitles makeObjectsPerformSelector:@selector(setUndo:) withObject:_undo];
 }
@@ -128,7 +128,7 @@ NSString *HBChaptersChangedNotification  = @"HBChaptersChangedNotification";
     _container = container;
 
     [self.audio containerChanged:container];
-    [self.subtitles containerChanged:container];
+    [self.subtitles setContainer:container];
     [self.video containerChanged];
 
     // post a notification for any interested observers to indicate that our video container has changed
index 29487dd6c459ad4f200e0e934e4e6ed2b624d65a..0a2c565940e7088d7552642203ea946bbe5a9197 100644 (file)
@@ -9,24 +9,8 @@
 
 NS_ASSUME_NONNULL_BEGIN
 
-extern NSString *keySubTrackSelectionIndex;
-extern NSString *keySubTrackName;
-extern NSString *keySubTrackIndex;
-extern NSString *keySubTrackLanguage;
-extern NSString *keySubTrackLanguageIsoCode;
-extern NSString *keySubTrackType;
-
-extern NSString *keySubTrackForced;
-extern NSString *keySubTrackBurned;
-extern NSString *keySubTrackDefault;
-
-extern NSString *keySubTrackSrtOffset;
-extern NSString *keySubTrackSrtFilePath;
-extern NSString *keySubTrackSrtCharCode;
-extern NSString *keySubTrackSrtCharCodeIndex;
-extern NSString *keySubTrackLanguageIndex;
-
 @class HBTitle;
+@class HBSubtitlesTrack;
 @class HBSubtitlesDefaults;
 
 @interface HBSubtitles : NSObject <NSSecureCoding, NSCopying, HBPresetCoding>
@@ -37,35 +21,26 @@ extern NSString *keySubTrackLanguageIndex;
 - (void)removeAll;
 - (void)reloadDefaults;
 
-- (void)validatePassthru;
-- (NSMutableDictionary *)createSubtitleTrack;
-- (NSMutableDictionary *)trackFromSourceTrackIndex:(NSInteger)index;
-
-@property (nonatomic, readonly) NSMutableArray *masterTrackArray;  // the master list of audio tracks from the title
-@property (nonatomic, readonly) NSMutableArray *tracks;
+- (void)addSrtTrackFromURL:(NSURL *)srtURL;
 
-@property (nonatomic, readwrite, strong) NSString *foreignAudioSearchTrackName;
-@property (nonatomic, readonly) NSArray *charCodeArray;
-
-@property (nonatomic, readonly) NSArray *languagesArray;
-@property (nonatomic, readonly) NSInteger languagesArrayDefIndex;
+@property (nonatomic, readonly) NSMutableArray<NSDictionary *> *sourceTracks;
+@property (nonatomic, readonly) NSMutableArray<HBSubtitlesTrack *> *tracks;
 
 @property (nonatomic, readwrite, strong) HBSubtitlesDefaults *defaults;
 
 /**
  *  For internal use
  */
-
-- (void)containerChanged:(int)container;
-@property (nonatomic, readwrite) int container; // initially is the default HB_MUX_MP4
+@property (nonatomic, readwrite) int container;
+@property (nonatomic, readwrite, weak, nullable) NSUndoManager *undo;
 
 @end
 
 @interface HBSubtitles (KVC)
 
 @property (nonatomic, readonly) NSUInteger countOfTracks;
-- (id)objectInTracksAtIndex:(NSUInteger)index;
-- (void)insertObject:(id)audioObject inTracksAtIndex:(NSUInteger)index;
+- (HBSubtitlesTrack *)objectInTracksAtIndex:(NSUInteger)index;
+- (void)insertObject:(HBSubtitlesTrack *)audioObject inTracksAtIndex:(NSUInteger)index;
 - (void)removeObjectFromTracksAtIndex:(NSUInteger)index;
 
 @end
index 85b69ac10a975f1b660528dc6f1b435ae691476c..8368c2c7454541daccdf144e10cb150463937904 100644 (file)
@@ -1,38 +1,34 @@
-//
-//  HBSubtitles.m
-//  HandBrake
-//
-//  Created by Damiano Galassi on 12/01/15.
-//
-//
+/*  HBSubtitles.m $
+
+ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr/>.
+ It may be used under the terms of the GNU General Public License. */
 
 #import "HBSubtitles.h"
 #import "HBSubtitlesDefaults.h"
 
+#import "HBSubtitlesTrack.h"
+
 #import "HBTitle.h"
 #import "HBCodingUtilities.h"
 
-#include "lang.h"
 #include "common.h"
 
-NSString *keySubTrackSelectionIndex = @"keySubTrackSelectionIndex";
-NSString *keySubTrackName = @"keySubTrackName";
-NSString *keySubTrackIndex = @"keySubTrackIndex";
-NSString *keySubTrackLanguage = @"keySubTrackLanguage";
-NSString *keySubTrackLanguageIsoCode = @"keySubTrackLanguageIsoCode";
-NSString *keySubTrackType = @"keySubTrackType";
+extern NSString *keySubTrackName;
+extern NSString *keySubTrackLanguageIsoCode;
+extern NSString *keySubTrackType;
+
+extern NSString *keySubTrackSrtFileURL;
 
-NSString *keySubTrackForced = @"keySubTrackForced";
-NSString *keySubTrackBurned = @"keySubTrackBurned";
-NSString *keySubTrackDefault = @"keySubTrackDefault";
+#define NONE_TRACK_INDEX        0
+#define FOREIGN_TRACK_INDEX     1
 
-NSString *keySubTrackSrtOffset = @"keySubTrackSrtOffset";
-NSString *keySubTrackSrtFilePath = @"keySubTrackSrtFilePath";
-NSString *keySubTrackSrtCharCode = @"keySubTrackSrtCharCode";
-NSString *keySubTrackSrtCharCodeIndex = @"keySubTrackSrtCharCodeIndex";
-NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
+@interface HBSubtitles () <HBTrackDataSource, HBTrackDelegate>
 
-#define CHAR_CODE_DEFAULT_INDEX 11
+/// Used to aovid circular dependecy validation.
+@property (nonatomic, readwrite) BOOL validating;
+
+@end
 
 @implementation HBSubtitles
 
@@ -43,72 +39,167 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
     {
         _container = HB_MUX_MP4;
 
+        _sourceTracks = [title.subtitlesTracks mutableCopy];
         _tracks = [[NSMutableArray alloc] init];
         _defaults = [[HBSubtitlesDefaults alloc] init];
 
-        _masterTrackArray = [title.subtitlesTracks mutableCopy];
-
-        NSMutableArray *forcedSourceNamesArray = [NSMutableArray array];
-        for (NSDictionary *dict in _masterTrackArray)
+        NSMutableSet<NSString *> *forcedSourceNamesArray = [NSMutableSet set];
+        int foreignAudioType = VOBSUB;
+        for (NSDictionary *dict in _sourceTracks)
         {
             enum subsource source = [dict[keySubTrackType] intValue];
-            NSString *subSourceName = @(hb_subsource_name(source));
+            NSString *name = @(hb_subsource_name(source));
             // if the subtitle track can be forced, add its source name to the array
-            if (hb_subtitle_can_force(source) && [forcedSourceNamesArray containsObject:subSourceName] == NO)
+            if (hb_subtitle_can_force(source) && name.length)
             {
-                [forcedSourceNamesArray addObject:subSourceName];
+                [forcedSourceNamesArray addObject:name];
             }
         }
 
         // now set the name of the Foreign Audio Search track
+        NSMutableString *foreignAudioSearchTrackName = [@"Foreign Audio Search (Bitmap)" mutableCopy];
         if (forcedSourceNamesArray.count)
         {
-            [forcedSourceNamesArray sortUsingComparator:^(id obj1, id obj2)
-             {
-                 return [((NSString *)obj1) compare:((NSString *)obj2)];
-             }];
+            [foreignAudioSearchTrackName appendFormat:@" ("];
+            for (NSString *name in forcedSourceNamesArray)
+            {
+                [foreignAudioSearchTrackName appendFormat:@"%@, ", name];
+            }
+            [foreignAudioSearchTrackName deleteCharactersInRange:NSMakeRange(foreignAudioSearchTrackName.length - 2, 2)];
+            [foreignAudioSearchTrackName appendFormat:@")"];
+        }
 
-            NSString *tempList = @"";
-            for (NSString *tempString in forcedSourceNamesArray)
+        // Add the none and foreign track to the source array
+        NSDictionary *none = @{  keySubTrackName: NSLocalizedString(@"None", nil)};
+        [_sourceTracks insertObject:none atIndex:0];
+
+        NSDictionary *foreign = @{ keySubTrackName: foreignAudioSearchTrackName,
+                                   keySubTrackType: @(foreignAudioType) };
+        [_sourceTracks insertObject:foreign atIndex:1];
+
+    }
+    return self;
+}
+
+#pragma mark - Data Source
+
+- (NSDictionary<NSString *, id> *)sourceTrackAtIndex:(NSUInteger)idx;
+{
+    return self.sourceTracks[idx];
+}
+
+- (NSArray<NSString *> *)sourceTracksArray
+{
+    NSMutableArray *sourceNames = [NSMutableArray array];
+
+    for (NSDictionary *track in self.sourceTracks)
+    {
+        [sourceNames addObject:track[keySubTrackName]];
+    }
+
+    return sourceNames;
+}
+
+#pragma mark - Delegate
+
+- (void)track:(HBSubtitlesTrack *)track didChangeSourceFrom:(NSUInteger)oldSourceIdx;
+{
+    // If the source was changed to None, remove the track
+    if (track.sourceTrackIdx == NONE_TRACK_INDEX)
+    {
+        NSUInteger idx = [self.tracks indexOfObject:track];
+        [self removeObjectFromTracksAtIndex:idx];
+    }
+    // If the source was changed to Foreign Audio Track,
+    // insert it at top if it wasn't already there
+    else if (track.sourceTrackIdx == FOREIGN_TRACK_INDEX)
+    {
+        NSUInteger idx = [self.tracks indexOfObject:track];
+        if (idx != 0)
+        {
+            [self removeObjectFromTracksAtIndex:idx];
+            if (self.tracks[0].sourceTrackIdx != FOREIGN_TRACK_INDEX)
             {
-                if (tempList.length)
-                {
-                    tempList = [tempList stringByAppendingString:@", "];
-                }
-                tempList = [tempList stringByAppendingString:tempString];
+                [self insertObject:track inTracksAtIndex:0];
             }
-            self.foreignAudioSearchTrackName = [NSString stringWithFormat:@"Foreign Audio Search (Bitmap) (%@)", tempList];
         }
-        else
+        [self addNoneTrack];
+    }
+    // Else add a new None track
+    else if (oldSourceIdx == NONE_TRACK_INDEX)
+    {
+        [self addNoneTrack];
+    }
+    [self validatePassthru];
+}
+
+- (BOOL)canSetBurnedInOption:(HBSubtitlesTrack *)track
+{
+    BOOL result = YES;
+    for (HBSubtitlesTrack *subTrack in self.tracks)
+    {
+        if (subTrack != track && subTrack.isEnabled
+            && subTrack.sourceTrackIdx > FOREIGN_TRACK_INDEX && !subTrack.canPassthru)
         {
-            self.foreignAudioSearchTrackName = @"Foreign Audio Search (Bitmap)";
+            result = NO;
         }
     }
-    return self;
+    return result;
 }
 
-- (void)addAllTracks
+- (void)didSetBurnedInOption:(HBSubtitlesTrack *)track
 {
-    [self.tracks removeAllObjects];
+    if (self.validating == NO && track.sourceTrackIdx != FOREIGN_TRACK_INDEX)
+    {
+        self.validating = YES;
+        NSUInteger idx = [self.tracks indexOfObject:track];
+        [self validateBurned:idx];
+        self.validating = NO;
+    }
+}
 
-    // Add the foreign audio search pass
-    [self addTrack:[self trackFromSourceTrackIndex:-1]];
+- (void)didSetDefaultOption:(HBSubtitlesTrack *)track
+{
+    if (self.validating == NO && track.sourceTrackIdx != FOREIGN_TRACK_INDEX)
+    {
+        self.validating = YES;
+        NSUInteger idx = [self.tracks indexOfObject:track];
+        [self validateDefault:idx];
+        self.validating = NO;
+    }
+}
 
-    // Add the remainings tracks
-    for (NSDictionary *track in self.masterTrackArray)
+- (void)addNoneTrack
+{
+    HBSubtitlesTrack *track = [self trackFromSourceTrackIndex:NONE_TRACK_INDEX];
+    [self addTrack:track];
+}
+
+#pragma mark - Public methods
+
+- (void)addAllTracks
+{
+    while (self.countOfTracks)
     {
-        NSInteger sourceIndex = [track[keySubTrackIndex] integerValue];
-        [self addTrack:[self trackFromSourceTrackIndex:sourceIndex]];
+        [self removeObjectFromTracksAtIndex:0];
     }
 
-    [self.tracks addObject:[self createSubtitleTrack]];
+    // Add the remainings tracks
+    for (NSUInteger idx = 1; idx < self.sourceTracksArray.count; idx++) {
+        [self addTrack:[self trackFromSourceTrackIndex:idx]];
+    }
+
+    [self addNoneTrack];
     [self validatePassthru];
 }
 
 - (void)removeAll
 {
-    [self.tracks removeAllObjects];
-    [self.tracks addObject:[self createSubtitleTrack]];
+    while (self.countOfTracks)
+    {
+        [self removeObjectFromTracksAtIndex:0];
+    }
+    [self addNoneTrack];
 }
 
 - (void)reloadDefaults
@@ -116,116 +207,63 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
     [self addTracksFromDefaults];
 }
 
-// This gets called whenever the video container changes.
-- (void)containerChanged:(int)container
+- (void)addSrtTrackFromURL:(NSURL *)srtURL
 {
-    self.container = container;
+    // Create a new entry for the subtitle source array so it shows up in our subtitle source list
+    [self.sourceTracks addObject:@{keySubTrackName: srtURL.lastPathComponent,
+                                   keySubTrackType: @(SRTSUB),
+                                   keySubTrackSrtFileURL: srtURL}];
+    HBSubtitlesTrack *track = [self trackFromSourceTrackIndex:self.sourceTracksArray.count - 1];
+    [self insertObject:track inTracksAtIndex:[self countOfTracks] - 1];
+}
 
-    [self validatePassthru];
+- (void)setContainer:(int)container
+{
+    _container = container;
+    for (HBSubtitlesTrack *track in self.tracks)
+    {
+        track.container = container;
+    }
+    if (!(self.undo.isUndoing || self.undo.isRedoing))
+    {
+        [self validatePassthru];
+    }
+}
+
+- (void)setUndo:(NSUndoManager *)undo
+{
+    _undo = undo;
+    for (HBSubtitlesTrack *track in self.tracks)
+    {
+        track.undo = undo;
+    }
 }
 
 /**
  *  Convenience method to add a track to subtitlesArray.
- *  It calculates the keySubTrackSelectionIndex.
  *
  *  @param track the track to add.
  */
-- (void)addTrack:(NSMutableDictionary *)newTrack
+- (void)addTrack:(HBSubtitlesTrack *)newTrack
 {
-    newTrack[keySubTrackSelectionIndex] = @([newTrack[keySubTrackIndex] integerValue] + 1 + (self.tracks.count == 0));
     [self insertObject:newTrack inTracksAtIndex:[self countOfTracks]];
 }
 
-/**
- *  Creates a new subtitle track.
- */
-- (NSMutableDictionary *)createSubtitleTrack
-{
-    NSMutableDictionary *newSubtitleTrack = [[NSMutableDictionary alloc] init];
-    newSubtitleTrack[keySubTrackIndex] = @(-2);
-    newSubtitleTrack[keySubTrackSelectionIndex] = @0;
-    newSubtitleTrack[keySubTrackName] = @"None";
-    newSubtitleTrack[keySubTrackForced] = @0;
-    newSubtitleTrack[keySubTrackBurned] = @0;
-    newSubtitleTrack[keySubTrackDefault] = @0;
-
-    return newSubtitleTrack;
-}
-
 /**
  *  Creates a new track dictionary from a source track.
  *
- *  @param index the index of the source track in the subtitlesSourceArray,
- *               -1 means a Foreign Audio Search pass.
- *
- *  @return a new mutable track dictionary.
+ *  @param index the index of the source track in the subtitlesSourceArray
  */
-- (NSMutableDictionary *)trackFromSourceTrackIndex:(NSInteger)index
+- (HBSubtitlesTrack *)trackFromSourceTrackIndex:(NSInteger)index
 {
-    NSMutableDictionary *track = [self createSubtitleTrack];
-
-    if (index == -1)
-    {
-        /*
-         * we are foreign lang search, which is inherently bitmap
-         *
-         * since it can be either VOBSUB or PGS and the latter can't be
-         * passed through to MP4, we need to know whether there are any
-         * PGS tracks in the source - otherwise we can just set the
-         * source track type to VOBSUB
-         */
-        int subtitleTrackType = VOBSUB;
-        if ([self.foreignAudioSearchTrackName rangeOfString:@(hb_subsource_name(PGSSUB))].location != NSNotFound)
-        {
-            subtitleTrackType = PGSSUB;
-        }
-        // Use -1 to indicate the foreign lang search
-        track[keySubTrackIndex] = @(-1);
-        track[keySubTrackName] = self.foreignAudioSearchTrackName;
-        track[keySubTrackType] = @(subtitleTrackType);
-        // foreign lang search is most useful when combined w/Forced Only - make it default
-        track[keySubTrackForced] = @1;
-    }
-    else
-    {
-        NSDictionary *sourceTrack = self.masterTrackArray[index];
-
-        track[keySubTrackIndex] = @(index);
-        track[keySubTrackName] = sourceTrack[keySubTrackName];
-
-        /* check to see if we are an srt, in which case set our file path and source track type kvp's*/
-        if ([self.masterTrackArray[index][keySubTrackType] intValue] == SRTSUB)
-        {
-            track[keySubTrackType] = @(SRTSUB);
-            track[keySubTrackSrtFilePath] = sourceTrack[keySubTrackSrtFilePath];
-
-            track[keySubTrackLanguageIndex] = @(self.languagesArrayDefIndex);
-            track[keySubTrackLanguageIsoCode] = self.languagesArray[self.languagesArrayDefIndex][1];
-
-            track[keySubTrackSrtCharCodeIndex] = @(CHAR_CODE_DEFAULT_INDEX);
-            track[keySubTrackSrtCharCode] = self.charCodeArray[CHAR_CODE_DEFAULT_INDEX];
-        }
-        else
-        {
-            track[keySubTrackType] = sourceTrack[keySubTrackType];
-        }
-    }
-
-    if (!hb_subtitle_can_burn([track[keySubTrackType] intValue]))
-    {
-        /* the source track cannot be burned in, so uncheck the widget */
-        track[keySubTrackBurned] = @0;
-    }
-
-    if (!hb_subtitle_can_force([track[keySubTrackType] intValue]))
-    {
-        /* the source track does not support forced flags, so uncheck the widget */
-        track[keySubTrackForced] = @0;
-    }
-    
+    HBSubtitlesTrack *track = [[HBSubtitlesTrack alloc] initWithTrackIdx:index container:self.container
+                                                              dataSource:self delegate:self];
+    track.undo = self.undo;
     return track;
 }
 
+#pragma mark - Defaults
+
 /**
  *  Remove all the subtitles tracks and
  *  add new ones based on the defaults settings
@@ -236,12 +274,15 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
     // so we don't add the same track twice.
     NSMutableIndexSet *tracksAdded = [NSMutableIndexSet indexSet];
 
-    [self.tracks removeAllObjects];
+    while (self.countOfTracks)
+    {
+        [self removeObjectFromTracksAtIndex:0];
+    }
 
     // Add the foreign audio search pass
     if (self.defaults.addForeignAudioSearch)
     {
-        [self addTrack:[self trackFromSourceTrackIndex:-1]];
+        [self addTrack:[self trackFromSourceTrackIndex:FOREIGN_TRACK_INDEX]];
     }
 
     // Add the tracks for the selected languages
@@ -249,23 +290,24 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
     {
         for (NSString *lang in self.defaults.trackSelectionLanguages)
         {
-            for (NSDictionary *track in self.masterTrackArray)
+            NSUInteger idx = 0;
+            for (NSDictionary *track in self.sourceTracks)
             {
-                if ([lang isEqualToString:@"und"] || [track[keySubTrackLanguageIsoCode] isEqualToString:lang])
+                if (idx > FOREIGN_TRACK_INDEX &&
+                    ([lang isEqualToString:@"und"] || [track[keySubTrackLanguageIsoCode] isEqualToString:lang]))
                 {
-                    NSInteger sourceIndex = [track[keySubTrackIndex] intValue];
-
-                    if (![tracksAdded containsIndex:sourceIndex])
+                    if (![tracksAdded containsIndex:idx])
                     {
-                        [self addTrack:[self trackFromSourceTrackIndex:sourceIndex]];
+                        [self addTrack:[self trackFromSourceTrackIndex:idx]];
                     }
-                    [tracksAdded addIndex:sourceIndex];
+                    [tracksAdded addIndex:idx];
 
                     if (self.defaults.trackSelectionBehavior == HBSubtitleTrackSelectionBehaviorFirst)
                     {
                         break;
                     }
                 }
+                idx++;
             }
         }
     }
@@ -273,14 +315,14 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
     // Add the closed captions track if there is one.
     if (self.defaults.addCC)
     {
-        for (NSDictionary *track in self.masterTrackArray)
+        NSUInteger idx = 0;
+        for (NSDictionary *track in self.sourceTracks)
         {
             if ([track[keySubTrackType] intValue] == CC608SUB)
             {
-                NSInteger sourceIndex = [track[keySubTrackIndex] intValue];
-                if (![tracksAdded containsIndex:sourceIndex])
+                if (![tracksAdded containsIndex:idx])
                 {
-                    [self addTrack:[self trackFromSourceTrackIndex:sourceIndex]];
+                    [self addTrack:[self trackFromSourceTrackIndex:idx]];
                 }
 
                 if (self.defaults.trackSelectionBehavior == HBSubtitleTrackSelectionBehaviorFirst)
@@ -288,6 +330,7 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
                     break;
                 }
             }
+            idx++;
         }
     }
 
@@ -296,25 +339,26 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
     {
         if (self.defaults.burnInBehavior == HBSubtitleTrackBurnInBehaviorFirst)
         {
-            if ([self.tracks.firstObject[keySubTrackIndex] integerValue] != -1)
+            if (self.tracks.firstObject.sourceTrackIdx != FOREIGN_TRACK_INDEX)
             {
-                self.tracks.firstObject[keySubTrackBurned] = @YES;
+                self.tracks.firstObject.burnedIn = YES;
             }
             else if (self.tracks.count > 1)
             {
-                self.tracks[1][keySubTrackBurned] = @YES;
+                self.tracks[0].burnedIn = NO;
+                self.tracks[1].burnedIn = YES;
             }
         }
         else if (self.defaults.burnInBehavior == HBSubtitleTrackBurnInBehaviorForeignAudio)
         {
-            if ([self.tracks.firstObject[keySubTrackIndex] integerValue] == -1)
+            if (self.tracks.firstObject.sourceTrackIdx == FOREIGN_TRACK_INDEX)
             {
-                self.tracks.firstObject[keySubTrackBurned] = @YES;
+                self.tracks.firstObject.burnedIn = YES;
             }
         }
         else if (self.defaults.burnInBehavior == HBSubtitleTrackBurnInBehaviorForeignAudioThenFirst)
         {
-            self.tracks.firstObject[keySubTrackBurned] = @YES;
+            self.tracks.firstObject.burnedIn = YES;
         }
     }
 
@@ -325,18 +369,18 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
         BOOL bitmapSubtitlesFound = NO;
 
         NSMutableArray *tracksToDelete = [[NSMutableArray alloc] init];
-        for (NSMutableDictionary *track in self.tracks)
+        for (HBSubtitlesTrack *track in self.tracks)
         {
-            if ([track[keySubTrackIndex] integerValue] != -1)
+            if (track.sourceTrackIdx != 1)
             {
-                if ((([track[keySubTrackType] intValue] == VOBSUB && self.defaults.burnInDVDSubtitles) ||
-                     ([track[keySubTrackType] intValue] == PGSSUB && self.defaults.burnInBluraySubtitles)) &&
+                if (((track.type == VOBSUB && self.defaults.burnInDVDSubtitles) ||
+                     (track.type == PGSSUB && self.defaults.burnInBluraySubtitles)) &&
                     !bitmapSubtitlesFound)
                 {
-                    track[keySubTrackBurned] = @YES;
+                    track.burnedIn = YES;
                     bitmapSubtitlesFound = YES;
                 }
-                else if ([track[keySubTrackType] intValue] == VOBSUB || [track[keySubTrackType] intValue] == PGSSUB)
+                else if (track.type == VOBSUB || track.type == PGSSUB)
                 {
                     [tracksToDelete addObject:track];
                 }
@@ -346,10 +390,12 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
     }
 
     // Add an empty track
-    [self insertObject:[self createSubtitleTrack] inTracksAtIndex:[self countOfTracks]];
+    [self addNoneTrack];
     [self validatePassthru];
 }
 
+#pragma mark - Validation
+
 /**
  *  Checks whether any subtitles in the list cannot be passed through.
  *  Set the first of any such subtitles to burned-in, remove the others.
@@ -357,103 +403,70 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
 - (void)validatePassthru
 {
     BOOL convertToBurnInUsed = NO;
-    NSMutableArray *tracksToDelete = [[NSMutableArray alloc] init];
+    NSMutableIndexSet *tracksToDelete = [[NSMutableIndexSet alloc] init];
 
     // convert any non-None incompatible tracks to burn-in or remove them
-    for (NSMutableDictionary *track in self.tracks)
+    NSUInteger idx = 0;
+    for (HBSubtitlesTrack *track in self.tracks)
     {
-        if (track[keySubTrackType] == nil)
-        {
-            continue;
-        }
-
-        int subtitleTrackType = [track[keySubTrackType] intValue];
-        if (!hb_subtitle_can_pass(subtitleTrackType, self.container))
+        if (track.isEnabled && track.sourceTrackIdx > FOREIGN_TRACK_INDEX && !track.canPassthru)
         {
             if (convertToBurnInUsed == NO)
             {
                 //we haven't set any track to burned-in yet, so we can
-                track[keySubTrackBurned] = @1;
+                track.burnedIn = YES;
                 convertToBurnInUsed = YES; //remove any additional tracks
             }
             else
             {
                 //we already have a burned-in track, we must remove others
-                [tracksToDelete addObject:track];
+                [tracksToDelete addIndex:idx];
             }
         }
+        idx++;
     }
+
     //if we converted a track to burned-in, unset it for tracks that support passthru
     if (convertToBurnInUsed == YES)
     {
-        for (NSMutableDictionary *track in self.tracks)
+        for (HBSubtitlesTrack *track in self.tracks)
         {
-            if (track[keySubTrackType] == nil)
+            if (track.isEnabled && track.sourceTrackIdx > FOREIGN_TRACK_INDEX && track.canPassthru)
             {
-                continue;
-            }
-
-            int subtitleTrackType = [track[keySubTrackType] intValue];
-            if (hb_subtitle_can_pass(subtitleTrackType, self.container))
-            {
-                track[keySubTrackBurned] = @0;
+                track.burnedIn = NO;
             }
         }
     }
 
-    [self willChangeValueForKey:@"tracks"];
-    if (tracksToDelete.count)
-    {
-        [self.tracks removeObjectsInArray:tracksToDelete];
-    }
-    [self didChangeValueForKey:@"tracks"];
-
-}
-
-#pragma mark - Languages
-
-@synthesize languagesArray = _languagesArray;
-
-- (NSArray *)languagesArray
-{
-    if (!_languagesArray)
+    // Delete the tracks
+    NSUInteger currentIndex = [tracksToDelete lastIndex];
+    while (currentIndex != NSNotFound)
     {
-        _languagesArray = [self populateLanguageArray];
+        [self removeObjectFromTracksAtIndex:currentIndex];
+        currentIndex = [tracksToDelete indexLessThanIndex:currentIndex];
     }
-
-    return _languagesArray;
 }
 
-- (NSArray *)populateLanguageArray
+- (void)validateBurned:(NSUInteger)index
 {
-    NSMutableArray *languages = [[NSMutableArray alloc] init];
-
-    for (const iso639_lang_t * lang = lang_get_next(NULL); lang != NULL; lang = lang_get_next(lang))
+    [self.tracks enumerateObjectsUsingBlock:^(HBSubtitlesTrack *track, NSUInteger idx, BOOL *stop)
     {
-        [languages addObject:@[@(lang->eng_name),
-                               @(lang->iso639_2)]];
-        if (!strcasecmp(lang->eng_name, "English"))
+        if (idx != index && track.sourceTrackIdx != FOREIGN_TRACK_INDEX)
         {
-            _languagesArrayDefIndex = [languages count] - 1;
+            track.burnedIn = NO;
         }
-    }
-    return [languages copy];
+    }];
 }
 
-@synthesize charCodeArray = _charCodeArray;
-
-- (NSArray *)charCodeArray
+- (void)validateDefault:(NSUInteger)index
 {
-    if (!_charCodeArray)
+    [self.tracks enumerateObjectsUsingBlock:^(HBSubtitlesTrack *obj, NSUInteger idx, BOOL *stop)
     {
-        // populate the charCodeArray.
-        _charCodeArray = @[@"ANSI_X3.4-1968", @"ANSI_X3.4-1986", @"ANSI_X3.4", @"ANSI_X3.110-1983", @"ANSI_X3.110", @"ASCII",
-                            @"ECMA-114", @"ECMA-118", @"ECMA-128", @"ECMA-CYRILLIC", @"IEC_P27-1", @"ISO-8859-1", @"ISO-8859-2",
-                            @"ISO-8859-3", @"ISO-8859-4", @"ISO-8859-5", @"ISO-8859-6", @"ISO-8859-7", @"ISO-8859-8", @"ISO-8859-9",
-                            @"ISO-8859-9E", @"ISO-8859-10", @"ISO-8859-11", @"ISO-8859-13", @"ISO-8859-14", @"ISO-8859-15", @"ISO-8859-16",
-                            @"UTF-7", @"UTF-8", @"UTF-16", @"UTF-16LE", @"UTF-16BE", @"UTF-32", @"UTF-32LE", @"UTF-32BE"];
-    }
-    return _charCodeArray;
+        if (idx != index && obj.sourceTrackIdx != FOREIGN_TRACK_INDEX)
+        {
+            obj.def = NO;
+        }
+    }];
 }
 
 #pragma mark - NSCopying
@@ -465,15 +478,13 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
     if (copy)
     {
         copy->_container = _container;
-
-        copy->_masterTrackArray = [_masterTrackArray mutableCopy];
-        copy->_foreignAudioSearchTrackName = [_foreignAudioSearchTrackName copy];
+        copy->_sourceTracks = [_sourceTracks mutableCopy];
 
         copy->_tracks = [[NSMutableArray alloc] init];
         [_tracks enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
             if (idx < _tracks.count)
             {
-                NSMutableDictionary *trackCopy = [obj copy];
+                id trackCopy = [obj copy];
                 [copy->_tracks addObject:trackCopy];
             }
         }];
@@ -493,14 +504,11 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
 
 - (void)encodeWithCoder:(NSCoder *)coder
 {
-    [coder encodeInt:1 forKey:@"HBAudioVersion"];
+    [coder encodeInt:1 forKey:@"HBSubtitlesVersion"];
 
     encodeInt(_container);
-
-    encodeObject(_masterTrackArray);
-    encodeObject(_foreignAudioSearchTrackName);
+    encodeObject(_sourceTracks);
     encodeObject(_tracks);
-
     encodeObject(_defaults);
 }
 
@@ -509,11 +517,15 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
     self = [super init];
 
     decodeInt(_container);
-
-    decodeObject(_masterTrackArray, NSMutableArray);
-    decodeObject(_foreignAudioSearchTrackName, NSString);
+    decodeObject(_sourceTracks, NSMutableArray);
     decodeObject(_tracks, NSMutableArray);
 
+    for (HBSubtitlesTrack *track in _tracks)
+    {
+        track.dataSource = self;
+        track.delegate = self;
+    }
+
     decodeObject(_defaults, HBSubtitlesDefaults);
 
     return self;
@@ -535,23 +547,26 @@ NSString *keySubTrackLanguageIndex = @"keySubTrackLanguageIndex";
 #pragma mark -
 #pragma mark KVC
 
-- (NSUInteger) countOfTracks
+- (NSUInteger)countOfTracks
 {
     return self.tracks.count;
 }
 
-- (id)objectInTracksAtIndex:(NSUInteger)index
+- (HBSubtitlesTrack *)objectInTracksAtIndex:(NSUInteger)index
 {
     return self.tracks[index];
 }
 
-- (void)insertObject:(id)track inTracksAtIndex:(NSUInteger)index;
+- (void)insertObject:(HBSubtitlesTrack *)track inTracksAtIndex:(NSUInteger)index;
 {
+    [[self.undo prepareWithInvocationTarget:self] removeObjectFromTracksAtIndex:index];
     [self.tracks insertObject:track atIndex:index];
 }
 
 - (void)removeObjectFromTracksAtIndex:(NSUInteger)index
 {
+    HBSubtitlesTrack *track = self.tracks[index];
+    [[self.undo prepareWithInvocationTarget:self] insertObject:track inTracksAtIndex:index];
     [self.tracks removeObjectAtIndex:index];
 }
 
index 509ab0de507316ae1ac870139e5ebeed4e162264..3fb0b7f50e023df323dcf235afd3622bb1855aac 100644 (file)
 #import "HBSubtitles.h"
 #import "HBSubtitlesDefaults.h"
 
-#include "hb.h"
-#include "lang.h"
-
-static void *HBSubtitlesControllerContext = &HBSubtitlesControllerContext;
-
-@interface HBSubtitlesController () <NSTableViewDataSource, NSTableViewDelegate>
-
-// IBOutles
-@property (unsafe_unretained) IBOutlet NSTableView *fTableView;
+@interface HBSubtitlesController ()
 
 // Defaults
 @property (nonatomic, readwrite, strong) HBSubtitlesDefaultsController *defaultsController;
 
-// Cached table view's cells
-@property (nonatomic, readonly) NSPopUpButtonCell *languagesCell;
-@property (nonatomic, readonly) NSPopUpButtonCell *encodingsCell;
-
 @end
 
 @implementation HBSubtitlesController
@@ -34,41 +22,12 @@ static void *HBSubtitlesControllerContext = &HBSubtitlesControllerContext;
 - (instancetype)init
 {
     self = [super initWithNibName:@"Subtitles" bundle:nil];
-
-    [self addObserver:self forKeyPath:@"self.subtitles.tracks" options:NSKeyValueObservingOptionInitial context:HBSubtitlesControllerContext];
-
     return self;
 }
 
-#pragma mark - KVO
-
-- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
-{
-    if (context == HBSubtitlesControllerContext)
-    {
-        // We use KVO to update the table manually
-        // because this table isn't using bindings
-        if ([keyPath isEqualToString:@"self.subtitles.tracks"])
-        {
-            [self.fTableView reloadData];
-        }
-    }
-    else
-    {
-        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
-    }
-}
-
-- (void)setSubtitles:(HBSubtitles *)subtitles
-{
-    _subtitles = subtitles;
-
-    [self.fTableView reloadData];    
-}
-
 #pragma mark - Actions
 
-- (BOOL)validateUserInterfaceItem:(id < NSValidatedUserInterfaceItem >)anItem
+- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)anItem
 {
     return (self.subtitles != nil);
 }
@@ -79,7 +38,6 @@ static void *HBSubtitlesControllerContext = &HBSubtitlesControllerContext;
 - (IBAction)addAll:(id)sender
 {
     [self.subtitles addAllTracks];
-    [self.fTableView reloadData];
 }
 
 /**
@@ -88,7 +46,6 @@ static void *HBSubtitlesControllerContext = &HBSubtitlesControllerContext;
 - (IBAction)removeAll:(id)sender
 {
     [self.subtitles removeAll];
-    [self.fTableView reloadData];
 }
 
 /**
@@ -98,15 +55,14 @@ static void *HBSubtitlesControllerContext = &HBSubtitlesControllerContext;
 - (IBAction)addTracksFromDefaults:(id)sender
 {
     [self.subtitles reloadDefaults];
-    [self.fTableView reloadData];
 }
 
 - (IBAction)showSettingsSheet:(id)sender
 {
     self.defaultsController = [[HBSubtitlesDefaultsController alloc] initWithSettings:self.subtitles.defaults];
 
-       [NSApp beginSheet:[self.defaultsController window]
-       modalForWindow:[[self view] window]
+       [NSApp beginSheet:self.defaultsController.window
+       modalForWindow:self.view.window
         modalDelegate:self
        didEndSelector:@selector(sheetDidEnd)
           contextInfo:NULL];
@@ -117,388 +73,6 @@ static void *HBSubtitlesControllerContext = &HBSubtitlesControllerContext;
     self.defaultsController = nil;
 }
 
-#pragma mark - Subtitles tracks creation and validation
-
-/**
- *  Checks whether any subtitles in the list cannot be passed through.
- *  Set the first of any such subtitles to burned-in, remove the others.
- */
-- (void)validatePassthru
-{
-    [self.subtitles validatePassthru];
-    [self.fTableView reloadData];
-}
-
-- (void)validateBurned:(NSInteger)index
-{
-    [self.subtitles.tracks enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
-     {
-         if (idx != index)
-         {
-             obj[keySubTrackBurned] = @0;
-         }
-     }];
-    [self validatePassthru];
-}
-
-- (void)validateDefault:(NSInteger)index
-{
-    [self.subtitles.tracks enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
-    {
-        if (idx != index)
-        {
-            obj[keySubTrackDefault] = @0;
-        }
-    }];
-}
-
-#pragma mark -
-#pragma mark Subtitle Table Data Source Methods
-
-- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
-{
-    return self.subtitles.tracks.count;
-}
-
-- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
-{
-    NSDictionary *track = self.subtitles.tracks[rowIndex];
-
-    if ([[aTableColumn identifier] isEqualToString:@"track"])
-    {
-        NSNumber *index = track[keySubTrackSelectionIndex];
-        if (index)
-            return index;
-        else
-            return @0;
-    }
-    else if ([[aTableColumn identifier] isEqualToString:@"forced"])
-    {
-        return track[keySubTrackForced];
-    }
-    else if ([[aTableColumn identifier] isEqualToString:@"burned"])
-    {
-        return track[keySubTrackBurned];
-    }
-    else if ([[aTableColumn identifier] isEqualToString:@"default"])
-    {
-        return track[keySubTrackDefault];
-    }
-    /* These next three columns only apply to srt's. they are disabled for source subs */
-    else if ([[aTableColumn identifier] isEqualToString:@"srt_lang"])
-    {
-        if ([track[keySubTrackType] intValue] == SRTSUB)
-        {
-            return track[keySubTrackLanguageIndex];
-        }
-        else
-        {
-            return @0;
-        }
-    }
-    else if ([[aTableColumn identifier] isEqualToString:@"srt_charcode"])
-    {
-        if ([track[keySubTrackType] intValue] == SRTSUB)
-        {
-            return track[keySubTrackSrtCharCodeIndex];
-        }
-        else
-        {
-            return @0;
-        }
-    }
-    else if ([[aTableColumn identifier] isEqualToString:@"srt_offset"])
-    {
-        if (track[keySubTrackSrtOffset])
-        {
-            return [track[keySubTrackSrtOffset] stringValue];
-        }
-        else
-        {
-            return @"0";
-        }
-    }
-
-    return nil;
-}
-
-/**
- *  Called whenever a widget in the table is edited or changed, we use it to record the change in the controlling array
- *  including removing and adding new tracks via the "None" ("track" index of 0)
- */
-- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
-{
-    if ([[aTableColumn identifier] isEqualToString:@"track"])
-    {
-        /* Set the array to track if we are vobsub (picture sub) */
-        if ([anObject intValue] > 0)
-        {
-            NSMutableDictionary *newTrack = [self.subtitles trackFromSourceTrackIndex:[anObject integerValue] - 1 - (rowIndex == 0)];
-            // Selection index calculation
-            newTrack[keySubTrackSelectionIndex] = @([anObject integerValue]);
-            self.subtitles.tracks[rowIndex] = newTrack;
-        }
-    }
-    else if ([[aTableColumn identifier] isEqualToString:@"forced"])
-    {
-        self.subtitles.tracks[rowIndex][keySubTrackForced] = @([anObject intValue]);
-    }
-    else if ([[aTableColumn identifier] isEqualToString:@"burned"])
-    {
-        self.subtitles.tracks[rowIndex][keySubTrackBurned] = @([anObject intValue]);
-        if([anObject intValue] == 1)
-        {
-            /* Burned In and Default are mutually exclusive */
-            self.subtitles.tracks[rowIndex][keySubTrackDefault] = @0;
-        }
-        /* now we need to make sure no other tracks are set to burned if we have set burned */
-        if ([anObject intValue] == 1)
-        {
-            [self validateBurned:rowIndex];
-        }
-    }
-    else if ([[aTableColumn identifier] isEqualToString:@"default"])
-    {
-        self.subtitles.tracks[rowIndex][keySubTrackDefault] = @([anObject intValue]);
-        if([anObject intValue] == 1)
-        {
-            /* Burned In and Default are mutually exclusive */
-            self.subtitles.tracks[rowIndex][keySubTrackBurned] = @0;
-        }
-        /* now we need to make sure no other tracks are set to default */
-        if ([anObject intValue] == 1)
-        {
-            [self validateDefault:rowIndex];
-        }
-    }
-    /* These next three columns only apply to srt's. they are disabled for source subs */
-    else if ([[aTableColumn identifier] isEqualToString:@"srt_lang"])
-    {
-        self.subtitles.tracks[rowIndex][keySubTrackLanguageIndex] = @([anObject intValue]);
-        self.subtitles.tracks[rowIndex][keySubTrackLanguageIsoCode] = self.subtitles.languagesArray[[anObject intValue]][1];
-    }
-    else if ([[aTableColumn identifier] isEqualToString:@"srt_charcode"])
-    {
-        /* charCodeArray */
-        self.subtitles.tracks[rowIndex][keySubTrackSrtCharCodeIndex] = @([anObject intValue]);
-        self.subtitles.tracks[rowIndex][keySubTrackSrtCharCode] = self.subtitles.charCodeArray[[anObject intValue]];
-    }
-    else if ([[aTableColumn identifier] isEqualToString:@"srt_offset"])
-    {
-        self.subtitles.tracks[rowIndex][keySubTrackSrtOffset] = @([anObject integerValue]);
-    }
-
-    /* now lets do a bit of logic to add / remove tracks as necessary via the "None" track (index 0) */
-    if ([[aTableColumn identifier] isEqualToString:@"track"])
-    {
-        /* Since currently no quicktime based playback devices support soft vobsubs in mp4, we make sure "burned in" is specified
-         * by default to avoid massive confusion and anarchy. However we also want to guard against multiple burned in subtitle tracks
-         * as libhb would ignore all but the first one anyway. Plus it would probably be stupid.
-         */
-        if ((self.subtitles.container & HB_MUX_MASK_MP4) && ([anObject intValue] != 0))
-        {
-            if ([self.subtitles.tracks[rowIndex][keySubTrackType] intValue] == VOBSUB)
-            {
-                /* lets see if there are currently any burned in subs specified */
-                BOOL subtrackBurnedInFound = NO;
-                for (id tempObject in self.subtitles.tracks)
-                {
-                    if ([tempObject[keySubTrackBurned] intValue] == 1)
-                    {
-                        subtrackBurnedInFound = YES;
-                    }
-                }
-                /* if we have no current vobsub set to burn it in ... burn it in by default */
-                if (!subtrackBurnedInFound)
-                {
-                    self.subtitles.tracks[rowIndex][keySubTrackBurned] = @1;
-                    /* Burned In and Default are mutually exclusive */
-                    self.subtitles.tracks[rowIndex][keySubTrackDefault] = @0;
-                }
-            }
-        }
-
-        /* We use the track popup index number (presumes index 0 is "None" which is ignored and only used to remove tracks if need be)
-         * to determine whether to 1 modify an existing track, 2. add a new empty "None" track or 3. remove an existing track.
-         */
-
-        if ([anObject intValue] != 0 && rowIndex == [self.subtitles.tracks count] - 1) // if we have a last track which != "None"
-        {
-            /* add a new empty None track */
-            [self.subtitles.tracks addObject:[self.subtitles createSubtitleTrack]];
-        }
-        else if ([anObject intValue] == 0 && rowIndex != ([self.subtitles.tracks count] -1))// if this track is set to "None" and not the last track displayed
-        {
-            /* we know the user chose to remove this track by setting it to None, so remove it from the array */
-            /* However,if this is the first track we have to reset the selected index of the next track by + 1, since it will now become
-             * the first track, which has to account for the extra "Foreign Language Search" index. */
-            if (rowIndex == 0 && [self.subtitles.tracks[1][keySubTrackSelectionIndex] intValue] != 0)
-            {
-                /* get the index of the selection in row one (which is track two) */
-                int trackOneSelectedIndex = [self.subtitles.tracks[1][keySubTrackSelectionIndex] intValue];
-                /* increment the index of the subtitle menu item by one, to account for Foreign Language Search which is unique to the first track */
-                self.subtitles.tracks[1][keySubTrackSelectionIndex] = @(trackOneSelectedIndex + 1);
-            }
-            /* now that we have made the adjustment for track one (index 0) go ahead and delete the track */
-            [self.subtitles.tracks removeObjectAtIndex: rowIndex];
-        }
-
-        // Validate the current passthru tracks.
-        [self validatePassthru];
-    }
-
-    [aTableView reloadData];
-}
-
-#pragma mark -
-#pragma mark Subtitle Table Delegate Methods
-
-- (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex
-{
-    if ([[tableColumn identifier] isEqualToString:@"track"])
-    {
-        // 'track' is a popup of all available source subtitle tracks for the given title
-        NSPopUpButtonCell *cellTrackPopup = [[NSPopUpButtonCell alloc] init];
-        [cellTrackPopup setControlSize:NSSmallControlSize];
-        [cellTrackPopup setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
-
-        // Add our initial "None" track which we use to add source tracks or remove tracks.
-        // "None" is always index 0.
-        [[cellTrackPopup menu] addItemWithTitle:@"None" action:NULL keyEquivalent:@""];
-
-        // Foreign Audio Search (index 1 in the popup) is only available for the first track
-        if (rowIndex == 0)
-        {
-            [[cellTrackPopup menu] addItemWithTitle:self.subtitles.foreignAudioSearchTrackName action:NULL keyEquivalent:@""];
-        }
-
-        for (NSDictionary *track in self.subtitles.masterTrackArray)
-        {
-            [[cellTrackPopup menu] addItemWithTitle:track[keySubTrackName] action:NULL keyEquivalent:@""];
-        }
-
-        return cellTrackPopup;
-    }
-    else if ([[tableColumn identifier] isEqualToString:@"srt_lang"])
-    {
-        return self.languagesCell;
-    }
-    else if ([[tableColumn identifier] isEqualToString:@"srt_charcode"])
-    {
-        return self.encodingsCell;
-    }
-
-    return nil;
-}
-
-/**
- *  Enables/Disables the table view cells.
- */
-- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
-{
-    if ([[aTableColumn identifier] isEqualToString:@"track"])
-    {
-        return;
-    }
-
-    // If the Track is None, we disable the other cells as None is an empty track
-    if ([self.subtitles.tracks[rowIndex][keySubTrackSelectionIndex] intValue] == 0)
-    {
-        [aCell setEnabled:NO];
-    }
-    else
-    {
-        // Since we have a valid track, we go ahead and enable the rest of the widgets and set them according to the controlling array */
-        [aCell setEnabled:YES];
-    }
-
-    if ([[aTableColumn identifier] isEqualToString:@"forced"])
-    {
-        // Disable the "Forced Only" checkbox if a) the track is "None" or b) the subtitle track doesn't support forced flags
-        if (![self.subtitles.tracks[rowIndex][keySubTrackSelectionIndex] intValue] ||
-            !hb_subtitle_can_force([self.subtitles.tracks[rowIndex][keySubTrackType] intValue]))
-        {
-            [aCell setEnabled:NO];
-        }
-        else
-        {
-            [aCell setEnabled:YES];
-        }
-    }
-    else if ([[aTableColumn identifier] isEqualToString:@"burned"])
-    {
-        /*
-         * Disable the "Burned In" checkbox if:
-         * a) the track is "None" OR
-         * b) the subtitle track can't be burned in OR
-         * c) the subtitle track can't be passed through (e.g. PGS w/MP4)
-         */
-        int subtitleTrackType = [self.subtitles.tracks[rowIndex][keySubTrackType] intValue];
-        if (![self.subtitles.tracks[rowIndex][keySubTrackSelectionIndex] intValue] ||
-            !hb_subtitle_can_burn(subtitleTrackType) || !hb_subtitle_can_pass(subtitleTrackType, self.subtitles.container))
-        {
-            [aCell setEnabled:NO];
-        }
-        else
-        {
-            [aCell setEnabled:YES];
-        }
-    }
-    else if ([[aTableColumn identifier] isEqualToString:@"default"])
-    {
-        /*
-         * Disable the "Default" checkbox if:
-         * a) the track is "None" OR
-         * b) the subtitle track can't be passed through (e.g. PGS w/MP4)
-         */
-        if (![self.subtitles.tracks[rowIndex][keySubTrackSelectionIndex] intValue] ||
-            !hb_subtitle_can_pass([self.subtitles.tracks[rowIndex][keySubTrackType] intValue], self.subtitles.container))
-        {
-            [aCell setEnabled:NO];
-        }
-        else
-        {
-            [aCell setEnabled:YES];
-        }
-    }
-    /* These next three columns only apply to srt's. they are disabled for source subs */
-    else if ([[aTableColumn identifier] isEqualToString:@"srt_lang"])
-    {
-        /* We have an srt file so set the track type (Source or SRT, and the srt file path ) kvp's*/
-        if ([self.subtitles.tracks[rowIndex][keySubTrackType] intValue] == SRTSUB)
-        {
-            [aCell setEnabled:YES];
-        }
-        else
-        {
-            [aCell setEnabled:NO];
-        }
-    }
-    else if ([[aTableColumn identifier] isEqualToString:@"srt_charcode"])
-    {
-        /* We have an srt file so set the track type (Source or SRT, and the srt file path ) kvp's*/
-        if ([self.subtitles.tracks[rowIndex][keySubTrackType] intValue] == SRTSUB)
-        {
-            [aCell setEnabled:YES];
-        }
-        else
-        {
-            [aCell setEnabled:NO];
-        }
-    }
-    else if ([[aTableColumn identifier] isEqualToString:@"srt_offset"])
-    {
-        if ([self.subtitles.tracks[rowIndex][keySubTrackType] intValue] == SRTSUB)
-        {
-            [aCell setEnabled:YES];
-        }
-        else
-        {
-            [aCell setEnabled:NO];
-        }
-    }
-}
-
 #pragma mark - Srt import
 
 /**
@@ -509,92 +83,33 @@ static void *HBSubtitlesControllerContext = &HBSubtitlesControllerContext;
 - (IBAction)browseImportSrtFile:(id)sender
 {
     NSOpenPanel *panel = [NSOpenPanel openPanel];
-    [panel setAllowsMultipleSelection:NO];
-    [panel setCanChooseFiles:YES];
-    [panel setCanChooseDirectories:NO];
+    panel.allowsMultipleSelection = NO;
+    panel.canChooseFiles = YES;
+    panel.canChooseDirectories = NO;
 
     NSURL *sourceDirectory;
-       if ([[NSUserDefaults standardUserDefaults] URLForKey:@"LastSrtImportDirectoryURL"])
-       {
-               sourceDirectory = [[NSUserDefaults standardUserDefaults] URLForKey:@"LastSrtImportDirectoryURL"];
-       }
-       else
-       {
-               sourceDirectory = [[NSURL fileURLWithPath:NSHomeDirectory()] URLByAppendingPathComponent:@"Desktop"];
-       }
-
-    /* we open up the browse srt sheet here and call for browseImportSrtFileDone after the sheet is closed */
-    NSArray *fileTypes = @[@"plist", @"srt"];
-    [panel setDirectoryURL:sourceDirectory];
-    [panel setAllowedFileTypes:fileTypes];
-    [panel beginSheetModalForWindow:[[self view] window] completionHandler:^(NSInteger result) {
-        if (result == NSOKButton)
-        {
-            NSURL *importSrtFileURL = [panel URL];
-            NSURL *importSrtDirectory = [importSrtFileURL URLByDeletingLastPathComponent];
-            [[NSUserDefaults standardUserDefaults] setURL:importSrtDirectory forKey:@"LastSrtImportDirectoryURL"];
-
-            /* Create a new entry for the subtitle source array so it shows up in our subtitle source list */
-            NSString *displayname = [importSrtFileURL lastPathComponent];// grok an appropriate display name from the srt subtitle */
-
-            /* create a dictionary of source subtitle information to store in our array */
-            [self.subtitles.masterTrackArray addObject:@{keySubTrackIndex: @(self.subtitles.masterTrackArray.count),
-                                                  keySubTrackName: displayname,
-                                                  keySubTrackType: @(SRTSUB),
-                                                  keySubTrackSrtFilePath: importSrtFileURL.path}];
-
-            // Now create a new srt subtitle dictionary assuming the user wants to add it to their list
-            NSMutableDictionary *newSubtitleSrtTrack = [self.subtitles trackFromSourceTrackIndex:self.subtitles.masterTrackArray.count - 1];
-            // Calculate the pop up selection index
-            newSubtitleSrtTrack[keySubTrackSelectionIndex] = @(self.subtitles.masterTrackArray.count + (self.subtitles.tracks.count == 1));
-            [self.subtitles.tracks insertObject:newSubtitleSrtTrack atIndex:self.subtitles.tracks.count - 1];
-
-            [self.fTableView reloadData];
-        }
-    }];
-}
-
-#pragma mark - UI cells
-
-@synthesize languagesCell = _languagesCell;
-
-- (NSPopUpButtonCell *)languagesCell
-{
-    if (!_languagesCell)
+    if ([[NSUserDefaults standardUserDefaults] URLForKey:@"LastSrtImportDirectoryURL"])
     {
-        // 'srt_lang' is a popup of commonly used languages to be matched to the source srt file
-        _languagesCell = [[NSPopUpButtonCell alloc] init];
-        // Set the Popups properties
-        [_languagesCell setControlSize:NSSmallControlSize];
-        [_languagesCell setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
-
-        // list our languages as per the languagesArray
-        for (NSArray *lang in self.subtitles.languagesArray)
-        {
-            [[_languagesCell menu] addItemWithTitle:lang[0] action:NULL keyEquivalent:@""];
-        }
+        sourceDirectory = [[NSUserDefaults standardUserDefaults] URLForKey:@"LastSrtImportDirectoryURL"];
+    }
+    else
+    {
+        sourceDirectory = [[NSURL fileURLWithPath:NSHomeDirectory()] URLByAppendingPathComponent:@"Desktop"];
     }
-    return _languagesCell;
-}
-
-@synthesize encodingsCell = _encodingsCell;
 
-- (NSPopUpButtonCell *)encodingsCell
-{
-    if (!_encodingsCell) {
-        // 'srt_charcode' is a popup of commonly used character codes to be matched to the source srt file
-        _encodingsCell = [[NSPopUpButtonCell alloc] init];
-        // Set the Popups properties
-        [_encodingsCell setControlSize:NSSmallControlSize];
-        [_encodingsCell setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
+    panel.directoryURL = sourceDirectory;
+    panel.allowedFileTypes = @[@"srt"];
 
-        // list our character codes, as per charCodeArray
-        for (NSString *charCode in self.subtitles.charCodeArray)
+    [panel beginSheetModalForWindow:self.view.window completionHandler:^(NSInteger result)
+    {
+        if (result == NSFileHandlingPanelOKButton)
         {
-            [[_encodingsCell menu] addItemWithTitle:charCode action: NULL keyEquivalent: @""];
+            NSURL *importSrtFileURL = panel.URL;
+            NSURL *importSrtDirectory = importSrtFileURL.URLByDeletingLastPathComponent;
+            [[NSUserDefaults standardUserDefaults] setURL:importSrtDirectory forKey:@"LastSrtImportDirectoryURL"];
+
+            [self.subtitles addSrtTrackFromURL:importSrtFileURL];
         }
-    }
-    return _encodingsCell;
+    }];
 }
-
 @end
diff --git a/macosx/HBSubtitlesTrack.h b/macosx/HBSubtitlesTrack.h
new file mode 100644 (file)
index 0000000..d093a80
--- /dev/null
@@ -0,0 +1,85 @@
+/*  HBSubtitlesTrack.h
+
+ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr/>.
+ It may be used under the terms of the GNU General Public License. */
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class HBSubtitlesTrack;
+
+/**
+ *  HBTrackDataSource
+ */
+@protocol HBTrackDataSource <NSObject>
+- (NSDictionary<NSString *, id> *)sourceTrackAtIndex:(NSUInteger)idx;
+- (NSArray<NSString *> *)sourceTracksArray;
+@end
+
+/**
+ * HBTrackDelegate
+ */
+@protocol HBTrackDelegate <NSObject>
+- (void)track:(HBSubtitlesTrack *)track didChangeSourceFrom:(NSUInteger)oldSourceIdx;
+
+- (BOOL)canSetBurnedInOption:(HBSubtitlesTrack *)track;
+- (void)didSetBurnedInOption:(HBSubtitlesTrack *)track;
+
+- (void)didSetDefaultOption:(HBSubtitlesTrack *)track;
+@end
+
+@interface HBSubtitlesTrack : NSObject <NSSecureCoding, NSCopying>
+
+- (instancetype)initWithTrackIdx:(NSUInteger)index
+                       container:(int)container
+                      dataSource:(id<HBTrackDataSource>)dataSource
+                        delegate:(id<HBTrackDelegate>)delegate;
+
+/// The index of the source in the data source tracks array.
+@property (nonatomic, readonly) NSUInteger sourceTrackIdx;
+/// Format.
+@property (nonatomic, readonly) int type;
+@property (nonatomic, readwrite) int container;
+
+/// Whether to use only the forced subtitles of the track.
+@property (nonatomic, readwrite) BOOL forcedOnly;
+/// Whether the track should be burned.
+@property (nonatomic, readwrite) BOOL burnedIn;
+/// Whether is the default track.
+@property (nonatomic, readwrite) BOOL def;
+
+/// The URL of the external subtitles file.
+@property (nonatomic, readwrite, copy, nullable) NSURL *fileURL;
+/// The ISO 639/2 language code of the external subtitles file.
+@property (nonatomic, readwrite, copy, nullable) NSString *isoLanguage;
+/// The character encoding of the external subtitles file.
+@property (nonatomic, readwrite, copy, nullable) NSString *charCode;
+/// The offset in milliseconds  of the external subtitles file.
+@property (nonatomic, readwrite) int offset;
+
+@property (nonatomic, readwrite, weak, nullable) NSUndoManager *undo;
+
+@property (nonatomic, readwrite, weak) id<HBTrackDataSource> dataSource;
+@property (nonatomic, readwrite, weak) id<HBTrackDelegate> delegate;
+
+/// A complete list of the possible languages.
+- (NSArray<NSString *> *)languages;
+/// A complete list of the possible encodings.
+- (NSArray<NSString *> *)encodings;
+
+@property (nonatomic, readonly) BOOL isSrt;
+@property (nonatomic, readonly) BOOL isEnabled;
+@property (nonatomic, readonly) BOOL canPassthru;
+
+@end
+
+/**
+ * HBIsoLanguageTrasformer is a trasformer to transform
+ * a ISO 639/2 code to a human readable language name.
+ */
+@interface HBIsoLanguageTrasformer : NSValueTransformer
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/macosx/HBSubtitlesTrack.m b/macosx/HBSubtitlesTrack.m
new file mode 100644 (file)
index 0000000..dfa6986
--- /dev/null
@@ -0,0 +1,420 @@
+/*  HBSubtitlesTrack.m
+
+ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr/>.
+ It may be used under the terms of the GNU General Public License. */
+
+#import "HBSubtitlesTrack.h"
+#import "HBCodingUtilities.h"
+
+#include "common.h"
+#include "lang.h"
+
+#define CHAR_CODE_DEFAULT_INDEX 11
+
+static NSArray *charEncodingArray = nil;
+static NSArray *_languagesArray = nil;
+
+NSString *keySubTrackName = @"keySubTrackName";
+NSString *keySubTrackLanguageIsoCode = @"keySubTrackLanguageIsoCode";
+NSString *keySubTrackType = @"keySubTrackType";
+NSString *keySubTrackSrtFileURL = @"keySubTrackSrtFileURL";
+
+@interface HBSubtitlesTrack ()
+@property (nonatomic, readwrite) BOOL validating;
+@end
+
+@implementation HBSubtitlesTrack
+
++ (void)initialize
+{
+    if ([HBSubtitlesTrack class] == self)
+    {
+        // populate the charCodeArray.
+        charEncodingArray = @[@"ANSI_X3.4-1968", @"ANSI_X3.4-1986", @"ANSI_X3.4", @"ANSI_X3.110-1983", @"ANSI_X3.110",
+                              @"ASCII", @"ECMA-114", @"ECMA-118", @"ECMA-128", @"ECMA-CYRILLIC", @"IEC_P27-1", @"ISO-8859-1",
+                              @"ISO-8859-2", @"ISO-8859-3", @"ISO-8859-4", @"ISO-8859-5", @"ISO-8859-6", @"ISO-8859-7",
+                              @"ISO-8859-8", @"ISO-8859-9", @"ISO-8859-9E", @"ISO-8859-10", @"ISO-8859-11", @"ISO-8859-13",
+                              @"ISO-8859-14", @"ISO-8859-15", @"ISO-8859-16", @"UTF-7", @"UTF-8", @"UTF-16", @"UTF-16LE",
+                              @"UTF-16BE", @"UTF-32", @"UTF-32LE", @"UTF-32BE"];
+
+        // populate the languages array
+        NSMutableArray *languages = [[NSMutableArray alloc] init];
+        for (const iso639_lang_t *lang = lang_get_next(NULL); lang != NULL; lang = lang_get_next(lang))
+        {
+            [languages addObject:@(lang->eng_name)];
+        }
+        _languagesArray = [languages copy];
+    }
+}
+
+- (instancetype)initWithTrackIdx:(NSUInteger)index
+                       container:(int)container
+                      dataSource:(id<HBTrackDataSource>)dataSource
+                        delegate:(id<HBTrackDelegate>)delegate
+{
+    self = [super init];
+    if (self)
+    {
+        _dataSource = dataSource;
+        _sourceTrackIdx = index;
+        _container = container;
+
+        [self validateSettings];
+
+        _delegate = delegate;
+    }
+
+    return self;
+}
+
+- (void)validateSettings
+{
+    NSDictionary *sourceTrack = [_dataSource sourceTrackAtIndex:_sourceTrackIdx];
+    self.type = [sourceTrack[keySubTrackType] intValue];
+
+    if (!hb_subtitle_can_burn(_type))
+    {
+        // the source track cannot be burned in, so uncheck the widget
+        self.burnedIn = NO;
+    }
+
+    if (!hb_subtitle_can_force(_type))
+    {
+        // the source track does not support forced flags, so uncheck the widget
+        self.forcedOnly = NO;
+    }
+
+    if (!hb_subtitle_can_pass(self.type, self.container))
+    {
+        self.burnedIn = YES;
+    }
+
+    if (_sourceTrackIdx == 1)
+    {
+        self.forcedOnly = YES;
+        self.burnedIn = YES;
+    }
+
+    // check to see if we are an srt, in which case set our file path and source track type kvp's
+    if (_type == SRTSUB)
+    {
+        self.fileURL = [sourceTrack[keySubTrackSrtFileURL] copy];
+        self.isoLanguage = @"eng";
+        self.charCode = charEncodingArray[CHAR_CODE_DEFAULT_INDEX];
+    }
+    else
+    {
+        self.fileURL = nil;
+        self.isoLanguage = nil;
+        self.charCode = nil;
+    }
+}
+
+#pragma mark - Track properties
+
+- (void)setSourceTrackIdx:(NSUInteger)sourceTrackIdx
+{
+    if (sourceTrackIdx != _sourceTrackIdx)
+    {
+        [[self.undo prepareWithInvocationTarget:self] setSourceTrackIdx:_sourceTrackIdx];
+    }
+
+    NSUInteger oldIdx = _sourceTrackIdx;
+    _sourceTrackIdx = sourceTrackIdx;
+
+    if (!(self.undo.isUndoing || self.undo.isRedoing))
+    {
+        [self validateSettings];
+
+        if (oldIdx != sourceTrackIdx)
+        {
+            [self.delegate track:self didChangeSourceFrom:oldIdx];
+        }
+    }
+}
+
+- (void)setType:(int)type
+{
+    if (type != _type)
+    {
+        [[self.undo prepareWithInvocationTarget:self] setType:_type];
+    }
+    _type = type;
+}
+
+- (void)setForcedOnly:(BOOL)forcedOnly
+{
+    if (forcedOnly != _forcedOnly)
+    {
+        [[self.undo prepareWithInvocationTarget:self] setForcedOnly:_forcedOnly];
+    }
+    _forcedOnly = forcedOnly;
+}
+
+- (void)setBurnedIn:(BOOL)burnedIn
+{
+    if (burnedIn != _burnedIn)
+    {
+        [[self.undo prepareWithInvocationTarget:self] setBurnedIn:_burnedIn];
+    }
+    _burnedIn = burnedIn;
+    if (!(self.undo.isUndoing || self.undo.isRedoing) || !self.validating)
+    {
+        self.validating = YES;
+        if (_burnedIn)
+        {
+            [self.delegate didSetBurnedInOption:self];
+            self.def = NO;
+        };
+        self.validating = NO;
+    }
+}
+
+- (BOOL)validateBurnedIn:(id *)ioValue error:(NSError * __autoreleasing *)outError
+{
+    BOOL retval = YES;
+
+    if (nil != *ioValue)
+    {
+        BOOL value = [*ioValue boolValue];
+        if (value && [self.delegate canSetBurnedInOption:self] == NO)
+        {
+            *ioValue = @NO;
+        }
+    }
+
+    return retval;
+}
+
+- (void)setDef:(BOOL)def
+{
+    if (def != _def)
+    {
+        [[self.undo prepareWithInvocationTarget:self] setDef:_def];
+    }
+    _def = def;
+    if (!(self.undo.isUndoing || self.undo.isRedoing) || !self.validating)
+    {
+        self.validating = YES;
+        if (_def)
+        {
+            [self.delegate didSetDefaultOption:self];
+            self.burnedIn = NO;
+        }
+        self.validating = NO;
+    }
+}
+
+#pragma mark - Srt only properties
+
+- (void)setFileURL:(NSURL *)fileURL
+{
+    if (fileURL != _fileURL)
+    {
+        [[self.undo prepareWithInvocationTarget:self] setFileURL:_fileURL];
+    }
+    _fileURL = [fileURL copy];
+}
+
+- (void)setIsoLanguage:(NSString *)isoLanguage
+{
+    if (![isoLanguage isEqualToString:_isoLanguage])
+    {
+        [[self.undo prepareWithInvocationTarget:self] setIsoLanguage:_isoLanguage];
+    }
+    _isoLanguage = [isoLanguage copy];
+}
+
+- (void)setCharCode:(NSString *)charCode
+{
+    if (![charCode isEqualToString:_charCode])
+    {
+        [[self.undo prepareWithInvocationTarget:self] setCharCode:_charCode];
+    }
+    _charCode = [charCode copy];
+}
+
+- (void)setOffset:(int)offset
+{
+    if (offset != _offset)
+    {
+        [[self.undo prepareWithInvocationTarget:self] setOffset:_def];
+    }
+    _offset = offset;
+}
+
+#pragma mark -
+
+- (NSArray *)sourceTracksArray
+{
+    return [self.dataSource sourceTracksArray];
+}
+
+- (BOOL)isSrt
+{
+    return self.type == SRTSUB;
+}
+
+- (BOOL)isEnabled
+{
+    return self.sourceTrackIdx != 0;
+}
+
+- (BOOL)canPassthru
+{
+    return (BOOL)hb_subtitle_can_pass(self.type, self.container) && self.isEnabled;
+}
+
+- (NSArray<NSString *> *)languages
+{
+    return _languagesArray;
+}
+- (NSArray<NSString *> *)encodings
+{
+    return charEncodingArray;
+}
+
+#pragma mark - KVO
+
++ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
+
+{
+    NSSet *retval = nil;
+
+    if ([key isEqualToString: @"isSrt"])
+    {
+        retval = [NSSet setWithObjects: @"type", nil];
+    }
+    else if ([key isEqualToString: @"isEnabled"])
+    {
+        retval = [NSSet setWithObjects: @"sourceTrackIdx", nil];
+    }
+    else if ([key isEqualToString: @"canPassthru"])
+    {
+        retval = [NSSet setWithObjects: @"isEnabled", @"sourceTrackIdx", nil];
+    }
+
+    return retval;
+}
+
+- (void)setNilValueForKey:(NSString *)key
+{
+    if ([key isEqualToString:@"offset"])
+    {
+        [self setValue:@0 forKey:key];
+    }
+}
+
+#pragma mark - NSCopying
+
+- (instancetype)copyWithZone:(NSZone *)zone
+{
+    HBSubtitlesTrack *copy = [[[self class] alloc] init];
+
+    if (copy)
+    {
+        copy->_sourceTrackIdx = _sourceTrackIdx;
+        copy->_type = _type;
+
+        copy->_forcedOnly = _forcedOnly;
+        copy->_burnedIn = _burnedIn;
+        copy->_def = _def;
+
+        copy->_fileURL = [_fileURL copy];
+        copy->_isoLanguage = [_isoLanguage copy];
+        copy->_charCode = [_charCode copy];
+        copy->_offset = _offset;
+    }
+
+    return copy;
+}
+
+#pragma mark - NSCoding
+
++ (BOOL)supportsSecureCoding
+{
+    return YES;
+}
+
+- (void)encodeWithCoder:(NSCoder *)coder
+{
+    [coder encodeInt:1 forKey:@"HBSubtitlesTrackVersion"];
+
+    encodeInteger(_sourceTrackIdx);
+    encodeInt(_type);
+    encodeInt(_container);
+
+    encodeBool(_forcedOnly);
+    encodeBool(_burnedIn);
+    encodeBool(_def);
+
+    encodeObject(_fileURL);
+    encodeObject(_isoLanguage);
+    encodeObject(_charCode);
+    encodeInt(_offset);
+}
+
+- (instancetype)initWithCoder:(NSCoder *)decoder
+{
+    self = [super init];
+
+    decodeInteger(_sourceTrackIdx);
+    decodeInt(_type);
+    decodeInt(_container);
+
+    decodeBool(_forcedOnly);
+    decodeBool(_burnedIn);
+    decodeBool(_def);
+
+    decodeObject(_fileURL, NSURL);
+    decodeObject(_isoLanguage, NSString);
+    decodeObject(_charCode, NSString);
+    decodeInt(_offset);
+
+    return self;
+}
+
+@end
+
+#pragma mark - Value Trasformers
+
+@implementation HBIsoLanguageTrasformer
+
++ (Class)transformedValueClass
+{
+    return [NSString class];
+}
+
+- (id)transformedValue:(id)value
+{
+    if ([value length])
+    {
+        iso639_lang_t *lang = lang_for_code2([value UTF8String]);
+        if (lang)
+        {
+            return @(lang->eng_name);
+        }
+    }
+    return nil;
+}
+
++ (BOOL)allowsReverseTransformation
+{
+    return YES;
+}
+
+- (id)reverseTransformedValue:(id)value
+{
+    if ([value length])
+    {
+        iso639_lang_t *lang = lang_for_english([value UTF8String]);
+        if (lang)
+        {
+            return @(lang->iso639_2);
+        }
+    }
+    return nil;
+}
+
+@end
index 0d9f93073ec7c1c2520baaad021a8d4d8f360d0c..af2fcbe5397f43baaf20259ce6b5b7d291d60dc7 100644 (file)
@@ -20,19 +20,9 @@ extern NSString *keyAudioInputChannelLayout;
 extern NSString *keyAudioTrackLanguageIsoCode;
 
 extern NSString *keySubTrackName;
-extern NSString *keySubTrackIndex;
-extern NSString *keySubTrackLanguage;
 extern NSString *keySubTrackLanguageIsoCode;
 extern NSString *keySubTrackType;
 
-extern NSString *keySubTrackForced;
-extern NSString *keySubTrackBurned;
-extern NSString *keySubTrackDefault;
-
-extern NSString *keySubTrackSrtOffset;
-extern NSString *keySubTrackSrtFilePath;
-extern NSString *keySubTrackSrtCharCode;
-
 @interface HBTitle ()
 
 @property (nonatomic, readonly) hb_title_t *hb_title;
@@ -224,9 +214,7 @@ extern NSString *keySubTrackSrtCharCode;
 
             /* create a dictionary of source subtitle information to store in our array */
             [tracks addObject:@{keySubTrackName: [NSString stringWithFormat:@"%d: %@ (%@) (%@)", i, nativeLanguage, bitmapOrText, subSourceName],
-                                              keySubTrackIndex: @(i),
                                               keySubTrackType: @(subtitle->source),
-                                              keySubTrackLanguage: nativeLanguage,
                                               keySubTrackLanguageIsoCode: @(subtitle->iso639_2)}];
         }
 
index 0e61bd41423169527297bf226ea633a318958fd9..78ce4c05c266f5a5f1fe69804b51d6df4ee8c0dc 100644 (file)
@@ -18,19 +18,9 @@ extern NSString *keyAudioInputChannelLayout;
 extern NSString *keyAudioTrackLanguageIsoCode;
 
 extern NSString *keySubTrackName;
-extern NSString *keySubTrackIndex;
-extern NSString *keySubTrackLanguage;
 extern NSString *keySubTrackLanguageIsoCode;
 extern NSString *keySubTrackType;
 
-extern NSString *keySubTrackForced;
-extern NSString *keySubTrackBurned;
-extern NSString *keySubTrackDefault;
-
-extern NSString *keySubTrackSrtOffset;
-extern NSString *keySubTrackSrtFilePath;
-extern NSString *keySubTrackSrtCharCode;
-
 @implementation HBMockTitle
 
 - (instancetype)init
@@ -161,7 +151,6 @@ extern NSString *keySubTrackSrtCharCode;
 
             // create a dictionary of source subtitle information to store in our array
             [tracks addObject:@{keySubTrackName: [NSString stringWithFormat:@"%d: %@ (%@) (%@)", i, nativeLanguage, bitmapOrText, subSourceName],
-                                keySubTrackIndex: @(i),
                                 keySubTrackType: @(subtitle->source),
                                 keySubTrackLanguage: nativeLanguage,
                                 keySubTrackLanguageIsoCode: @(subtitle->iso639_2)}];
index 654be76b1e8e3b011256e7a8e8341f9b54a9deae..47f7f3cf16ca48efdb16f9866a7f81db58959bb6 100644 (file)
                A9160A351AE7A165009A7818 /* HBCodingUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = A9160A341AE7A165009A7818 /* HBCodingUtilities.m */; };
                A91726E7197291BC00D1AFEF /* HBChapterTitlesController.m in Sources */ = {isa = PBXBuildFile; fileRef = A91726E6197291BC00D1AFEF /* HBChapterTitlesController.m */; };
                A91806711A4807B000FC9BED /* HBRange.m in Sources */ = {isa = PBXBuildFile; fileRef = A91806701A4807B000FC9BED /* HBRange.m */; };
+               A9181CB21BD76F8400E5C8B0 /* HBSubtitlesTrack.m in Sources */ = {isa = PBXBuildFile; fileRef = A9181CB11BD76F8400E5C8B0 /* HBSubtitlesTrack.m */; };
                A91AFD0C1A948827009BECED /* HBOutputFileWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = A91AFD0B1A948827009BECED /* HBOutputFileWriter.m */; };
                A91AFD0F1A949472009BECED /* HBJobOutputFileWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = A91AFD0E1A949472009BECED /* HBJobOutputFileWriter.m */; };
                A91C024D1A16516A00DEA6F3 /* JobSmall@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = A91C024C1A16516A00DEA6F3 /* JobSmall@2x.png */; };
                A91726E6197291BC00D1AFEF /* HBChapterTitlesController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBChapterTitlesController.m; sourceTree = "<group>"; };
                A918066F1A4807B000FC9BED /* HBRange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBRange.h; sourceTree = "<group>"; };
                A91806701A4807B000FC9BED /* HBRange.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBRange.m; sourceTree = "<group>"; };
+               A9181CB01BD76F8400E5C8B0 /* HBSubtitlesTrack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBSubtitlesTrack.h; sourceTree = "<group>"; };
+               A9181CB11BD76F8400E5C8B0 /* HBSubtitlesTrack.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBSubtitlesTrack.m; sourceTree = "<group>"; };
                A91AFD0A1A948827009BECED /* HBOutputFileWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBOutputFileWriter.h; sourceTree = "<group>"; };
                A91AFD0B1A948827009BECED /* HBOutputFileWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBOutputFileWriter.m; sourceTree = "<group>"; };
                A91AFD0D1A949472009BECED /* HBJobOutputFileWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBJobOutputFileWriter.h; sourceTree = "<group>"; };
                        children = (
                                A91017B21A64440A00039BFB /* HBSubtitles.h */,
                                A91017B31A64440A00039BFB /* HBSubtitles.m */,
+                               A9181CB01BD76F8400E5C8B0 /* HBSubtitlesTrack.h */,
+                               A9181CB11BD76F8400E5C8B0 /* HBSubtitlesTrack.m */,
                                A9F4728B1976BAA70009EC65 /* HBSubtitlesDefaults.h */,
                                A9F4728C1976BAA70009EC65 /* HBSubtitlesDefaults.m */,
                        );
                                A9DEC87F1A23DF6F00C79B48 /* HBJob.m in Sources */,
                                A9E2FD271A21BC4A000E8D3F /* HBAddPresetController.m in Sources */,
                                A975C08E1AE8C5270061870D /* HBStateFormatter.m in Sources */,
+                               A9181CB21BD76F8400E5C8B0 /* HBSubtitlesTrack.m in Sources */,
                                A9D488A51996270300E9B1BA /* HBTreeNode.m in Sources */,
                                A9597A2A1A49749D00007771 /* HBRange+UIAdditions.m in Sources */,
                        );