diff options
author | Damiano Galassi <[email protected]> | 2019-07-19 21:17:27 +0200 |
---|---|---|
committer | Damiano Galassi <[email protected]> | 2019-07-19 21:17:27 +0200 |
commit | 97734fff76d15a2da60f7956d82d463ac7ce6ece (patch) | |
tree | 11d568970a940eea0bb12dd8594773da39c830a4 /macosx | |
parent | f7654c69cb5bad5dcdeaa7375e04327c5178d387 (diff) |
MacGui: refactor the queue and implement part of the new queue ui.
Diffstat (limited to 'macosx')
-rw-r--r-- | macosx/Base.lproj/HBQueueDetailsViewController.xib | 96 | ||||
-rw-r--r-- | macosx/Base.lproj/HBQueueTableViewController.xib | 178 | ||||
-rw-r--r-- | macosx/Base.lproj/MainMenu.xib | 8 | ||||
-rw-r--r-- | macosx/Base.lproj/MainWindow.xib | 22 | ||||
-rw-r--r-- | macosx/Base.lproj/Queue.xib | 319 | ||||
-rw-r--r-- | macosx/HBAppDelegate.h | 10 | ||||
-rw-r--r-- | macosx/HBAppDelegate.m | 43 | ||||
-rw-r--r-- | macosx/HBController.h | 11 | ||||
-rw-r--r-- | macosx/HBController.m | 154 | ||||
-rw-r--r-- | macosx/HBJob+UIAdditions.h | 2 | ||||
-rw-r--r-- | macosx/HBJob+UIAdditions.m | 68 | ||||
-rw-r--r-- | macosx/HBQueue.h | 93 | ||||
-rw-r--r-- | macosx/HBQueue.m | 765 | ||||
-rw-r--r-- | macosx/HBQueueController.h | 27 | ||||
-rw-r--r-- | macosx/HBQueueController.m | 1457 | ||||
-rw-r--r-- | macosx/HBQueueDetailsViewController.h | 27 | ||||
-rw-r--r-- | macosx/HBQueueDetailsViewController.m | 65 | ||||
-rw-r--r-- | macosx/HBQueueItem.m | 18 | ||||
-rw-r--r-- | macosx/HBQueueItemView.h | 3 | ||||
-rw-r--r-- | macosx/HBQueueItemView.m | 25 | ||||
-rw-r--r-- | macosx/HBQueueTableViewController.h | 29 | ||||
-rw-r--r-- | macosx/HBQueueTableViewController.m | 339 | ||||
-rw-r--r-- | macosx/HandBrake.xcodeproj/project.pbxproj | 46 |
23 files changed, 2181 insertions, 1624 deletions
diff --git a/macosx/Base.lproj/HBQueueDetailsViewController.xib b/macosx/Base.lproj/HBQueueDetailsViewController.xib new file mode 100644 index 000000000..278805b95 --- /dev/null +++ b/macosx/Base.lproj/HBQueueDetailsViewController.xib @@ -0,0 +1,96 @@ +<?xml version="1.0" encoding="UTF-8"?> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> + <dependencies> + <deployment identifier="macosx"/> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/> + <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> + </dependencies> + <objects> + <customObject id="-2" userLabel="File's Owner" customClass="HBQueueDetailsViewController"> + <connections> + <outlet property="detailsLabel" destination="tBk-3B-YhZ" id="UvN-Op-Nf2"/> + <outlet property="scrollView" destination="jKW-nH-vE3" id="DWg-bJ-i8M"/> + <outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/> + </connections> + </customObject> + <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> + <customObject id="-3" userLabel="Application" customClass="NSObject"/> + <customView id="Hz6-mo-xeY"> + <rect key="frame" x="0.0" y="0.0" width="480" height="98"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <subviews> + <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Z14-gh-hWn"> + <rect key="frame" x="402" y="13" width="64" height="32"/> + <buttonCell key="cell" type="push" title="Edit" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="erY-5X-50l"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> + <connections> + <action selector="editItem:" target="-2" id="ozj-G7-6d5"/> + <binding destination="-2" name="enabled" keyPath="self.item" id="Nss-YT-PPX"> + <dictionary key="options"> + <string key="NSValueTransformerName">NSIsNotNil</string> + </dictionary> + </binding> + </connections> + </button> + <scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="jKW-nH-vE3"> + <rect key="frame" x="20" y="61" width="440" height="17"/> + <clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="Nz1-nG-orF"> + <rect key="frame" x="0.0" y="0.0" width="440" height="17"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <subviews> + <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="tBk-3B-YhZ"> + <rect key="frame" x="0.0" y="0.0" width="440" height="17"/> + <textFieldCell key="cell" selectable="YES" allowsUndo="NO" title="Multiline Label" allowsEditingTextAttributes="YES" id="cFx-4D-4X9"> + <font key="font" usesAppearanceFont="YES"/> + <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + </textField> + </subviews> + <constraints> + <constraint firstAttribute="trailing" secondItem="tBk-3B-YhZ" secondAttribute="trailing" constant="2" id="Jct-L0-ooa"/> + <constraint firstItem="tBk-3B-YhZ" firstAttribute="leading" secondItem="Nz1-nG-orF" secondAttribute="leading" constant="2" id="a3e-oE-U6F"/> + <constraint firstItem="tBk-3B-YhZ" firstAttribute="top" secondItem="Nz1-nG-orF" secondAttribute="top" id="gKz-v4-CFb"/> + </constraints> + </clipView> + <scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="Lon-kY-Grs"> + <rect key="frame" x="-100" y="-100" width="444" height="16"/> + <autoresizingMask key="autoresizingMask"/> + </scroller> + <scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="vPy-U2-zcL"> + <rect key="frame" x="429" y="1" width="16" height="17"/> + <autoresizingMask key="autoresizingMask"/> + </scroller> + </scrollView> + <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oq4-g0-srt"> + <rect key="frame" x="330" y="13" width="75" height="32"/> + <buttonCell key="cell" type="push" title="Reset" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="hgl-N3-x4S"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> + <connections> + <action selector="resetItem:" target="-2" id="42R-t4-FxO"/> + <binding destination="-2" name="enabled" keyPath="self.item" id="L2a-Rh-3D1"> + <dictionary key="options"> + <string key="NSValueTransformerName">NSIsNotNil</string> + </dictionary> + </binding> + </connections> + </button> + </subviews> + <constraints> + <constraint firstItem="Z14-gh-hWn" firstAttribute="leading" secondItem="oq4-g0-srt" secondAttribute="trailing" constant="9" id="3oM-tb-KiT"/> + <constraint firstItem="oq4-g0-srt" firstAttribute="firstBaseline" secondItem="Z14-gh-hWn" secondAttribute="firstBaseline" id="95H-sb-RO5"/> + <constraint firstAttribute="trailing" secondItem="jKW-nH-vE3" secondAttribute="trailing" constant="20" id="Cdo-eU-VL6"/> + <constraint firstAttribute="bottom" secondItem="Z14-gh-hWn" secondAttribute="bottom" constant="20" id="CwL-gc-KKJ"/> + <constraint firstItem="Z14-gh-hWn" firstAttribute="top" secondItem="jKW-nH-vE3" secondAttribute="bottom" constant="20" id="TC5-6s-DmD"/> + <constraint firstItem="jKW-nH-vE3" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="20" id="TWB-aM-yau"/> + <constraint firstAttribute="trailing" secondItem="Z14-gh-hWn" secondAttribute="trailing" constant="20" id="tSm-Qq-U0q"/> + <constraint firstItem="jKW-nH-vE3" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="20" id="zXd-jW-bKU"/> + </constraints> + <point key="canvasLocation" x="140" y="306.5"/> + </customView> + </objects> +</document> diff --git a/macosx/Base.lproj/HBQueueTableViewController.xib b/macosx/Base.lproj/HBQueueTableViewController.xib new file mode 100644 index 000000000..8eb989790 --- /dev/null +++ b/macosx/Base.lproj/HBQueueTableViewController.xib @@ -0,0 +1,178 @@ +<?xml version="1.0" encoding="UTF-8"?> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> + <dependencies> + <deployment identifier="macosx"/> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/> + <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> + </dependencies> + <objects> + <customObject id="-2" userLabel="File's Owner" customClass="HBQueueTableViewController"> + <connections> + <outlet property="tableView" destination="VV7-t7-Ufd" id="VSC-MG-zZW"/> + <outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/> + </connections> + </customObject> + <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> + <customObject id="-3" userLabel="Application" customClass="NSObject"/> + <customView id="Hz6-mo-xeY"> + <rect key="frame" x="0.0" y="0.0" width="543" height="150"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <subviews> + <scrollView autohidesScrollers="YES" horizontalLineScroll="24" horizontalPageScroll="10" verticalLineScroll="24" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="aFR-JM-AgT"> + <rect key="frame" x="-1" y="-1" width="545" height="152"/> + <clipView key="contentView" drawsBackground="NO" id="Q5n-II-uln"> + <rect key="frame" x="1" y="1" width="543" height="150"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <subviews> + <tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" alternatingRowBackgroundColors="YES" columnSelection="YES" autosaveColumns="NO" rowHeight="22" rowSizeStyle="automatic" viewBased="YES" id="VV7-t7-Ufd" customClass="HBTableView"> + <rect key="frame" x="0.0" y="0.0" width="543" height="150"/> + <autoresizingMask key="autoresizingMask"/> + <size key="intercellSpacing" width="3" height="2"/> + <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/> + <color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/> + <tableColumns> + <tableColumn width="540" minWidth="40" maxWidth="3000" id="QfC-OR-ahq"> + <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left"> + <font key="font" metaFont="smallSystem"/> + <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/> + </tableHeaderCell> + <textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="K6c-HW-um8"> + <font key="font" metaFont="system"/> + <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/> + <prototypeCellViews> + <tableCellView identifier="MainSimpleCell" id="Til-BP-Zag" customClass="HBQueueItemView"> + <rect key="frame" x="1" y="1" width="540" height="22"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <subviews> + <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ns0-GA-WCh"> + <rect key="frame" x="21" y="3" width="494" height="17"/> + <constraints> + <constraint firstAttribute="height" relation="greaterThanOrEqual" constant="17" id="5Tc-Oy-HWg"/> + </constraints> + <textFieldCell key="cell" enabled="NO" allowsUndo="NO" sendsActionOnEndEditing="YES" title="Table View Cell" id="xSd-Hl-5Qk"> + <font key="font" metaFont="system"/> + <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + </textField> + <button horizontalHuggingPriority="750" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="CSU-Pb-Uaw"> + <rect key="frame" x="521" y="4" width="16" height="16"/> + <constraints> + <constraint firstAttribute="height" constant="16" id="NiA-3H-N2G"/> + <constraint firstAttribute="width" constant="16" id="cei-uT-jX0"/> + </constraints> + <buttonCell key="cell" type="bevel" bezelStyle="rounded" image="Delete" imagePosition="only" alignment="center" alternateImage="DeleteHighlightPressed" id="LOA-aO-spo"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> + </button> + <imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="kNw-fy-iYB"> + <rect key="frame" x="3" y="5" width="14" height="14"/> + <imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NSActionTemplate" id="ofG-k5-PfV"/> + </imageView> + </subviews> + <constraints> + <constraint firstItem="Ns0-GA-WCh" firstAttribute="leading" secondItem="kNw-fy-iYB" secondAttribute="trailing" constant="6" id="KxO-Fj-XQO"/> + <constraint firstItem="kNw-fy-iYB" firstAttribute="leading" secondItem="Til-BP-Zag" secondAttribute="leading" constant="3" id="LIY-lT-8dm"/> + <constraint firstAttribute="trailing" secondItem="CSU-Pb-Uaw" secondAttribute="trailing" constant="3" id="dIt-QV-ws7"/> + <constraint firstItem="Ns0-GA-WCh" firstAttribute="top" secondItem="Til-BP-Zag" secondAttribute="top" constant="2" id="emx-1Y-YDk"/> + <constraint firstItem="CSU-Pb-Uaw" firstAttribute="top" secondItem="Til-BP-Zag" secondAttribute="top" constant="2" id="g9F-L6-mfh"/> + <constraint firstItem="CSU-Pb-Uaw" firstAttribute="leading" secondItem="Ns0-GA-WCh" secondAttribute="trailing" constant="8" id="nds-NF-PaH"/> + <constraint firstItem="kNw-fy-iYB" firstAttribute="top" secondItem="Til-BP-Zag" secondAttribute="top" constant="3" id="r8c-Vs-yih"/> + </constraints> + <connections> + <outlet property="imageView" destination="kNw-fy-iYB" id="PMS-Sv-QjR"/> + <outlet property="removeButton" destination="CSU-Pb-Uaw" id="zX0-eM-VbX"/> + <outlet property="textField" destination="Ns0-GA-WCh" id="vvk-OU-Psw"/> + </connections> + </tableCellView> + </prototypeCellViews> + </tableColumn> + </tableColumns> + <connections> + <outlet property="dataSource" destination="-2" id="Jhm-K7-pBS"/> + <outlet property="delegate" destination="-2" id="22i-mE-trX"/> + <outlet property="menu" destination="42P-N1-a6j" id="5Zc-oy-bJ0"/> + </connections> + </tableView> + </subviews> + <nil key="backgroundColor"/> + </clipView> + <scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="JPN-K8-xyG"> + <rect key="frame" x="1" y="139" width="541" height="16"/> + <autoresizingMask key="autoresizingMask"/> + </scroller> + <scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="VMM-tz-dQ6"> + <rect key="frame" x="224" y="17" width="15" height="102"/> + <autoresizingMask key="autoresizingMask"/> + </scroller> + </scrollView> + </subviews> + <constraints> + <constraint firstItem="aFR-JM-AgT" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="-1" id="dbq-rc-NhT"/> + <constraint firstAttribute="trailing" secondItem="aFR-JM-AgT" secondAttribute="trailing" constant="-1" id="pkZ-zZ-B9T"/> + <constraint firstItem="aFR-JM-AgT" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="-1" id="q6u-sa-SjI"/> + <constraint firstAttribute="bottom" secondItem="aFR-JM-AgT" secondAttribute="bottom" constant="-1" id="uKJ-B6-0sE"/> + </constraints> + <point key="canvasLocation" x="-32.5" y="-53"/> + </customView> + <menu id="42P-N1-a6j" userLabel="ContextMenu"> + <items> + <menuItem title="Show Source in Finder" id="Pps-jm-zcd"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="revealSelectedQueueItemsSources:" target="-2" id="Nbh-Zc-E4X"/> + </connections> + </menuItem> + <menuItem title="Show Destination in Finder" id="c1Z-ZC-b8b"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="revealSelectedQueueItems:" target="-2" id="s3C-6U-Ycl"/> + </connections> + </menuItem> + <menuItem isSeparatorItem="YES" id="cVv-nf-8tx"/> + <menuItem title="Edit Job Settings" id="DTF-om-j0l"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="editSelectedQueueItem:" target="-2" id="drH-QA-nh8"/> + </connections> + </menuItem> + <menuItem title="Reset Stopped Job" id="xMs-43-lL0"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="resetJobState:" target="-2" id="nsj-r6-Vd4"/> + </connections> + </menuItem> + <menuItem isSeparatorItem="YES" id="ozr-b8-nig"/> + <menuItem title="Remove All Jobs" id="5AR-we-iHC"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="removeAll:" target="-2" id="IRO-VC-N0n"/> + </connections> + </menuItem> + <menuItem title="Remove Completed Jobs" id="gNj-D1-wa7"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="removeCompleted:" target="-2" id="nUM-Me-Fr2"/> + </connections> + </menuItem> + <menuItem title="Remove Selected Job" id="egD-8R-PTJ"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="removeSelectedQueueItem:" target="-2" id="EIy-d4-yhJ"/> + </connections> + </menuItem> + </items> + <point key="canvasLocation" x="-152" y="642"/> + </menu> + </objects> + <resources> + <image name="Delete" width="16" height="16"/> + <image name="DeleteHighlightPressed" width="16" height="16"/> + <image name="NSActionTemplate" width="14" height="14"/> + </resources> +</document> diff --git a/macosx/Base.lproj/MainMenu.xib b/macosx/Base.lproj/MainMenu.xib index 4c6e39b68..26adb990c 100644 --- a/macosx/Base.lproj/MainMenu.xib +++ b/macosx/Base.lproj/MainMenu.xib @@ -1,8 +1,8 @@ <?xml version="1.0" encoding="UTF-8"?> -<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.68" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none"> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none"> <dependencies> <deployment identifier="macosx"/> - <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.68"/> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/> </dependencies> <objects> <customObject id="-2" userLabel="File's Owner" customClass="NSApplication"> @@ -123,12 +123,12 @@ </menuItem> <menuItem title="Start Encoding" keyEquivalent="s" id="2444"> <connections> - <action selector="rip:" target="-1" id="gca-l6-qac"/> + <action selector="toggleStartCancel:" target="-1" id="l0f-rM-50p"/> </connections> </menuItem> <menuItem title="Pause Encoding" keyEquivalent="p" id="2494"> <connections> - <action selector="pause:" target="-1" id="tq4-oz-OV9"/> + <action selector="togglePauseResume:" target="-1" id="Di1-je-YJZ"/> </connections> </menuItem> </items> diff --git a/macosx/Base.lproj/MainWindow.xib b/macosx/Base.lproj/MainWindow.xib index 1dcd4f4d0..d691c3cb5 100644 --- a/macosx/Base.lproj/MainWindow.xib +++ b/macosx/Base.lproj/MainWindow.xib @@ -1,8 +1,8 @@ <?xml version="1.0" encoding="UTF-8"?> -<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.68" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> <dependencies> <deployment identifier="macosx"/> - <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.68"/> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> </dependencies> <objects> @@ -40,7 +40,7 @@ <windowCollectionBehavior key="collectionBehavior" fullScreenPrimary="YES"/> <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> <rect key="contentRect" x="41" y="572" width="885" height="600"/> - <rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/> + <rect key="screenRect" x="0.0" y="0.0" width="1920" height="1177"/> <view key="contentView" id="2" customClass="HBFocusRingView"> <rect key="frame" x="0.0" y="0.0" width="885" height="600"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> @@ -53,7 +53,7 @@ <tabViewItems> <tabViewItem label="Summary" identifier="" id="BA0-eg-2Ka"> <view key="view" id="BjX-E2-6tb"> - <rect key="frame" x="10" y="25" width="839" height="344"/> + <rect key="frame" x="10" y="29" width="839" height="340"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> </view> </tabViewItem> @@ -428,6 +428,11 @@ </popUpButtonCell> <connections> <accessibilityConnection property="title" destination="5180" id="1bt-ea-b2b"/> + <binding destination="-2" name="enabled" keyPath="self.job" id="pA6-GK-w84"> + <dictionary key="options"> + <string key="NSValueTransformerName">NSIsNotNil</string> + </dictionary> + </binding> <binding destination="-2" name="selectedValue" keyPath="self.job.angle" previousBinding="If4-Fp-R0g" id="3Gk-ma-r2w"> <dictionary key="options"> <string key="NSNullPlaceholder" base64-UTF8="YES"> @@ -435,11 +440,6 @@ IA </string> </dictionary> </binding> - <binding destination="-2" name="enabled" keyPath="self.job" id="pA6-GK-w84"> - <dictionary key="options"> - <string key="NSValueTransformerName">NSIsNotNil</string> - </dictionary> - </binding> <binding destination="-2" name="content" keyPath="self.job.angles" id="If4-Fp-R0g"/> <binding destination="5676" name="hidden" keyPath="values.UseDvdNav" previousBinding="pA6-GK-w84" id="Ocv-Hr-bje"> <dictionary key="options"> @@ -730,12 +730,12 @@ Blu-ray and DVD sources often have multiple titles, the longest of which is typi </toolbarItem> <toolbarItem implicitItemIdentifier="10063EA1-C821-4363-8F59-7840853EB568" label="Start" paletteLabel="Start Encoding" toolTip="Start Encoding" tag="-1" image="encode" id="byg-kj-sEM"> <connections> - <action selector="rip:" target="-2" id="f6w-0B-Qvn"/> + <action selector="toggleStartCancel:" target="-2" id="6gH-Cz-Nuo"/> </connections> </toolbarItem> <toolbarItem implicitItemIdentifier="B4A5CE50-6CD5-4CD0-B639-E1516E1C85C1" label="Pause" paletteLabel="Pause Encoding" toolTip="Pause Encoding" tag="-1" image="pauseencode" id="wTQ-KF-5KW"> <connections> - <action selector="pause:" target="-2" id="AN1-8V-lc1"/> + <action selector="togglePauseResume:" target="-2" id="AN1-8V-lc1"/> </connections> </toolbarItem> <toolbarItem implicitItemIdentifier="3B6A53EE-F973-41A7-A0DF-AEBD73DAC28E" label="Add To Queue" paletteLabel="Add To Queue" toolTip="Add To Queue" tag="-1" image="addqueue" id="DZZ-Fe-wjw"> diff --git a/macosx/Base.lproj/Queue.xib b/macosx/Base.lproj/Queue.xib index bb379a101..bf105a3fa 100644 --- a/macosx/Base.lproj/Queue.xib +++ b/macosx/Base.lproj/Queue.xib @@ -1,18 +1,15 @@ <?xml version="1.0" encoding="UTF-8"?> -<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.59" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> <dependencies> <deployment identifier="macosx"/> - <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.59"/> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> </dependencies> <objects> <customObject id="-2" userLabel="File's Owner" customClass="HBQueueController"> <connections> - <outlet property="countTextField" destination="2511" id="7vs-Ty-tNx"/> <outlet property="pauseToolbarItem" destination="s7o-pK-heI" id="SP7-Fq-gw9"/> - <outlet property="progressTextField" destination="2646" id="E60-Gv-b2q"/> <outlet property="ripToolbarItem" destination="SX6-mq-Hck" id="va2-0n-3T0"/> - <outlet property="tableView" destination="Zhj-ec-Xhl" id="4Ki-XG-eF7"/> <outlet property="window" destination="2576" id="2645"/> </connections> </customObject> @@ -23,205 +20,11 @@ <windowCollectionBehavior key="collectionBehavior" fullScreenPrimary="YES"/> <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> <rect key="contentRect" x="157" y="863" width="583" height="423"/> - <rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/> + <rect key="screenRect" x="0.0" y="0.0" width="1920" height="1177"/> <value key="minSize" type="size" width="525" height="340"/> <view key="contentView" id="2577"> <rect key="frame" x="0.0" y="0.0" width="583" height="423"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> - <subviews> - <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" allowsCharacterPickerTouchBarItem="YES" preferredMaxLayoutWidth="6000" translatesAutoresizingMaskIntoConstraints="NO" id="2511"> - <rect key="frame" x="18" y="399" width="547" height="14"/> - <textFieldCell key="cell" controlSize="small" sendsActionOnEndEditing="YES" title="Pending Jobs" id="2637"> - <font key="font" metaFont="smallSystem"/> - <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> - <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> - </textFieldCell> - </textField> - <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" allowsCharacterPickerTouchBarItem="YES" preferredMaxLayoutWidth="6000" translatesAutoresizingMaskIntoConstraints="NO" id="2646"> - <rect key="frame" x="18" y="365" width="547" height="30"/> - <constraints> - <constraint firstAttribute="height" constant="30" id="IvQ-56-oOt"/> - </constraints> - <textFieldCell key="cell" controlSize="small" sendsActionOnEndEditing="YES" alignment="left" title="There are no jobs currently encoding" id="2647"> - <font key="font" metaFont="smallSystem"/> - <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> - <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> - </textFieldCell> - </textField> - <scrollView autohidesScrollers="YES" horizontalLineScroll="22" horizontalPageScroll="10" verticalLineScroll="22" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="V8p-UJ-HY0"> - <rect key="frame" x="20" y="20" width="543" height="337"/> - <clipView key="contentView" drawsBackground="NO" id="U2w-5W-t6z"> - <rect key="frame" x="1" y="1" width="541" height="335"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> - <subviews> - <tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" alternatingRowBackgroundColors="YES" columnSelection="YES" autosaveColumns="NO" rowHeight="20" rowSizeStyle="automatic" viewBased="YES" id="Zhj-ec-Xhl" customClass="HBTableView"> - <rect key="frame" x="0.0" y="0.0" width="541" height="335"/> - <autoresizingMask key="autoresizingMask"/> - <size key="intercellSpacing" width="3" height="2"/> - <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/> - <color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/> - <tableColumns> - <tableColumn width="538" minWidth="40" maxWidth="3000" id="bXr-Oy-mqu"> - <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left"> - <font key="font" metaFont="smallSystem"/> - <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> - <color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/> - </tableHeaderCell> - <textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="MWq-ie-HjX"> - <font key="font" metaFont="system"/> - <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> - <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/> - </textFieldCell> - <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/> - <prototypeCellViews> - <tableCellView identifier="MainCellForSizing" id="fwz-hO-Ryv" customClass="HBQueueItemView"> - <rect key="frame" x="1" y="1" width="538" height="20"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> - <subviews> - <button horizontalHuggingPriority="750" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="zFo-Hu-qOb"> - <rect key="frame" x="3" y="4" width="13" height="13"/> - <buttonCell key="cell" type="disclosureTriangle" bezelStyle="disclosure" imagePosition="above" alignment="left" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="vwa-tA-Eef"> - <behavior key="behavior" pushIn="YES" changeBackground="YES" changeGray="YES" lightByContents="YES"/> - <font key="font" metaFont="system"/> - </buttonCell> - </button> - <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ytl-nJ-QF2"> - <rect key="frame" x="36" y="2" width="482" height="17"/> - <constraints> - <constraint firstAttribute="height" relation="greaterThanOrEqual" constant="17" id="KX1-hQ-cAs"/> - </constraints> - <textFieldCell key="cell" enabled="NO" allowsUndo="NO" sendsActionOnEndEditing="YES" title="Table View Cell" id="anR-ZS-eOK"> - <font key="font" metaFont="system"/> - <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> - <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> - </textFieldCell> - </textField> - <imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="iLH-IM-e3U"> - <rect key="frame" x="20" y="4" width="14" height="14"/> - <imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NSActionTemplate" id="Fd0-UE-Y9E"/> - </imageView> - <button horizontalHuggingPriority="750" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="3iF-mi-Ofb"> - <rect key="frame" x="519" y="3" width="16" height="16"/> - <constraints> - <constraint firstAttribute="width" constant="16" id="YU8-rD-aOs"/> - <constraint firstAttribute="height" constant="16" id="zUz-oi-p3D"/> - </constraints> - <buttonCell key="cell" type="bevel" bezelStyle="rounded" image="Delete" imagePosition="only" alignment="center" alternateImage="DeleteHighlightPressed" id="6ta-Ak-dc3"> - <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> - <font key="font" metaFont="system"/> - </buttonCell> - </button> - </subviews> - <constraints> - <constraint firstItem="3iF-mi-Ofb" firstAttribute="leading" secondItem="ytl-nJ-QF2" secondAttribute="trailing" constant="3" id="B8U-H8-LVU"/> - <constraint firstItem="3iF-mi-Ofb" firstAttribute="top" secondItem="fwz-hO-Ryv" secondAttribute="top" constant="1" id="Fc7-bU-swQ"/> - <constraint firstAttribute="bottom" secondItem="ytl-nJ-QF2" secondAttribute="bottom" constant="2" id="PoD-SD-75N"/> - <constraint firstItem="zFo-Hu-qOb" firstAttribute="leading" secondItem="fwz-hO-Ryv" secondAttribute="leading" constant="3" id="Tlt-5u-MdO"/> - <constraint firstItem="iLH-IM-e3U" firstAttribute="centerY" secondItem="zFo-Hu-qOb" secondAttribute="centerY" id="XMi-MB-91O"/> - <constraint firstItem="zFo-Hu-qOb" firstAttribute="top" secondItem="fwz-hO-Ryv" secondAttribute="top" constant="3" id="ita-PD-WDw"/> - <constraint firstAttribute="trailing" secondItem="3iF-mi-Ofb" secondAttribute="trailing" constant="3" id="pYB-wp-Ngx"/> - <constraint firstItem="ytl-nJ-QF2" firstAttribute="leading" secondItem="iLH-IM-e3U" secondAttribute="trailing" constant="4" id="uLz-ZO-W6Z"/> - <constraint firstItem="iLH-IM-e3U" firstAttribute="leading" secondItem="zFo-Hu-qOb" secondAttribute="trailing" constant="4" id="utq-Rq-M1K"/> - <constraint firstItem="ytl-nJ-QF2" firstAttribute="top" secondItem="fwz-hO-Ryv" secondAttribute="top" constant="1" id="wzO-Uy-p6R"/> - </constraints> - <connections> - <outlet property="expandButton" destination="zFo-Hu-qOb" id="DCW-dQ-Jt2"/> - <outlet property="imageView" destination="iLH-IM-e3U" id="pnj-VU-8lk"/> - <outlet property="removeButton" destination="3iF-mi-Ofb" id="okE-58-pJ0"/> - <outlet property="textField" destination="ytl-nJ-QF2" id="ujr-58-CSG"/> - </connections> - </tableCellView> - <tableCellView identifier="MainCell" id="WdL-72-BXn" customClass="HBQueueItemView"> - <rect key="frame" x="1" y="23" width="538" height="20"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> - <subviews> - <button horizontalHuggingPriority="750" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hDo-Zl-9lr"> - <rect key="frame" x="3" y="4" width="13" height="13"/> - <buttonCell key="cell" type="disclosureTriangle" bezelStyle="disclosure" imagePosition="above" alignment="left" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="gix-c5-O2h"> - <behavior key="behavior" pushIn="YES" changeBackground="YES" changeGray="YES" lightByContents="YES"/> - <font key="font" metaFont="system"/> - </buttonCell> - </button> - <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="93s-90-w6h"> - <rect key="frame" x="36" y="2" width="482" height="17"/> - <constraints> - <constraint firstAttribute="height" relation="greaterThanOrEqual" constant="17" id="fPa-ff-vgB"/> - </constraints> - <textFieldCell key="cell" enabled="NO" allowsUndo="NO" sendsActionOnEndEditing="YES" title="Table View Cell" id="F1i-sW-mz6"> - <font key="font" metaFont="system"/> - <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> - <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> - </textFieldCell> - </textField> - <imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="fGK-O0-x2n"> - <rect key="frame" x="20" y="4" width="14" height="14"/> - <imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NSActionTemplate" id="sjq-oR-z7j"/> - </imageView> - <button horizontalHuggingPriority="750" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="J7T-PN-aVk"> - <rect key="frame" x="519" y="3" width="16" height="16"/> - <constraints> - <constraint firstAttribute="width" constant="16" id="V0F-7D-TYq"/> - <constraint firstAttribute="height" constant="16" id="fVl-JE-S8p"/> - </constraints> - <buttonCell key="cell" type="bevel" bezelStyle="rounded" image="Delete" imagePosition="only" alignment="center" alternateImage="DeleteHighlightPressed" id="DHN-sj-IkJ"> - <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> - <font key="font" metaFont="system"/> - </buttonCell> - </button> - </subviews> - <constraints> - <constraint firstItem="hDo-Zl-9lr" firstAttribute="leading" secondItem="WdL-72-BXn" secondAttribute="leading" constant="3" id="46G-ka-wQC"/> - <constraint firstItem="J7T-PN-aVk" firstAttribute="top" secondItem="WdL-72-BXn" secondAttribute="top" constant="1" id="Cnn-dM-JY0"/> - <constraint firstAttribute="bottom" secondItem="93s-90-w6h" secondAttribute="bottom" constant="2" id="FUz-Ej-rNL"/> - <constraint firstItem="93s-90-w6h" firstAttribute="leading" secondItem="fGK-O0-x2n" secondAttribute="trailing" constant="4" id="TXB-Wp-bvX"/> - <constraint firstItem="fGK-O0-x2n" firstAttribute="leading" secondItem="hDo-Zl-9lr" secondAttribute="trailing" constant="4" id="agy-M6-bFJ"/> - <constraint firstItem="93s-90-w6h" firstAttribute="top" secondItem="WdL-72-BXn" secondAttribute="top" constant="1" id="mkK-5a-3hE"/> - <constraint firstAttribute="trailing" secondItem="J7T-PN-aVk" secondAttribute="trailing" constant="3" id="n4J-ih-N0C"/> - <constraint firstItem="fGK-O0-x2n" firstAttribute="centerY" secondItem="hDo-Zl-9lr" secondAttribute="centerY" id="uBZ-4T-gyj"/> - <constraint firstItem="J7T-PN-aVk" firstAttribute="leading" secondItem="93s-90-w6h" secondAttribute="trailing" constant="3" id="uCp-vf-aad"/> - <constraint firstItem="hDo-Zl-9lr" firstAttribute="top" secondItem="WdL-72-BXn" secondAttribute="top" constant="3" id="ypH-kh-t70"/> - </constraints> - <connections> - <outlet property="expandButton" destination="hDo-Zl-9lr" id="kd5-U2-oiI"/> - <outlet property="imageView" destination="fGK-O0-x2n" id="6pO-2g-qSk"/> - <outlet property="removeButton" destination="J7T-PN-aVk" id="17N-VN-hCz"/> - <outlet property="textField" destination="93s-90-w6h" id="Bxj-ru-lZr"/> - </connections> - </tableCellView> - </prototypeCellViews> - </tableColumn> - </tableColumns> - <connections> - <outlet property="dataSource" destination="-2" id="TEa-pj-uBd"/> - <outlet property="delegate" destination="-2" id="DkB-HG-P9X"/> - <outlet property="menu" destination="2649" id="3do-4Q-kGX"/> - </connections> - </tableView> - </subviews> - <nil key="backgroundColor"/> - </clipView> - <scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="1UD-VE-aty"> - <rect key="frame" x="1" y="320" width="541" height="16"/> - <autoresizingMask key="autoresizingMask"/> - </scroller> - <scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="Ory-ZM-JQ8"> - <rect key="frame" x="224" y="17" width="15" height="102"/> - <autoresizingMask key="autoresizingMask"/> - </scroller> - </scrollView> - </subviews> - <constraints> - <constraint firstItem="2511" firstAttribute="leading" secondItem="2577" secondAttribute="leading" constant="20" id="8Xc-AN-fPc"/> - <constraint firstAttribute="trailing" secondItem="2511" secondAttribute="trailing" constant="20" id="Eu0-GV-JYK"/> - <constraint firstAttribute="trailing" secondItem="V8p-UJ-HY0" secondAttribute="trailing" constant="20" id="JIB-NW-QyO"/> - <constraint firstAttribute="trailing" secondItem="2646" secondAttribute="trailing" constant="20" id="QhH-jG-52I"/> - <constraint firstItem="2646" firstAttribute="top" secondItem="2511" secondAttribute="bottom" constant="4" id="agE-FW-5eL"/> - <constraint firstAttribute="bottom" secondItem="V8p-UJ-HY0" secondAttribute="bottom" constant="20" id="nkP-qP-x6z"/> - <constraint firstItem="V8p-UJ-HY0" firstAttribute="top" secondItem="2646" secondAttribute="bottom" constant="8" id="pLi-au-o2H"/> - <constraint firstItem="2511" firstAttribute="top" secondItem="2577" secondAttribute="top" constant="10" id="q87-Mh-5mE"/> - <constraint firstItem="2646" firstAttribute="leading" secondItem="2577" secondAttribute="leading" constant="20" id="r9l-aI-qMG"/> - <constraint firstItem="V8p-UJ-HY0" firstAttribute="leading" secondItem="2577" secondAttribute="leading" constant="20" id="uaI-oz-plH"/> - </constraints> </view> <toolbar key="toolbar" implicitIdentifier="0FE76B40-49B7-48AE-B44E-D1B8034BC88A" explicitIdentifier="HBQueueToolbar" displayMode="iconAndLabel" sizeMode="regular" id="ZVb-ld-0UP"> <allowedToolbarItems> @@ -237,9 +40,50 @@ </toolbarItem> <toolbarItem implicitItemIdentifier="NSToolbarSpaceItem" id="rHN-a0-oZQ"/> <toolbarItem implicitItemIdentifier="NSToolbarFlexibleSpaceItem" id="QuV-M8-cet"/> + <toolbarItem implicitItemIdentifier="E914B732-9FA4-42E0-AFA6-353A8ACC406B" label="Actions" paletteLabel="Actions" image="NSActionTemplate" sizingBehavior="auto" id="DCB-WP-NRs"> + <nil key="toolTip"/> + <popUpButton key="view" verticalHuggingPriority="750" id="Wfu-Hw-JBC"> + <rect key="frame" x="4" y="14" width="40" height="24"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <popUpButtonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="border" imageScaling="proportionallyDown" inset="2" pullsDown="YES" id="Zxj-rr-UnA"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="menu"/> + <menu key="menu" id="BBi-6p-GyW"> + <items> + <menuItem image="NSActionTemplate" hidden="YES" id="l0x-1G-MEr"/> + <menuItem title="Reset All Jobs" id="kPX-am-UX1"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="resetAll:" target="-2" id="AYP-Za-Zyi"/> + </connections> + </menuItem> + <menuItem title="Reset Failed Jobs" id="0zn-7P-JbR"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="resetFailed:" target="-2" id="gJP-lg-vxx"/> + </connections> + </menuItem> + <menuItem isSeparatorItem="YES" id="9Kv-XR-d3o"/> + <menuItem title="Remove All Jobs" id="Tvo-xt-sag"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="removeAll:" target="-2" id="RYc-Bg-ORR"/> + </connections> + </menuItem> + <menuItem title="Remove Completed Jobs" id="9tg-YJ-DuI"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="removeCompleted:" target="-2" id="J6v-wU-JAD"/> + </connections> + </menuItem> + </items> + </menu> + </popUpButtonCell> + </popUpButton> + </toolbarItem> <toolbarItem implicitItemIdentifier="938D3EC6-1547-4AAB-86AF-B3FD3C7AF8BD" label="When Done" paletteLabel="When Done" toolTip="Action to perform when encoding is complete." id="a3c-kV-98E"> <size key="minSize" width="100" height="25"/> - <size key="maxSize" width="210" height="25"/> + <size key="maxSize" width="200" height="25"/> <popUpButton key="view" verticalHuggingPriority="750" id="rfS-M1-CnB"> <rect key="frame" x="0.0" y="14" width="200" height="25"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> @@ -262,74 +106,41 @@ </connections> </popUpButton> </toolbarItem> + <toolbarItem implicitItemIdentifier="963C2AD9-6708-4781-9411-3111E9713E3B" label="Details" paletteLabel="Details" image="NSGoLeftTemplate" sizingBehavior="auto" id="DfN-Iw-bzy"> + <nil key="toolTip"/> + <button key="view" verticalHuggingPriority="750" id="KyK-r0-0eJ"> + <rect key="frame" x="6" y="14" width="32" height="25"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="NSGoLeftTemplate" imagePosition="only" alignment="center" lineBreakMode="truncatingTail" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="BjZ-aR-Hpe"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> + </button> + <connections> + <action selector="toggleDetails:" target="-2" id="EcM-0b-BRV"/> + </connections> + </toolbarItem> </allowedToolbarItems> <defaultToolbarItems> <toolbarItem reference="SX6-mq-Hck"/> <toolbarItem reference="s7o-pK-heI"/> - <toolbarItem reference="QuV-M8-cet"/> + <toolbarItem reference="rHN-a0-oZQ"/> + <toolbarItem reference="DCB-WP-NRs"/> <toolbarItem reference="a3c-kV-98E"/> + <toolbarItem reference="QuV-M8-cet"/> + <toolbarItem reference="DfN-Iw-bzy"/> </defaultToolbarItems> </toolbar> <connections> <outlet property="delegate" destination="-2" id="2579"/> </connections> - <point key="canvasLocation" x="-773" y="15"/> + <point key="canvasLocation" x="-948" y="-24"/> </window> - <menu id="2649" userLabel="ContextMenu"> - <items> - <menuItem title="Show Source in Finder" id="gbW-g1-lEc"> - <modifierMask key="keyEquivalentModifierMask"/> - <connections> - <action selector="revealSelectedQueueItemsSources:" target="-2" id="NY5-Sp-e08"/> - </connections> - </menuItem> - <menuItem title="Show Destination in Finder" id="2655"> - <modifierMask key="keyEquivalentModifierMask"/> - <connections> - <action selector="revealSelectedQueueItems:" target="-2" id="qtj-uq-KvZ"/> - </connections> - </menuItem> - <menuItem isSeparatorItem="YES" id="Au5-j1-AAd"/> - <menuItem title="Edit Job Settings" id="2650"> - <modifierMask key="keyEquivalentModifierMask"/> - <connections> - <action selector="editSelectedQueueItem:" target="-2" id="2654"/> - </connections> - </menuItem> - <menuItem title="Reset Stopped Job" id="zy6-ab-ush"> - <modifierMask key="keyEquivalentModifierMask"/> - <connections> - <action selector="resetJobState:" target="-1" id="fxd-BP-VY6"/> - </connections> - </menuItem> - <menuItem isSeparatorItem="YES" id="1ZZ-71-d6P"/> - <menuItem title="Clear All Jobs" id="2652"> - <modifierMask key="keyEquivalentModifierMask"/> - <connections> - <action selector="clearAll:" target="-2" id="Q3d-9G-k0i"/> - </connections> - </menuItem> - <menuItem title="Clear Completed Jobs" id="XdJ-Sl-pwu"> - <modifierMask key="keyEquivalentModifierMask"/> - <connections> - <action selector="clearCompleted:" target="-2" id="LMt-sj-JIh"/> - </connections> - </menuItem> - <menuItem title="Clear Selected Job" id="Wfz-Kj-Vtx"> - <modifierMask key="keyEquivalentModifierMask"/> - <connections> - <action selector="removeSelectedQueueItem:" target="-2" id="i8t-gS-Bi3"/> - </connections> - </menuItem> - </items> - <point key="canvasLocation" x="233" y="753.5"/> - </menu> <userDefaultsController representsSharedInstance="YES" id="z2J-h1-IDv"/> </objects> <resources> - <image name="Delete" width="16" height="16"/> - <image name="DeleteHighlightPressed" width="16" height="16"/> <image name="NSActionTemplate" width="14" height="14"/> + <image name="NSGoLeftTemplate" width="9" height="12"/> <image name="encode" width="32" height="32"/> <image name="pauseencode" width="32" height="32"/> </resources> diff --git a/macosx/HBAppDelegate.h b/macosx/HBAppDelegate.h index 387437b20..3762c62e9 100644 --- a/macosx/HBAppDelegate.h +++ b/macosx/HBAppDelegate.h @@ -6,11 +6,19 @@ #import <Cocoa/Cocoa.h> +@class HBJob; + @interface HBAppDelegate : NSObject <NSApplicationDelegate> - (IBAction)showPreferencesWindow:(id)sender; - (IBAction)showOutputPanel:(id)sender; -- (void)reloadPreset:(id)sender; + +- (IBAction)toggleStartCancel:(id)sender; +- (IBAction)togglePauseResume:(id)sender; + +- (IBAction)reloadPreset:(id)sender; + +- (void)openJob:(HBJob *)job completionHandler:(void (^)(BOOL result))handler; @end diff --git a/macosx/HBAppDelegate.m b/macosx/HBAppDelegate.m index de227e153..d8790fb7c 100644 --- a/macosx/HBAppDelegate.m +++ b/macosx/HBAppDelegate.m @@ -6,6 +6,8 @@ #import "HBAppDelegate.h" +#import "HBQueue.h" + #import "HBUtilities.h" #import "HBPresetsManager.h" #import "HBPreset.h" @@ -28,6 +30,8 @@ @property (unsafe_unretained) IBOutlet NSMenu *presetsMenu; @property (nonatomic, strong) HBPreferencesController *preferencesController; + +@property (nonatomic, strong) HBQueue *queue; @property (nonatomic, strong) HBQueueController *queueController; @property (nonatomic, strong) HBOutputPanelController *outputPanel; @@ -59,9 +63,10 @@ _presetsManager = [[HBPresetsManager alloc] initWithURL:[appSupportURL URLByAppendingPathComponent:PRESET_FILE]]; // Queue - _queueController = [[HBQueueController alloc] initWithURL:[appSupportURL URLByAppendingPathComponent:QUEUE_FILE]]; + _queue = [[HBQueue alloc] initWithURL:[appSupportURL URLByAppendingPathComponent:QUEUE_FILE]]; + _queueController = [[HBQueueController alloc] initWithQueue:_queue]; _queueController.delegate = self; - _mainController = [[HBController alloc] initWithQueue:_queueController presetsManager:_presetsManager]; + _mainController = [[HBController alloc] initWithDelegate:self queue:_queue presetsManager:_presetsManager]; } return self; } @@ -116,13 +121,13 @@ } else { - [self.queueController setEncodingJobsAsPending]; - [self.queueController removeCompletedJobs]; + [self.queue setEncodingJobsAsPending]; + [self.queue removeCompletedAndCancelledItems]; } // Now we re-check the queue array to see if there are // any remaining encodes to be done - if (self.queueController.count) + if (self.queue.items.count) { [self showMainWindow:self]; [self showQueueWindow:self]; @@ -158,7 +163,7 @@ - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)app { - if (self.queueController.core.state != HBStateIdle) + if (self.queue.isEncoding) { NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:NSLocalizedString(@"Are you sure you want to quit HandBrake?", @"Quit Alert -> message")]; @@ -192,6 +197,7 @@ _mainController = nil; _queueController = nil; + _queue = nil; [HBCore closeGlobal]; } @@ -206,7 +212,7 @@ { SEL action = menuItem.action; - if (action == @selector(rip:) || action == @selector(pause:)) + if (action == @selector(toggleStartCancel:) || action == @selector(togglePauseResume:)) { // Delegate the validation to the queue controller return [self.queueController validateMenuItem:menuItem]; @@ -281,21 +287,28 @@ } } +#pragma mark - Rescan job + +- (void)openJob:(HBJob *)job completionHandler:(void (^)(BOOL result))handler +{ + [self.mainController openJob:job completionHandler:handler]; +} + #pragma mark - Menu actions -- (IBAction)rip:(id)sender +- (IBAction)toggleStartCancel:(id)sender { - [self.queueController rip:self]; + [self.queueController toggleStartCancel:sender]; } -- (IBAction)pause:(id)sender +- (IBAction)togglePauseResume:(id)sender { - [self.queueController togglePauseResume:self]; + [self.queueController togglePauseResume:sender]; } - (IBAction)browseSources:(id)sender { - [self.mainController browseSources:self]; + [self.mainController browseSources:sender]; } #pragma mark - Presets Menu actions @@ -310,7 +323,7 @@ - (IBAction)reloadPreset:(id)sender { - [self.mainController reloadPreset:self]; + [self.mainController reloadPreset:sender]; } #pragma mark - Show Window Menu Items @@ -325,7 +338,7 @@ _preferencesController = [[HBPreferencesController alloc] init]; } - [self.preferencesController showWindow:self]; + [self.preferencesController showWindow:sender]; } /** @@ -346,7 +359,7 @@ - (IBAction)showPreviewWindow:(id)sender { - [self.mainController showPreviewWindow:self]; + [self.mainController showPreviewWindow:sender]; } /** diff --git a/macosx/HBController.h b/macosx/HBController.h index 8fb6cfbc7..fa8a9ac42 100644 --- a/macosx/HBController.h +++ b/macosx/HBController.h @@ -6,14 +6,15 @@ #import <Cocoa/Cocoa.h> -@class HBQueueController; +@class HBAppDelegate; +@class HBQueue; @class HBPresetsManager; @class HBJob; @interface HBController : NSWindowController -- (instancetype)initWithQueue:(HBQueueController *)queueController presetsManager:(HBPresetsManager *)manager; +- (instancetype)initWithDelegate:(HBAppDelegate *)delegate queue:(HBQueue *)queue presetsManager:(HBPresetsManager *)manager; - (void)launchAction; @@ -28,10 +29,8 @@ - (IBAction)addToQueue:(id)sender; - (IBAction)addAllTitlesToQueue:(id)sender; -- (void)setQueueInfo:(NSAttributedString *)info progress:(double)progress hidden:(BOOL)hidden; - -- (IBAction)rip:(id)sender; -- (IBAction)pause:(id)sender; +- (IBAction)toggleStartCancel:(id)sender; +- (IBAction)togglePauseResume:(id)sender; - (IBAction)selectPresetFromMenu:(id)sender; diff --git a/macosx/HBController.m b/macosx/HBController.m index 7dbbf7182..40b42ff9b 100644 --- a/macosx/HBController.m +++ b/macosx/HBController.m @@ -33,6 +33,7 @@ #import "HBRenamePresetController.h" #import "HBAutoNamer.h" +#import "HBQueue.h" @import HandBrakeKit; @@ -122,8 +123,11 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; /// The HBCore used for scanning. @property (nonatomic, strong) HBCore *core; -/// The queue controller. -@property (nonatomic, strong) HBQueueController *queue; +/// The app delegate. +@property (nonatomic, strong) HBAppDelegate *delegate; + +/// The queue. +@property (nonatomic, strong) HBQueue *queue; /// Whether the window is visible or occluded, /// useful to avoid updating the UI needlessly @@ -146,7 +150,7 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; @interface HBController (TouchBar) <NSTouchBarProvider, NSTouchBarDelegate> - (void)_touchBar_updateButtonsStateForScanCore:(HBState)state; -- (void)_touchBar_updateButtonsStateForQueueCore:(HBState)state; +- (void)_touchBar_updateQueueButtonsState; - (void)_touchBar_validateUserInterfaceItems; @end @@ -155,7 +159,7 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; @implementation HBController -- (instancetype)initWithQueue:(HBQueueController *)queueController presetsManager:(HBPresetsManager *)manager; +- (instancetype)initWithDelegate:(HBAppDelegate *)delegate queue:(HBQueue *)queue presetsManager:(HBPresetsManager *)manager; { self = [super initWithWindowNibName:@"MainWindow"]; if (self) @@ -168,8 +172,8 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; fPreviewController = [[HBPreviewController alloc] init]; fPreviewController.documentController = self; - _queue = queueController; - _queue.controller = self; + _delegate = delegate; + _queue = queue; presetManager = manager; _currentPreset = manager.defaultPreset; @@ -271,6 +275,8 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; fFiltersViewController = [[HBFiltersViewController alloc] init]; [fFiltersTab setView:[fFiltersViewController view]]; + // Add the observeers + [self.core addObserver:self forKeyPath:@"state" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:HBControllerScanCoreContext]; @@ -283,6 +289,28 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:HBControllerQueueCoreContext]; + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueDidStartNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + self.bottomConstrain.animator.constant = 0; + self->fRipIndicatorShown = YES; + self->fRipIndicator.hidden = NO; + }]; + + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueDidCompleteNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + self.bottomConstrain.animator.constant = -WINDOW_HEIGHT_OFFSET; + self->fRipIndicator.hidden = YES; + self->fRipIndicatorShown = NO; + }]; + + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueProgressNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + self.progressInfo = note.userInfo[HBQueueProgressNotificationInfoKey]; + self.progress = [note.userInfo[HBQueueProgressNotificationPercentKey] doubleValue]; + + if (self->_visible) + { + [self updateProgress]; + } + }]; + self.presetsMenuBuilder = [[HBPresetsMenuBuilder alloc] initWithMenu:self.presetsPopup.menu action:@selector(selectPresetFromMenu:) size:[NSFont smallSystemFontSize] @@ -349,12 +377,11 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; } else if (context == HBControllerQueueCoreContext) { - HBState state = self.queue.core.state; - [self updateToolbarButtonsStateForQueueCore:state]; + [self updateToolbarButtonsState]; [self.window.toolbar validateVisibleItems]; if (@available(macOS 10.12.2, *)) { - [self _touchBar_updateButtonsStateForQueueCore:state]; + [self _touchBar_updateQueueButtonsState]; [self _touchBar_validateUserInterfaceItems]; } NSUInteger count = self.queue.pendingItemsCount; @@ -382,9 +409,9 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; } } -- (void)updateToolbarButtonsStateForQueueCore:(HBState)state +- (void)updateToolbarButtonsState { - if (state == HBStatePaused) + if (self.queue.canResume) { _pauseToolbarItem.image = [NSImage imageNamed: @"encode"]; _pauseToolbarItem.label = NSLocalizedString(@"Resume", @"Toolbar Pause Item"); @@ -397,7 +424,7 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; _pauseToolbarItem.toolTip = NSLocalizedString(@"Pause Encoding", @"Toolbar Pause Item"); } - if (state == HBStateScanning || state == HBStateWorking || state == HBStateSearching || state == HBStateMuxing || state == HBStatePaused) + if (self.queue.isEncoding) { _ripToolbarItem.image = [NSImage imageNamed:@"stopencode"]; _ripToolbarItem.label = NSLocalizedString(@"Stop", @"Toolbar Start/Stop Item"); @@ -443,7 +470,7 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; { return YES; } - if (action == @selector(rip:) || action == @selector(addToQueue:)) + if (action == @selector(toggleStartCancel:) || action == @selector(addToQueue:)) { return NO; } @@ -453,30 +480,20 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; return YES; } - HBState queueState = _queue.core.state; - - if (action == @selector(rip:)) + if (action == @selector(toggleStartCancel:)) { - if (queueState == HBStateScanning || queueState == HBStateWorking || queueState == HBStateSearching || - queueState == HBStateMuxing || queueState == HBStatePaused) + if (self.queue.isEncoding) { return YES; } else { - return (self.job != nil || _queue.pendingItemsCount > 0); + return (self.job != nil || self.queue.canEncode); } } - if (action == @selector(pause:)) { - if (queueState == HBStatePaused) - { - return YES; - } - else - { - return (queueState == HBStateWorking || queueState == HBStateMuxing); - } + if (action == @selector(togglePauseResume:)) { + return self.queue.canPause || self.queue.canResume; } if (action == @selector(addToQueue:)) @@ -505,11 +522,11 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; { return self.window.attachedSheet == nil; } - if (action == @selector(pause:)) + if (action == @selector(togglePauseResume:)) { return [_queue validateMenuItem:menuItem]; } - if (action == @selector(rip:)) + if (action == @selector(toggleStartCancel:)) { BOOL result = [_queue validateMenuItem:menuItem]; @@ -1005,38 +1022,6 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; fRipIndicator.doubleValue = self.progress; } -- (void)setQueueInfo:(NSAttributedString *)info progress:(double)progress hidden:(BOOL)hidden -{ - self.progressInfo = info; - self.progress = progress; - - if (_visible) - { - [self updateProgress]; - } - - if (hidden) - { - if (fRipIndicatorShown) - { - self.bottomConstrain.animator.constant = -WINDOW_HEIGHT_OFFSET; - fRipIndicator.hidden = YES; - fRipIndicatorShown = NO; - } - } - else - { - // If progress bar hasn't been revealed at the bottom of the window, do - // that now. - if (!fRipIndicatorShown) - { - self.bottomConstrain.animator.constant = 0; - fRipIndicatorShown = YES; - fRipIndicator.hidden = NO; - } - } -} - #pragma mark - Job Handling /** @@ -1073,7 +1058,7 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; [alert beginSheetModalForWindow:self.window completionHandler:handler]; } - else if ([_queue jobExistAtURL:job.completeOutputURL]) + else if ([_queue itemExistAtURL:job.completeOutputURL]) { NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:NSLocalizedString(@"There is already a queue item for this destination.", @"File already exists in queue alert -> message")]; @@ -1123,24 +1108,19 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; [self doAddToQueue]; } - [_queue rip:self]; + [_delegate toggleStartCancel:self]; } /** * Puts up an alert before ultimately calling doRip */ -- (IBAction)rip:(id)sender +- (IBAction)toggleStartCancel:(id)sender { // Rip or Cancel ? - if (_queue.core.state == HBStateWorking || _queue.core.state == HBStatePaused || _queue.core.state == HBStateSearching) + if (_queue.isEncoding || _queue.canEncode) { // Displays an alert asking user if the want to cancel encoding of current job. - [_queue cancelRip:self]; - } - // If there are pending jobs in the queue, then this is a rip the queue - else if (_queue.pendingItemsCount > 0) - { - [_queue rip:self]; + [_delegate toggleStartCancel:self]; } else { @@ -1156,9 +1136,9 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; } } -- (IBAction)pause:(id)sender +- (IBAction)togglePauseResume:(id)sender { - [_queue togglePauseResume:sender]; + [_delegate togglePauseResume:sender]; } #pragma mark - @@ -1213,7 +1193,7 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; [destinations addObject:job.completeOutputURL]; } - if ([[NSFileManager defaultManager] fileExistsAtPath:job.completeOutputURL.path] || [_queue jobExistAtURL:job.completeOutputURL]) + if ([[NSFileManager defaultManager] fileExistsAtPath:job.completeOutputURL.path] || [_queue itemExistAtURL:job.completeOutputURL]) { fileExists = YES; break; @@ -1248,13 +1228,13 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; [alert beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) { if (returnCode == NSAlertSecondButtonReturn) { - [self->_queue addJobsFromArray:jobs]; + [self->_queue addJobs:jobs]; } }]; } else { - [_queue addJobsFromArray:jobs]; + [_queue addJobs:jobs]; } } @@ -1537,7 +1517,7 @@ static NSTouchBarItemIdentifier HBTouchBarActivity = @"fr.handbrake.activity"; NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; item.customizationLabel = NSLocalizedString(@"Start/Stop Encoding", @"Touch bar"); - NSButton *button = [NSButton buttonWithImage:[NSImage imageNamed:NSImageNameTouchBarPlayTemplate] target:self action:@selector(rip:)]; + NSButton *button = [NSButton buttonWithImage:[NSImage imageNamed:NSImageNameTouchBarPlayTemplate] target:self action:@selector(toggleStartCancel:)]; item.view = button; return item; @@ -1547,7 +1527,7 @@ static NSTouchBarItemIdentifier HBTouchBarActivity = @"fr.handbrake.activity"; NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; item.customizationLabel = NSLocalizedString(@"Pause Encoding", @"Touch bar"); - NSButton *button = [NSButton buttonWithImage:[NSImage imageNamed:NSImageNameTouchBarPauseTemplate] target:self action:@selector(pause:)]; + NSButton *button = [NSButton buttonWithImage:[NSImage imageNamed:NSImageNameTouchBarPauseTemplate] target:self action:@selector(togglePauseResume:)]; item.view = button; return item; @@ -1592,24 +1572,26 @@ static NSTouchBarItemIdentifier HBTouchBarActivity = @"fr.handbrake.activity"; } } -- (void)_touchBar_updateButtonsStateForQueueCore:(HBState)state; +- (void)_touchBar_updateQueueButtonsState { NSButton *ripButton = (NSButton *)[[self.touchBar itemForIdentifier:HBTouchBarRip] view]; NSButton *pauseButton = (NSButton *)[[self.touchBar itemForIdentifier:HBTouchBarPause] view]; - if (state == HBStateScanning || state == HBStateWorking || state == HBStateSearching || state == HBStateMuxing) + if (self.queue.isEncoding) { ripButton.image = [NSImage imageNamed:NSImageNameTouchBarRecordStopTemplate]; - pauseButton.image = [NSImage imageNamed:NSImageNameTouchBarPauseTemplate]; } - else if (state == HBStatePaused) + else + { + ripButton.image = [NSImage imageNamed:NSImageNameTouchBarPlayTemplate]; + } + + if (self.queue.canResume) { - ripButton.image = [NSImage imageNamed:NSImageNameTouchBarRecordStopTemplate]; pauseButton.image = [NSImage imageNamed:NSImageNameTouchBarPlayTemplate]; } - else if (state == HBStateIdle) + else { - ripButton.image = [NSImage imageNamed:NSImageNameTouchBarPlayTemplate]; pauseButton.image = [NSImage imageNamed:NSImageNameTouchBarPauseTemplate]; } } diff --git a/macosx/HBJob+UIAdditions.h b/macosx/HBJob+UIAdditions.h index 99dcc10c8..7099f08b0 100644 --- a/macosx/HBJob+UIAdditions.h +++ b/macosx/HBJob+UIAdditions.h @@ -16,7 +16,9 @@ @property (nonatomic, readonly) NSArray<NSString *> *containers; @property (nonatomic, readonly) NSAttributedString *attributedTitleDescription; + @property (nonatomic, readonly) NSAttributedString *attributedDescription; +@property (nonatomic, readonly) NSAttributedString *attributedExpandedDescription; @property (nonatomic, readonly) NSString *shortDescription; @property (nonatomic, readonly) NSString *filtersShortDescription; diff --git a/macosx/HBJob+UIAdditions.m b/macosx/HBJob+UIAdditions.m index 4f711bacb..28c7edfe5 100644 --- a/macosx/HBJob+UIAdditions.m +++ b/macosx/HBJob+UIAdditions.m @@ -119,13 +119,8 @@ static NSDictionary *shortHeightAttr; } } -- (NSAttributedString *)titleAttributedDescription +- (NSString *)rangeDescription { - NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] init]; - - // Job name - [attrString appendString:self.description withAttributes:titleAttr]; - // Range type NSString *startStopString = @""; if (self.range.type == HBRangeTypeChapters) @@ -163,16 +158,37 @@ static NSDictionary *shortHeightAttr; if (passesString.length) { - [attrString appendString:[NSString stringWithFormat:HBKitLocalizedString(@" (Title %d, %@, %@) ▸ %@\n", @"Title description"), - self.titleIdx, startStopString, passesString, self.outputFileName] - withAttributes:detailAttr]; + return [NSString stringWithFormat:HBKitLocalizedString(@"Title %d, %@, %@", @"Title description"), + self.titleIdx, startStopString, passesString]; } else { - [attrString appendString:[NSString stringWithFormat:HBKitLocalizedString(@" (Title %d, %@) ▸ %@\n", @"Title description"), - self.titleIdx, startStopString, self.outputFileName] - withAttributes:detailAttr]; + return [NSString stringWithFormat:HBKitLocalizedString(@"Title %d, %@", @"Title description"), + self.titleIdx, startStopString]; } +} + +- (NSAttributedString *)rangeAttributedDescription +{ + NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] init]; + + [attrString appendString:@"\t" withAttributes:detailAttr]; + [attrString appendString:HBKitLocalizedString(@"Range:", @"Range description") withAttributes:detailBoldAttr]; + [attrString appendString:@" \t" withAttributes:detailAttr]; + [attrString appendString:self.rangeDescription withAttributes:detailAttr]; + [attrString appendString:@"\n" withAttributes:detailAttr]; + + return attrString; +} +- (NSAttributedString *)titleAttributedDescription +{ + NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] init]; + + // Job name + [attrString appendString:self.description withAttributes:titleAttr]; + + [attrString appendString:[NSString stringWithFormat:@" (%@) ▸ %@\n", [self rangeDescription], self.outputFileName] + withAttributes:detailAttr]; return attrString; } @@ -231,6 +247,19 @@ static NSDictionary *shortHeightAttr; return attrString; } +- (NSAttributedString *)sourceAttributedDescription +{ + NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] init]; + + [attrString appendString:@"\t" withAttributes:detailAttr]; + [attrString appendString:HBKitLocalizedString(@"Source:", @"Source description") withAttributes:detailBoldAttr]; + [attrString appendString:@" \t" withAttributes:detailAttr]; + [attrString appendString:self.fileURL.path withAttributes:detailAttr]; + [attrString appendString:@"\n" withAttributes:detailAttr]; + + return attrString; +} + - (NSAttributedString *)destinationAttributedDescription { NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] init]; @@ -629,21 +658,31 @@ static NSDictionary *shortHeightAttr; @autoreleasepool { - [attrString appendAttributedString:[self titleAttributedDescription]]; [attrString appendAttributedString:[self presetAttributedDescription]]; + [attrString appendString:@"\n" withAttributes: detailAttr]; + [attrString appendAttributedString:[self sourceAttributedDescription]]; + [attrString appendString:@"\n" withAttributes: detailAttr]; + [attrString appendAttributedString:[self destinationAttributedDescription]]; + [attrString appendString:@"\n" withAttributes: detailAttr]; [attrString appendAttributedString:[self formatAttributedDescription]]; + [attrString appendString:@"\n" withAttributes: detailAttr]; + [attrString appendAttributedString:[self rangeAttributedDescription]]; + [attrString appendString:@"\n" withAttributes: detailAttr]; [attrString appendAttributedString:[self dimensionsAttributedDescription]]; + [attrString appendString:@"\n" withAttributes: detailAttr]; [attrString appendAttributedString:[self filtersAttributedDescription]]; + [attrString appendString:@"\n" withAttributes: detailAttr]; [attrString appendAttributedString:[self videoAttributedDescription]]; + [attrString appendString:@"\n" withAttributes: detailAttr]; if (self.audio.countOfTracks > 1) { [attrString appendAttributedString:[self audioAttributedDescription]]; + [attrString appendString:@"\n" withAttributes: detailAttr]; } if (self.subtitles.countOfTracks > 1) { [attrString appendAttributedString:[self subtitlesAttributedDescription]]; } - [attrString appendAttributedString:[self destinationAttributedDescription]]; } [attrString deleteCharactersInRange:NSMakeRange(attrString.length - 1, 1)]; @@ -651,6 +690,7 @@ static NSDictionary *shortHeightAttr; return attrString; } + #pragma mark - Short descriptions - (NSString *)videoShortDescription diff --git a/macosx/HBQueue.h b/macosx/HBQueue.h new file mode 100644 index 000000000..0e176292b --- /dev/null +++ b/macosx/HBQueue.h @@ -0,0 +1,93 @@ +/* HBQueue.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> + +#import "HBCore.h" +#import "HBDistributedArray.h" +#import "HBQueueItem.h" +#import "HBJobOutputFileWriter.h" + +NS_ASSUME_NONNULL_BEGIN + +extern NSString * const HBQueueDidAddItemNotification; +extern NSString * const HBQueueDidRemoveItemNotification; +extern NSString * const HBQueueDidChangeItemNotification; +extern NSString * const HBQueueItemNotificationIndexesKey; // NSIndexSet + +extern NSString * const HBQueueDidMoveItemNotification; +extern NSString * const HBQueueItemNotificationSourceIndexesKey; // NSArray<NSNumber *> +extern NSString * const HBQueueItemNotificationTargetIndexesKey; // NSArray<NSNumber *> + +extern NSString * const HBQueueReloadItemsNotification; + +extern NSString * const HBQueueLowSpaceAlertNotification; + +extern NSString * const HBQueueProgressNotification; +extern NSString * const HBQueueProgressNotificationPercentKey; // NSNumber - double +extern NSString * const HBQueueProgressNotificationInfoKey; // NSString + +extern NSString * const HBQueueDidStartNotification; +extern NSString * const HBQueueDidCompleteNotification; + +extern NSString * const HBQueueDidCompleteItemNotification; +extern NSString * const HBQueueDidCompleteItemNotificationItemKey; // HBQueueItem + +@interface HBQueue : NSObject + +- (instancetype)initWithURL:(NSURL *)queueURL; + +@property (nonatomic, readonly) NSURL *queueURL; + +@property (nonatomic, readonly) HBCore *core; +@property (nonatomic, readonly) HBDistributedArray<HBQueueItem *> *items; + +@property (nonatomic, nullable) HBQueueItem *currentItem; +@property (nonatomic, nullable) HBJobOutputFileWriter *currentLog; + +@property (nonatomic) NSUInteger pendingItemsCount; +@property (nonatomic) NSUInteger completedItemsCount; + +@property (nonatomic) NSUndoManager *undoManager; + +- (void)addJob:(HBJob *)job; +- (void)addJobs:(NSArray<HBJob *> *)jobs; + +- (void)addQueueItems:(NSArray<HBQueueItem *> *)items atIndexes:(NSIndexSet *)indexes; +- (void)removeQueueItemAtIndex:(NSUInteger)index; +- (void)removeQueueItemsAtIndexes:(NSIndexSet *)indexes; +- (void)moveQueueItems:(NSArray<HBQueueItem *> *)items toIndex:(NSUInteger)index; + +- (BOOL)itemExistAtURL:(NSURL *)url; + +- (void)removeAllItems; +- (void)removeCompletedAndCancelledItems; +- (void)removeNotWorkingItems; +- (void)removeCompletedItems; + +- (void)resetItemsStateAtIndexes:(NSIndexSet *)indexes; +- (void)resetAllItems; +- (void)resetFailedItems; + +- (void)setEncodingJobsAsPending; + +@property (nonatomic, readonly) BOOL canEncode; +@property (nonatomic, readonly) BOOL isEncoding; + +- (void)start; +- (void)cancelCurrentItemAndContinue; +- (void)finishCurrentAndStop; +- (void)cancelCurrentItemAndStop; + +@property (nonatomic, readonly) BOOL canPause; +- (void)pause; + +@property (nonatomic, readonly) BOOL canResume; +- (void)resume; + +@end + +NS_ASSUME_NONNULL_END diff --git a/macosx/HBQueue.m b/macosx/HBQueue.m new file mode 100644 index 000000000..6aee92452 --- /dev/null +++ b/macosx/HBQueue.m @@ -0,0 +1,765 @@ +/* HBQueue.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 "HBQueue.h" +#import "NSArray+HBAdditions.h" + +NSString * const HBQueueDidAddItemNotification = @"HBQueueDidAddItemNotification"; +NSString * const HBQueueDidRemoveItemNotification = @"HBQueueDidRemoveItemNotification"; +NSString * const HBQueueDidChangeItemNotification = @"HBQueueDidChangeItemNotification"; + +NSString * const HBQueueItemNotificationIndexesKey = @"HBQueueReloadItemsNotification"; + +NSString * const HBQueueDidMoveItemNotification = @"HBQueueDidMoveItemNotification"; +NSString * const HBQueueItemNotificationSourceIndexesKey = @"HBQueueItemNotificationSourceIndexesKey"; +NSString * const HBQueueItemNotificationTargetIndexesKey = @"HBQueueItemNotificationTargetIndexesKey"; + +NSString * const HBQueueReloadItemsNotification = @"HBQueueReloadItemsNotification"; + +NSString * const HBQueueLowSpaceAlertNotification = @"HBQueueLowSpaceAlertNotification"; + +NSString * const HBQueueProgressNotification = @"HBQueueProgressNotification"; +NSString * const HBQueueProgressNotificationPercentKey = @"HBQueueProgressNotificationPercentKey"; +NSString * const HBQueueProgressNotificationInfoKey = @"HBQueueProgressNotificationInfoKey"; + +NSString * const HBQueueDidStartNotification = @"HBQueueDidStartNotification"; +NSString * const HBQueueDidCompleteNotification = @"HBQueueDidCompleteNotification"; + +NSString * const HBQueueDidCompleteItemNotification = @"HBQueueDidCompleteItemNotification"; +NSString * const HBQueueDidCompleteItemNotificationItemKey = @"HBQueueDidCompleteItemNotificationItemKey"; + +@interface HBQueue () + +@property (nonatomic) BOOL stop; + +@end + +@implementation HBQueue + +- (instancetype)initWithURL:(NSURL *)queueURL +{ + self = [super init]; + if (self) + { + NSInteger loggingLevel = [NSUserDefaults.standardUserDefaults integerForKey:@"LoggingLevel"]; + + // Init a separate instance of libhb for the queue + _core = [[HBCore alloc] initWithLogLevel:loggingLevel name:@"QueueCore"]; + _core.automaticallyPreventSleep = NO; + + _items = [[HBDistributedArray alloc] initWithURL:queueURL class:[HBQueueItem class]]; + + // Set up the observers + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadQueue) name:HBDistributedArrayChanged object:_items]; + + [self updateStats]; + } + return self; +} + +#pragma mark - Public methods + +- (void)addJob:(HBJob *)item +{ + NSParameterAssert(item); + [self addJobs:@[item]]; +} + +- (void)addJobs:(NSArray<HBJob *> *)jobs; +{ + NSParameterAssert(jobs); + + NSMutableArray<HBQueueItem *> *itemsToAdd = [NSMutableArray array]; + for (HBJob *job in jobs) + { + HBQueueItem *item = [[HBQueueItem alloc] initWithJob:job]; + [itemsToAdd addObject:item]; + } + if (itemsToAdd.count) + { + NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(self.items.count, itemsToAdd.count)]; + [self addQueueItems:itemsToAdd atIndexes:indexes]; + } +} + +- (BOOL)itemExistAtURL:(NSURL *)url +{ + NSParameterAssert(url); + + for (HBQueueItem *item in self.items) + { + if ((item.state == HBQueueItemStateReady || item.state == HBQueueItemStateWorking) + && [item.completeOutputURL isEqualTo:url]) + { + return YES; + } + } + return NO; +} + +- (NSUInteger)count +{ + return self.items.count; +} + +- (void)addQueueItems:(NSArray<HBQueueItem *> *)items atIndexes:(NSIndexSet *)indexes +{ + NSParameterAssert(items); + NSParameterAssert(indexes); + [self.items beginTransaction]; + + // Forward + NSUInteger currentIndex = indexes.firstIndex; + NSUInteger currentObjectIndex = 0; + while (currentIndex != NSNotFound) + { + [self.items insertObject:items[currentObjectIndex] atIndex:currentIndex]; + currentIndex = [indexes indexGreaterThanIndex:currentIndex]; + currentObjectIndex++; + } + + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidAddItemNotification object:self userInfo:@{HBQueueItemNotificationIndexesKey: indexes}]; + + NSUndoManager *undo = self.undoManager; + [[undo prepareWithInvocationTarget:self] removeQueueItemsAtIndexes:indexes]; + + if (!undo.isUndoing) + { + if (items.count == 1) + { + [undo setActionName:NSLocalizedString(@"Add Job To Queue", @"Queue undo action name")]; + } + else + { + [undo setActionName:NSLocalizedString(@"Add Jobs To Queue", @"Queue undo action name")]; + } + } + + [self updateStats]; + [self.items commit]; +} + +- (void)removeQueueItemAtIndex:(NSUInteger)index +{ + [self removeQueueItemsAtIndexes:[NSIndexSet indexSetWithIndex:index]]; +} + +- (void)removeQueueItemsAtIndexes:(NSIndexSet *)indexes +{ + NSParameterAssert(indexes); + + if (indexes.count == 0) + { + return; + } + + [self.items beginTransaction]; + + NSArray<HBQueueItem *> *removeItems = [self.items objectsAtIndexes:indexes]; + + if (self.items.count > indexes.lastIndex) + { + [self.items removeObjectsAtIndexes:indexes]; + } + + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidRemoveItemNotification object:self userInfo:@{HBQueueItemNotificationIndexesKey: indexes}]; + + NSUndoManager *undo = self.undoManager; + [[undo prepareWithInvocationTarget:self] addQueueItems:removeItems atIndexes:indexes]; + + if (!undo.isUndoing) + { + if (indexes.count == 1) + { + [undo setActionName:NSLocalizedString(@"Remove Job From Queue", @"Queue undo action name")]; + } + else + { + [undo setActionName:NSLocalizedString(@"Remove Jobs From Queue", @"Queue undo action name")]; + } + } + + [self updateStats]; + [self.items commit]; +} + +- (void)moveQueueItems:(NSArray<HBQueueItem *> *)items toIndex:(NSUInteger)index +{ + [self.items beginTransaction]; + + NSMutableArray<NSNumber *> *source = [NSMutableArray array]; + NSMutableArray<NSNumber *> *dest = [NSMutableArray array]; + + for (id object in items.reverseObjectEnumerator) + { + NSUInteger sourceIndex = [self.items indexOfObject:object]; + [self.items removeObjectAtIndex:sourceIndex]; + + if (sourceIndex < index) + { + index--; + } + + [self.items insertObject:object atIndex:index]; + + [source addObject:@(index)]; + [dest addObject:@(sourceIndex)]; + } + + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidMoveItemNotification + object:self + userInfo:@{HBQueueItemNotificationSourceIndexesKey: dest, + HBQueueItemNotificationTargetIndexesKey: source}]; + + NSUndoManager *undo = self.undoManager; + [[undo prepareWithInvocationTarget:self] moveQueueItemsAtIndexes:source toIndexes:dest]; + + if (!undo.isUndoing) + { + if (items.count == 1) + { + [undo setActionName:NSLocalizedString(@"Move Job in Queue", @"Queue undo action name")]; + } + else + { + [undo setActionName:NSLocalizedString(@"Move Jobs in Queue", @"Queue undo action name")]; + } + } + + [self.items commit]; +} + +- (void)moveQueueItemsAtIndexes:(NSArray *)source toIndexes:(NSArray *)dest +{ + [self.items beginTransaction]; + + NSMutableArray<NSNumber *> *newSource = [NSMutableArray array]; + NSMutableArray<NSNumber *> *newDest = [NSMutableArray array]; + + for (NSInteger idx = source.count - 1; idx >= 0; idx--) + { + NSUInteger sourceIndex = [source[idx] integerValue]; + NSUInteger destIndex = [dest[idx] integerValue]; + + [newSource addObject:@(destIndex)]; + [newDest addObject:@(sourceIndex)]; + + id obj = [self.items objectAtIndex:sourceIndex]; + [self.items removeObjectAtIndex:sourceIndex]; + [self.items insertObject:obj atIndex:destIndex]; + } + + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidMoveItemNotification + object:self + userInfo:@{HBQueueItemNotificationSourceIndexesKey: newDest, + HBQueueItemNotificationTargetIndexesKey: newSource}]; + + NSUndoManager *undo = self.undoManager; + [[undo prepareWithInvocationTarget:self] moveQueueItemsAtIndexes:newSource toIndexes:newDest]; + + if (!undo.isUndoing) + { + if (source.count == 1) + { + [undo setActionName:NSLocalizedString(@"Move Job in Queue", @"Queue undo action name")]; + } + else + { + [undo setActionName:NSLocalizedString(@"Move Jobs in Queue", @"Queue undo action name")]; + } + } + + [self.items commit]; +} + +/** + * This method will clear the queue of any encodes that are not still pending + * this includes both successfully completed encodes as well as canceled encodes + */ +- (void)removeCompletedAndCancelledItems +{ + [self.items beginTransaction]; + NSIndexSet *indexes = [self.items indexesOfObjectsUsingBlock:^BOOL(HBQueueItem *item) { + return (item.state == HBQueueItemStateCompleted || item.state == HBQueueItemStateCanceled); + }]; + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidRemoveItemNotification object:self userInfo:@{@"indexes": indexes}]; + [self.items commit]; +} + +/** + * This method will clear the queue of all encodes. effectively creating an empty queue + */ +- (void)removeAllItems +{ + [self.items beginTransaction]; + + [self removeQueueItemsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.items.count)]]; + [self.items commit]; +} + +- (void)removeNotWorkingItems +{ + [self.items beginTransaction]; + NSIndexSet *indexes = [self.items indexesOfObjectsUsingBlock:^BOOL(HBQueueItem *item) { + return (item.state != HBQueueItemStateWorking); + }]; + [self removeQueueItemsAtIndexes:indexes]; + [self.items commit]; +} + +- (void)removeCompletedItems +{ + [self.items beginTransaction]; + NSIndexSet *indexes = [self.items indexesOfObjectsUsingBlock:^BOOL(HBQueueItem *item) { + return (item.state == HBQueueItemStateCompleted); + }]; + [self removeQueueItemsAtIndexes:indexes]; + [self.items commit]; +} + +- (void)resetItemsStateAtIndexes:(NSIndexSet *)indexes +{ + if ([self.items beginTransaction] == HBDistributedArrayContentReload) + { + // Do not execture the action if the array changed. + [self.items commit]; + return; + } + + NSMutableIndexSet *updatedIndexes = [NSMutableIndexSet indexSet]; + + NSUInteger currentIndex = indexes.firstIndex; + while (currentIndex != NSNotFound) { + HBQueueItem *item = self.items[currentIndex]; + + if (item.state == HBQueueItemStateCanceled || item.state == HBQueueItemStateCompleted || item.state == HBQueueItemStateFailed) + { + item.state = HBQueueItemStateReady; + [updatedIndexes addIndex:currentIndex]; + } + currentIndex = [indexes indexGreaterThanIndex:currentIndex]; + } + + [self updateStats]; + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidChangeItemNotification object:self userInfo:@{HBQueueItemNotificationIndexesKey: indexes}]; + [self.items commit]; +} + +- (void)resetAllItems +{ + [self.items beginTransaction]; + NSIndexSet *indexes = [self.items indexesOfObjectsUsingBlock:^BOOL(HBQueueItem *item) { + return (item.state != HBQueueItemStateWorking); + }]; + [self resetItemsStateAtIndexes:indexes]; + [self.items commit]; +} + +- (void)resetFailedItems +{ + [self.items beginTransaction]; + NSIndexSet *indexes = [self.items indexesOfObjectsUsingBlock:^BOOL(HBQueueItem *item) { + return (item.state == HBQueueItemStateFailed); + }]; + [self resetItemsStateAtIndexes:indexes]; + [self.items commit]; +} + +/** + * This method will set any item marked as encoding back to pending + * currently used right after a queue reload + */ +- (void)setEncodingJobsAsPending +{ + [self.items beginTransaction]; + + NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet]; + NSUInteger idx = 0; + for (HBQueueItem *item in self.items) + { + // We want to keep any queue item that is pending or was previously being encoded + if (item.state == HBQueueItemStateWorking) + { + item.state = HBQueueItemStateReady; + [indexes addIndex:idx]; + } + idx++; + } + + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidChangeItemNotification object:self userInfo:@{HBQueueItemNotificationIndexesKey: indexes}]; + [self.items commit]; +} + +- (BOOL)canEncode +{ + return self.pendingItemsCount > 0; +} + +- (BOOL)isEncoding +{ + HBState s = self.core.state; + return (s == HBStateScanning) || (s == HBStatePaused) || (s == HBStateWorking) || (s == HBStateMuxing) || (s == HBStateSearching); +} + +- (BOOL)canPause +{ + HBState s = self.core.state; + return (s == HBStateWorking || s == HBStateMuxing); +} + +- (void)pause +{ + [self.core pause]; + [self.core allowSleep]; +} + +- (BOOL)canResume +{ + return self.core.state == HBStatePaused; +} + +- (void)resume +{ + [self.core resume]; + [self.core preventSleep]; +} + +#pragma mark - Private queue editing methods + +/** + * Reloads the queue, this is called + * when another HandBrake instances modifies the queue + */ +- (void)reloadQueue +{ + [self updateStats]; + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueReloadItemsNotification object:self]; +} + +- (void)updateStats +{ + // lets get the stats on the status of the queue array + NSUInteger pendingCount = 0; + NSUInteger completedCount = 0; + + for (HBQueueItem *item in self.items) + { + if (item.state == HBQueueItemStateReady) + { + pendingCount++; + } + if (item.state == HBQueueItemStateCompleted) + { + completedCount++; + } + } + + self.pendingItemsCount = pendingCount; + self.completedItemsCount = completedCount; +} + +- (BOOL)_isDiskSpaceLowAtURL:(NSURL *)url +{ + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"HBQueuePauseIfLowSpace"]) + { + NSURL *volumeURL = nil; + NSDictionary<NSURLResourceKey, id> *attrs = [url resourceValuesForKeys:@[NSURLIsVolumeKey, NSURLVolumeURLKey] error:NULL]; + long long minCapacity = [[[NSUserDefaults standardUserDefaults] stringForKey:@"HBQueueMinFreeSpace"] longLongValue] * 1000000000; + + volumeURL = [attrs[NSURLIsVolumeKey] boolValue] ? url : attrs[NSURLVolumeURLKey]; + + if (volumeURL) + { + [volumeURL removeCachedResourceValueForKey:NSURLVolumeAvailableCapacityKey]; + attrs = [volumeURL resourceValuesForKeys:@[NSURLVolumeAvailableCapacityKey] error:NULL]; + + if (attrs[NSURLVolumeAvailableCapacityKey]) + { + if ([attrs[NSURLVolumeAvailableCapacityKey] longLongValue] < minCapacity) + { + return YES; + } + } + } + } + + return NO; +} + +/** + * Used to get the next pending queue item and return it if found + */ +- (HBQueueItem *)getNextPendingQueueItem +{ + for (HBQueueItem *item in self.items) + { + if (item.state == HBQueueItemStateReady) + { + return item; + } + } + return nil; +} + +- (void)start +{ + if (self.canEncode && self.core.state == HBStateIdle) + { + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidStartNotification object:self]; + [self.core preventSleep]; + [self encodeNextQueueItem]; + } +} + +/** + * Starts the queue + */ +- (void)encodeNextQueueItem +{ + [self.items beginTransaction]; + self.currentItem = nil; + + // since we have completed an encode, we go to the next + if (self.stop) + { + [HBUtilities writeToActivityLog:"Queue manually stopped"]; + + self.stop = NO; + [self.core allowSleep]; + + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidCompleteNotification object:self]; + } + else + { + // Check to see if there are any more pending items in the queue + HBQueueItem *nextItem = [self getNextPendingQueueItem]; + + if (nextItem && [self _isDiskSpaceLowAtURL:nextItem.outputURL]) + { + // Disk space is low, show an alert + [HBUtilities writeToActivityLog:"Queue Stopped, low space on destination disk"]; + [self.core allowSleep]; + + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidCompleteNotification object:self]; + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueLowSpaceAlertNotification object:self]; + } + // If we still have more pending items in our queue, lets go to the next one + else if (nextItem) + { + // now we mark the queue item as working so another instance can not come along and try to scan it while we are scanning + nextItem.state = HBQueueItemStateWorking; + + // Tell HB to output a new activity log file for this encode + self.currentLog = [[HBJobOutputFileWriter alloc] initWithJob:nextItem.job]; + if (self.currentLog) + { + [[HBOutputRedirect stderrRedirect] addListener:self.currentLog]; + [[HBOutputRedirect stdoutRedirect] addListener:self.currentLog]; + } + + self.currentItem = nextItem; + NSIndexSet *indexes = [NSIndexSet indexSetWithIndex:[self.items indexOfObject:nextItem]]; + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidChangeItemNotification object:self userInfo:@{HBQueueItemNotificationIndexesKey: indexes}]; + + [self updateStats]; + + // now we can go ahead and scan the new pending queue item + [self encodeItem:nextItem]; + + // erase undo manager history + [self.undoManager removeAllActions]; + } + else + { + [HBUtilities writeToActivityLog:"Queue Done, there are no more pending encodes"]; + [self.core allowSleep]; + + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidCompleteNotification object:self]; + } + } + [self.items commit]; +} + +- (void)completedItem:(HBQueueItem *)item result:(HBCoreResult)result; +{ + NSParameterAssert(item); + [self.items beginTransaction]; + + // Since we are done with this encode, tell output to stop writing to the + // individual encode log. + [[HBOutputRedirect stderrRedirect] removeListener:self.currentLog]; + [[HBOutputRedirect stdoutRedirect] removeListener:self.currentLog]; + + self.currentLog = nil; + + // Mark the encode just finished + switch (result) { + case HBCoreResultDone: + item.state = HBQueueItemStateCompleted; + break; + case HBCoreResultCanceled: + item.state = HBQueueItemStateCanceled; + break; + default: + item.state = HBQueueItemStateFailed; + break; + } + + // Update UI + NSString *info = nil; + switch (result) { + case HBCoreResultDone: + info = NSLocalizedString(@"Encode Finished.", @"Queue status"); + break; + case HBCoreResultCanceled: + info = NSLocalizedString(@"Encode Canceled.", @"Queue status"); + break; + default: + info = NSLocalizedString(@"Encode Failed.", @"Queue status"); + break; + } + + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueProgressNotification object:self userInfo:@{HBQueueProgressNotificationPercentKey: @1.0, + HBQueueProgressNotificationInfoKey: info}]; + + NSInteger index = [self.items indexOfObject:item]; + NSIndexSet *indexes = index > -1 ? [NSIndexSet indexSetWithIndex:index] : [NSIndexSet indexSet]; + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidCompleteItemNotification object:self userInfo:@{HBQueueDidCompleteItemNotificationItemKey: item, + HBQueueItemNotificationIndexesKey: indexes}]; + + [self.items commit]; +} + +/** + * Here we actually tell hb_scan to perform the source scan, using the path to source and title number + */ +- (void)encodeItem:(HBQueueItem *)item +{ + NSParameterAssert(item); + + // Progress handler + void (^progressHandler)(HBState state, HBProgress progress, NSString *info) = ^(HBState state, HBProgress progress, NSString *info) + { + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueProgressNotification object:self userInfo:@{HBQueueProgressNotificationPercentKey: @0, + HBQueueProgressNotificationInfoKey: info}]; + }; + + // Completion handler + void (^completionHandler)(HBCoreResult result) = ^(HBCoreResult result) + { + if (result == HBCoreResultDone) + { + [self realEncodeItem:item]; + } + else + { + [self completedItem:item result:result]; + [self encodeNextQueueItem]; + } + }; + + // Only scan 10 previews before an encode - additional previews are + // only useful for autocrop and static previews, which are already taken care of at this point + [self.core scanURL:item.fileURL + titleIndex:item.job.titleIdx + previews:10 + minDuration:0 + progressHandler:progressHandler + completionHandler:completionHandler]; +} + +/** + * This assumes that we have re-scanned and loaded up a new queue item to send to libhb + */ +- (void)realEncodeItem:(HBQueueItem *)item +{ + NSParameterAssert(item); + + HBJob *job = item.job; + + // Reset the title in the job. + job.title = self.core.titles.firstObject; + + NSParameterAssert(job); + + HBStateFormatter *formatter = [[HBStateFormatter alloc] init]; + formatter.title = job.outputFileName; + self.core.stateFormatter = formatter; + + // Progress handler + void (^progressHandler)(HBState state, HBProgress progress, NSString *info) = ^(HBState state, HBProgress progress, NSString *info) + { + if (state == HBStateMuxing) + { + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueProgressNotification + object:self + userInfo:@{HBQueueProgressNotificationPercentKey: @1, + HBQueueProgressNotificationInfoKey: info}]; + } + else + { + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueProgressNotification + object:self + userInfo:@{HBQueueProgressNotificationPercentKey: @(progress.percent), + HBQueueProgressNotificationInfoKey: info}]; + } + }; + + // Completion handler + void (^completionHandler)(HBCoreResult result) = ^(HBCoreResult result) + { + [self completedItem:item result:result]; + [self encodeNextQueueItem]; + }; + + // We should be all setup so let 'er rip + [self.core encodeJob:job progressHandler:progressHandler completionHandler:completionHandler]; + + // We are done using the title, remove it from the job + job.title = nil; +} + +/** + * Cancels the current job + */ +- (void)doCancelCurrentItem +{ + if (self.core.state == HBStateScanning) + { + [self.core cancelScan]; + } + else + { + [self.core cancelEncode]; + } +} + +/** + * Cancels the current job and starts processing the next in queue. + */ +- (void)cancelCurrentItemAndContinue +{ + [self doCancelCurrentItem]; +} + +/** + * Cancels the current job and stops libhb from processing the remaining encodes. + */ +- (void)cancelCurrentItemAndStop +{ + self.stop = YES; + [self doCancelCurrentItem]; +} + +/** + * Finishes the current job and stops libhb from processing the remaining encodes. + */ +- (void)finishCurrentAndStop +{ + self.stop = YES; +} + +@end diff --git a/macosx/HBQueueController.h b/macosx/HBQueueController.h index 597b68a1d..722416cd1 100644 --- a/macosx/HBQueueController.h +++ b/macosx/HBQueueController.h @@ -10,36 +10,17 @@ NS_ASSUME_NONNULL_BEGIN @class HBAppDelegate; @class HBController; -@class HBOutputPanelController; -@class HBCore; -@class HBJob; +@class HBQueue; @interface HBQueueController : NSWindowController <NSToolbarDelegate, NSWindowDelegate> -- (instancetype)initWithURL:(NSURL *)queueURL; +- (instancetype)initWithQueue:(HBQueue *)queue; -/// The HBCore used for encoding. -@property (nonatomic, readonly) HBCore *core; +@property (nonatomic, readonly) HBQueue *queue; -@property (nonatomic, assign, nullable) HBController *controller; @property (nonatomic, weak, nullable) HBAppDelegate *delegate; -@property (nonatomic, readonly) NSUInteger count; -@property (nonatomic, readonly) NSUInteger pendingItemsCount; - -- (void)addJob:(HBJob *)item; -- (void)addJobsFromArray:(NSArray<HBJob *> *)items; - -- (BOOL)jobExistAtURL:(NSURL *)url; - -- (void)removeAllJobs; -- (void)removeCompletedJobs; - -- (void)setEncodingJobsAsPending; - -- (IBAction)rip:(id)sender; -- (IBAction)cancelRip:(id)sender; - +- (IBAction)toggleStartCancel:(id)sender; - (IBAction)togglePauseResume:(id)sender; @end diff --git a/macosx/HBQueueController.m b/macosx/HBQueueController.m index 87d0ceefe..40ae17334 100644 --- a/macosx/HBQueueController.m +++ b/macosx/HBQueueController.m @@ -6,112 +6,65 @@ #import "HBQueueController.h" -#import "HBQueueItem.h" - -#import "HBController.h" #import "HBAppDelegate.h" -#import "HBTableView.h" -#import "HBQueueItemView.h" - -#import "NSArray+HBAdditions.h" -#import "HBUtilities.h" +#import "HBQueue.h" +#import "HBQueueTableViewController.h" +#import "HBQueueDetailsViewController.h" #import "HBDockTile.h" - -#import "HBOutputRedirect.h" -#import "HBJobOutputFileWriter.h" #import "HBPreferencesController.h" +#import "NSArray+HBAdditions.h" @import HandBrakeKit; -// Pasteboard type for or drag operations -#define DragDropSimplePboardType @"HBQueueCustomTableViewPboardType" - -// DockTile update frequency in total percent increment -#define dockTileUpdateFrequency 0.1f - static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; -@interface HBQueueController () <NSTableViewDataSource, HBTableViewDelegate, HBQueueItemViewDelegate, NSUserNotificationCenterDelegate> +@interface HBQueueController () <NSUserNotificationCenterDelegate, HBQueueTableViewControllerDelegate, HBQueueDetailsViewControllerDelegate> + +@property (weak) IBOutlet NSSplitView *splitView; +@property (nonatomic) NSSplitViewController *splitViewController; +@property (nonatomic) HBQueueTableViewController *tableViewController; +@property (nonatomic) HBQueueDetailsViewController *detailsViewController; /// Whether the window is visible or occluded, /// useful to avoid updating the UI needlessly @property (nonatomic) BOOL visible; -// Progress -@property (nonatomic, strong) NSAttributedString *progressInfo; -@property (nonatomic, strong) NSDictionary *monospacedAttr; - @property (nonatomic, readonly) HBDockTile *dockTile; -@property (nonatomic, readwrite) double dockIconProgress; - -@property (unsafe_unretained) IBOutlet NSTextField *progressTextField; -@property (unsafe_unretained) IBOutlet NSTextField *countTextField; -@property (unsafe_unretained) IBOutlet HBTableView *tableView; +@property (nonatomic) double dockIconProgress; @property (nonatomic) IBOutlet NSToolbarItem *ripToolbarItem; @property (nonatomic) IBOutlet NSToolbarItem *pauseToolbarItem; -@property (nonatomic) NSTableCellView *dummyCell; -@property (nonatomic) NSLayoutConstraint *dummyCellWidth; - -@property (nonatomic, readonly) HBDistributedArray<HBQueueItem *> *items; - -@property (nonatomic) HBQueueItem *currentItem; -@property (nonatomic) HBJobOutputFileWriter *currentLog; - -@property (nonatomic, readwrite) BOOL stop; - -@property (nonatomic, readwrite) NSUInteger pendingItemsCount; -@property (nonatomic, readwrite) NSUInteger completedItemsCount; - -@property (nonatomic) NSArray<HBQueueItem *> *dragNodesArray; - @end @interface HBQueueController (TouchBar) <NSTouchBarProvider, NSTouchBarDelegate> -- (void)_touchBar_updateButtonsStateForQueueCore:(HBState)state; +- (void)_touchBar_updateButtonsState; - (void)_touchBar_validateUserInterfaceItems; @end @implementation HBQueueController -- (instancetype)initWithURL:(NSURL *)queueURL; +- (instancetype)initWithQueue:(HBQueue *)queue { - NSParameterAssert(queueURL); + NSParameterAssert(queue); if (self = [super initWithWindowNibName:@"Queue"]) { // Load the dockTile and instiante initial text fields - _dockTile = [[HBDockTile alloc] initWithDockTile:[[NSApplication sharedApplication] dockTile] - image:[[NSApplication sharedApplication] applicationIconImage]]; - - NSInteger loggingLevel = [[NSUserDefaults standardUserDefaults] integerForKey:@"LoggingLevel"]; - - // Init a separate instance of libhb for the queue - _core = [[HBCore alloc] initWithLogLevel:loggingLevel name:@"QueueCore"]; - _core.automaticallyPreventSleep = NO; + _dockTile = [[HBDockTile alloc] initWithDockTile:NSApplication.sharedApplication.dockTile + image:NSApplication.sharedApplication.applicationIconImage]; - // Progress - _monospacedAttr = @{NSFontAttributeName: [NSFont monospacedDigitSystemFontOfSize:[NSFont smallSystemFontSize] weight:NSFontWeightRegular]}; - _progressInfo = [[NSAttributedString alloc] initWithString:@""]; + // Init state + _queue = queue; - // Load the queue from disk. - _items = [[HBDistributedArray alloc] initWithURL:queueURL class:[HBQueueItem class]]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadQueue) name:HBDistributedArrayChanged object:_items]; - - [NSUserNotificationCenter defaultUserNotificationCenter].delegate = self; + NSUserNotificationCenter.defaultUserNotificationCenter.delegate = self; } return self; } -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - - (void)windowDidLoad { if (@available (macOS 10.12, *)) @@ -119,33 +72,116 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; self.window.tabbingMode = NSWindowTabbingModeDisallowed; } - // lets setup our queue list table view for drag and drop here - [self.tableView registerForDraggedTypes:@[DragDropSimplePboardType]]; - [self.tableView setDraggingSourceOperationMask:NSDragOperationEvery forLocal:YES]; - [self.tableView setVerticalMotionCanBeginDrag:YES]; + _queue.undoManager = self.window.undoManager; + + // Set up the child view controllers + _splitViewController = [[NSSplitViewController alloc] init]; + _splitViewController.splitView = _splitView; + _splitViewController.view.wantsLayer = YES; + _splitViewController.splitView.vertical = YES; + _splitViewController.splitView.autosaveName = @"HBQueueSplitViewAutosave"; + _splitViewController.splitView.identifier = @"HBQueueSplitViewIdentifier"; + + _tableViewController = [[HBQueueTableViewController alloc] initWithQueue:self.queue delegate:self]; + _detailsViewController = [[HBQueueDetailsViewController alloc] initWithDelegate:self]; + + NSSplitViewItem *tableItem = [NSSplitViewItem splitViewItemWithViewController:_tableViewController]; + tableItem.minimumThickness = 160; - [self updateQueueStats]; + [_splitViewController addSplitViewItem:tableItem]; - [self.core addObserver:self forKeyPath:@"state" + NSSplitViewItem *detailsItem = [NSSplitViewItem splitViewItemWithViewController:_detailsViewController]; + detailsItem.canCollapse = YES; + detailsItem.minimumThickness = 240; + + [_splitViewController addSplitViewItem:detailsItem]; + + self.window.contentViewController = _splitViewController; + + self.window.frameAutosaveName = @"HBQueueWindowFrameAutosave"; + [self.window setFrameFromString: @"HBQueueWindowFrameAutosave"]; + + // Set up observers + [self.queue.core addObserver:self forKeyPath:@"state" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:HBControllerQueueCoreContext]; - [self addObserver:self forKeyPath:@"pendingItemsCount" + [self.queue addObserver:self forKeyPath:@"pendingItemsCount" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:HBControllerQueueCoreContext]; + + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueLowSpaceAlertNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + [self queueLowDiskSpaceAlert]; + }]; + + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueDidCompleteNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + // Since there are no more items to encode, go to queueCompletedAlerts + // for user specified alerts after queue completed + [self queueCompletedAlerts]; + }]; + + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueProgressNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + // Update dock icon + double progress = [note.userInfo[HBQueueProgressNotificationPercentKey] doubleValue]; + double hours = 1; + double minutes = 1; + double seconds = 1; + +#define dockTileUpdateFrequency 0.1f + + if (self.dockIconProgress < 100.0 * progress) + { + [self.dockTile updateDockIcon:progress hours:hours minutes:minutes seconds:seconds]; + self.dockIconProgress += dockTileUpdateFrequency; + } + }]; + + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueDidCompleteItemNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + // Restore dock icon + [self.dockTile updateDockIcon:-1.0 withETA:@""]; + self.dockIconProgress = 0; + + // Run the per item notification and actions + HBQueueItem *item = note.userInfo[HBQueueDidCompleteItemNotificationItemKey]; + if (item.state == HBQueueItemStateCompleted) + { + [self sendToExternalApp:item]; + } + + if (item.state == HBQueueItemStateCompleted || item.state == HBQueueItemStateFailed) + { + [self itemCompletedAlerts:item]; + } + }]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (context == HBControllerQueueCoreContext) { - HBState state = self.core.state; - [self updateToolbarButtonsStateForQueueCore:state]; + [self updateToolbarButtonsState]; [self.window.toolbar validateVisibleItems]; + if (@available(macOS 10.12.2, *)) { - [self _touchBar_updateButtonsStateForQueueCore:state]; + [self _touchBar_updateButtonsState]; [self _touchBar_validateUserInterfaceItems]; } + + NSString *string; + if (self.queue.pendingItemsCount == 0) + { + string = NSLocalizedString(@"No encode pending", @"Queue status"); + } + else if (self.queue.pendingItemsCount == 1) + { + string = [NSString stringWithFormat: NSLocalizedString(@"%d encode pending", @"Queue status"), self.queue.pendingItemsCount]; + } + else + { + string = [NSString stringWithFormat: NSLocalizedString(@"%d encodes pending", @"Queue status"), self.queue.pendingItemsCount]; + } + + self.window.title = [NSString stringWithFormat: NSLocalizedString(@"Queue (%@)", @"Queue window title"), string]; } else { @@ -155,9 +191,9 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; #pragma mark Toolbar -- (void)updateToolbarButtonsStateForQueueCore:(HBState)state +- (void)updateToolbarButtonsState { - if (state == HBStatePaused) + if (self.queue.canResume) { _pauseToolbarItem.image = [NSImage imageNamed: @"encode"]; _pauseToolbarItem.label = NSLocalizedString(@"Resume", @"Toolbar Pause Item"); @@ -170,7 +206,7 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; _pauseToolbarItem.toolTip = NSLocalizedString(@"Pause Encoding", @"Toolbar Pause Item"); } - if (state == HBStateScanning || state == HBStateWorking || state == HBStateSearching || state == HBStateMuxing || state == HBStatePaused) + if (self.queue.isEncoding) { _ripToolbarItem.image = [NSImage imageNamed:@"stopencode"]; _ripToolbarItem.label = NSLocalizedString(@"Stop", @"Toolbar Start/Stop Item"); @@ -188,25 +224,23 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; { SEL action = menuItem.action; - if (action == @selector(rip:)) + if (action == @selector(toggleStartCancel:)) { - if (self.core.state == HBStateIdle) - { - menuItem.title = NSLocalizedString(@"Start Encoding", @"Queue -> start/stop menu"); - - return (self.pendingItemsCount > 0); - } - else if (self.core.state != HBStateIdle) + if (self.queue.isEncoding) { menuItem.title = NSLocalizedString(@"Stop Encoding", @"Queue -> start/stop menu"); - return YES; } + else + { + menuItem.title = NSLocalizedString(@"Start Encoding", @"Queue -> start/stop menu"); + return self.queue.canEncode; + } } - if (action == @selector(pause:)) + if (action == @selector(togglePauseResume:)) { - if (self.core.state != HBStatePaused) + if (self.queue.canPause) { menuItem.title = NSLocalizedString(@"Pause Encoding", @"Queue -> pause/resume menu"); } @@ -215,30 +249,7 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; menuItem.title = NSLocalizedString(@"Resume Encoding", @"Queue -> pause/resume men"); } - return (self.core.state == HBStateWorking || self.core.state == HBStatePaused); - } - - if (action == @selector(editSelectedQueueItem:) || - action == @selector(removeSelectedQueueItem:) || - action == @selector(revealSelectedQueueItems:) || - action == @selector(revealSelectedQueueItemsSources:)) - { - return (self.tableView.selectedRow != -1 || self.tableView.clickedRow != -1); - } - - if (action == @selector(resetJobState:)) - { - return self.tableView.targetedRowIndexes.count > 0; - } - - if (action == @selector(clearAll:)) - { - return self.items.count > 0; - } - - if (action == @selector(clearCompleted:)) - { - return self.completedItemsCount > 0; + return self.queue.canPause || self.queue.canResume; } return YES; @@ -246,30 +257,14 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; - (BOOL)validateUserIterfaceItemForAction:(SEL)action { - HBState s = self.core.state; - if (action == @selector(toggleStartCancel:)) { - if ((s == HBStateScanning) || (s == HBStatePaused) || (s == HBStateWorking) || (s == HBStateMuxing)) - { - return YES; - } - else - { - return (self.pendingItemsCount > 0); - } + return self.queue.isEncoding || self.queue.canEncode; } if (action == @selector(togglePauseResume:)) { - if (s == HBStatePaused) - { - return YES; - } - else - { - return (s == HBStateWorking || s == HBStateMuxing); - } + return self.queue.canPause || self.queue.canResume; } return NO; @@ -281,668 +276,160 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; return [self validateUserIterfaceItemForAction:action]; } -#pragma mark - Public methods -- (void)addJob:(HBJob *)item -{ - NSParameterAssert(item); - [self addJobsFromArray:@[item]]; -} - -- (void)addJobsFromArray:(NSArray<HBJob *> *)jobs; -{ - NSParameterAssert(jobs); - NSMutableArray *itemsToAdd = [NSMutableArray array]; - for (HBJob *job in jobs) - { - HBQueueItem *item = [[HBQueueItem alloc] initWithJob:job]; - [itemsToAdd addObject:item]; - } - if (itemsToAdd.count) - { - [self addQueueItems:itemsToAdd]; - } -} - -- (BOOL)jobExistAtURL:(NSURL *)url -{ - NSParameterAssert(url); - - for (HBQueueItem *item in self.items) - { - if ((item.state == HBQueueItemStateReady || item.state == HBQueueItemStateWorking) - && [item.completeOutputURL isEqualTo:url]) - { - return YES; - } - } - return NO; -} - -- (NSUInteger)count -{ - return self.items.count; -} - -/** - * This method will clear the queue of any encodes that are not still pending - * this includes both successfully completed encodes as well as canceled encodes - */ -- (void)removeCompletedJobs -{ - [self.items beginTransaction]; - NSIndexSet *indexes = [self.items indexesOfObjectsUsingBlock:^BOOL(HBQueueItem *item) { - return (item.state == HBQueueItemStateCompleted || item.state == HBQueueItemStateCanceled); - }]; - [self removeQueueItemsAtIndexes:indexes]; - [self.items commit]; -} - -/** - * This method will clear the queue of all encodes. effectively creating an empty queue - */ -- (void)removeAllJobs -{ - [self.items beginTransaction]; - [self removeQueueItemsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.items.count)]]; - [self.items commit]; -} - -/** - * This method will set any item marked as encoding back to pending - * currently used right after a queue reload - */ -- (void)setEncodingJobsAsPending +- (void)windowDidChangeOcclusionState:(NSNotification *)notification { - [self.items beginTransaction]; - - NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet]; - NSUInteger idx = 0; - for (HBQueueItem *item in self.items) - { - // We want to keep any queue item that is pending or was previously being encoded - if (item.state == HBQueueItemStateWorking) - { - item.state = HBQueueItemStateReady; - [indexes addIndex:idx]; - } - idx++; - } - [self reloadQueueItemsAtIndexes:indexes]; - [self.items commit]; + self.visible = self.window.occlusionState & NSWindowOcclusionStateVisible ? YES : NO; } #pragma mark - Private queue editing methods /** - * Reloads the queue, this is called - * when another HandBrake instances modifies the queue + * Delete encodes from the queue window and accompanying array + * Also handling first cancelling the encode if in fact its currently encoding. */ -- (void)reloadQueue -{ - [self updateQueueStats]; - [self.tableView reloadData]; - [self.window.undoManager removeAllActions]; -} - -- (void)reloadQueueItemAtIndex:(NSUInteger)idx -{ - [self reloadQueueItemsAtIndexes:[NSIndexSet indexSetWithIndex:idx]]; -} - -- (void)reloadQueueItemsAtIndexes:(NSIndexSet *)indexes -{ - NSIndexSet *columnIndexes = [NSIndexSet indexSetWithIndex:0]; - [self.tableView reloadDataForRowIndexes:indexes columnIndexes:columnIndexes]; - [self updateQueueStats]; -} - -- (void)addQueueItems:(NSArray<HBQueueItem *> *)items -{ - NSParameterAssert(items); - NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(self.items.count, items.count)]; - [self addQueueItems:items atIndexes:indexes]; -} - -- (void)addQueueItems:(NSArray *)items atIndexes:(NSIndexSet *)indexes -{ - NSParameterAssert(items); - NSParameterAssert(indexes); - [self.items beginTransaction]; - [self.tableView beginUpdates]; - - // Forward - NSUInteger currentIndex = indexes.firstIndex; - NSUInteger currentObjectIndex = 0; - while (currentIndex != NSNotFound) - { - [self.items insertObject:items[currentObjectIndex] atIndex:currentIndex]; - currentIndex = [indexes indexGreaterThanIndex:currentIndex]; - currentObjectIndex++; - } - - [self.tableView insertRowsAtIndexes:indexes - withAnimation:NSTableViewAnimationSlideDown]; - - NSUndoManager *undo = self.window.undoManager; - [[undo prepareWithInvocationTarget:self] removeQueueItemsAtIndexes:indexes]; - - if (!undo.isUndoing) - { - if (items.count == 1) - { - [undo setActionName:NSLocalizedString(@"Add Job To Queue", @"Queue undo action name")]; - } - else - { - [undo setActionName:NSLocalizedString(@"Add Jobs To Queue", @"Queue undo action name")]; - } - } - - [self.tableView endUpdates]; - [self updateQueueStats]; - [self.items commit]; -} - -- (void)removeQueueItemAtIndex:(NSUInteger)index -{ - [self removeQueueItemsAtIndexes:[NSIndexSet indexSetWithIndex:index]]; -} - - (void)removeQueueItemsAtIndexes:(NSIndexSet *)indexes { - NSParameterAssert(indexes); - - if (indexes.count == 0) + if ([self.queue.items beginTransaction] == HBDistributedArrayContentReload) { + // Do not execture the action if the array changed. + [self.queue.items commit]; return; } - [self.items beginTransaction]; - [self.tableView beginUpdates]; - - NSArray<HBQueueItem *> *removeItems = [self.items objectsAtIndexes:indexes]; - - if (self.items.count > indexes.lastIndex) - { - [self.items removeObjectsAtIndexes:indexes]; - } - - [self.tableView removeRowsAtIndexes:indexes withAnimation:NSTableViewAnimationSlideUp]; - [self.tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:indexes.firstIndex] byExtendingSelection:NO]; - - NSUndoManager *undo = self.window.undoManager; - [[undo prepareWithInvocationTarget:self] addQueueItems:removeItems atIndexes:indexes]; - - if (!undo.isUndoing) - { - if (indexes.count == 1) - { - [undo setActionName:NSLocalizedString(@"Remove Job From Queue", @"Queue undo action name")]; - } - else - { - [undo setActionName:NSLocalizedString(@"Remove Jobs From Queue", @"Queue undo action name")]; - } - } - - [self.tableView endUpdates]; - [self updateQueueStats]; - [self.items commit]; -} - -- (void)moveQueueItems:(NSArray *)items toIndex:(NSUInteger)index -{ - [self.items beginTransaction]; - [self.tableView beginUpdates]; - - NSMutableArray *source = [NSMutableArray array]; - NSMutableArray *dest = [NSMutableArray array]; - - for (id object in items.reverseObjectEnumerator) + if (indexes.count) { - NSUInteger sourceIndex = [self.items indexOfObject:object]; - [self.items removeObjectAtIndex:sourceIndex]; - - - if (sourceIndex < index) - { - index--; - } - - [self.items insertObject:object atIndex:index]; - - [source addObject:@(index)]; - [dest addObject:@(sourceIndex)]; - - [self.tableView moveRowAtIndex:sourceIndex toIndex:index]; - } - - NSUndoManager *undo = self.window.undoManager; - [[undo prepareWithInvocationTarget:self] moveQueueItemsAtIndexes:source toIndexes:dest]; + NSMutableIndexSet *mutableIndexes = [indexes mutableCopy]; + // if this is a currently encoding job, we need to be sure to alert the user, + // to let them decide to cancel it first, then if they do, we can come back and + // remove it + NSIndexSet *workingIndexes = [self.queue.items indexesOfObjectsUsingBlock:^BOOL(HBQueueItem *item) { + return item.state == HBQueueItemStateWorking; + }]; - if (!undo.isUndoing) - { - if (items.count == 1) + if ([mutableIndexes containsIndexes:workingIndexes]) { - [undo setActionName:NSLocalizedString(@"Move Job in Queue", @"Queue undo action name")]; - } - else - { - [undo setActionName:NSLocalizedString(@"Move Jobs in Queue", @"Queue undo action name")]; - } - } - - [self.tableView endUpdates]; - [self.items commit]; -} - -- (void)moveQueueItemsAtIndexes:(NSArray *)source toIndexes:(NSArray *)dest -{ - [self.items beginTransaction]; - [self.tableView beginUpdates]; - - NSMutableArray *newSource = [NSMutableArray array]; - NSMutableArray *newDest = [NSMutableArray array]; + [mutableIndexes removeIndexes:workingIndexes]; + NSArray<HBQueueItem *> *workingItems = [self.queue.items filteredArrayUsingBlock:^BOOL(HBQueueItem *item) { + return item.state == HBQueueItemStateWorking; + }]; - for (NSInteger idx = source.count - 1; idx >= 0; idx--) - { - NSUInteger sourceIndex = [source[idx] integerValue]; - NSUInteger destIndex = [dest[idx] integerValue]; + if ([workingItems containsObject:self.queue.currentItem]) + { + NSString *alertTitle = [NSString stringWithFormat:NSLocalizedString(@"Stop This Encode and Remove It?", @"Queue Stop Alert -> stop and remove message")]; - [newSource addObject:@(destIndex)]; - [newDest addObject:@(sourceIndex)]; + // Which window to attach the sheet to? + NSWindow *targetWindow = self.window; - id obj = [self.items objectAtIndex:sourceIndex]; - [self.items removeObjectAtIndex:sourceIndex]; - [self.items insertObject:obj atIndex:destIndex]; + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:alertTitle]; + [alert setInformativeText:NSLocalizedString(@"Your movie will be lost if you don't continue encoding.", @"Queue Stop Alert -> stop and remove informative text")]; + [alert addButtonWithTitle:NSLocalizedString(@"Keep Encoding", @"Queue Stop Alert -> stop and remove first button")]; + [alert addButtonWithTitle:NSLocalizedString(@"Stop Encoding and Delete", @"Queue Stop Alert -> stop and remove second button")]; + [alert setAlertStyle:NSAlertStyleCritical]; - [self.tableView moveRowAtIndex:sourceIndex toIndex:destIndex]; - } + [alert beginSheetModalForWindow:targetWindow completionHandler:^(NSModalResponse returnCode) { + if (returnCode == NSAlertSecondButtonReturn) + { + [self.queue.items beginTransaction]; - NSUndoManager *undo = self.window.undoManager; - [[undo prepareWithInvocationTarget:self] moveQueueItemsAtIndexes:newSource toIndexes:newDest]; + NSInteger index = [self.queue.items indexOfObject:self.queue.currentItem]; + [self.queue cancelCurrentItemAndContinue]; - if (!undo.isUndoing) - { - if (source.count == 1) - { - [undo setActionName:NSLocalizedString(@"Move Job in Queue", @"Queue undo action name")]; - } - else - { - [undo setActionName:NSLocalizedString(@"Move Jobs in Queue", @"Queue undo action name")]; + [self.queue removeQueueItemAtIndex:index]; + [self.queue.items commit]; + } + }]; + } } - } - - [self.tableView endUpdates]; - [self.items commit]; -} - -- (void)windowDidChangeOcclusionState:(NSNotification *)notification -{ - if ([self.window occlusionState] & NSWindowOcclusionStateVisible) - { - self.visible = YES; - self.progressTextField.attributedStringValue = self.progressInfo; - } - else - { - self.visible = NO; - } -} -- (void)updateProgress:(NSString *)info progress:(double)progress hidden:(BOOL)hidden -{ - self.progressInfo = [[NSAttributedString alloc] initWithString:info attributes:_monospacedAttr]; - if (self.visible) - { - self.progressTextField.attributedStringValue = _progressInfo; + // remove the non working items immediately + [self.queue removeQueueItemsAtIndexes:mutableIndexes]; } - [self.controller setQueueInfo:_progressInfo progress:progress hidden:hidden]; + [self.queue.items commit]; } -/** - * Updates the queue status label. - */ -- (void)updateQueueStats +- (void)doEditQueueItem:(HBQueueItem *)item { - // lets get the stats on the status of the queue array - NSUInteger pendingCount = 0; - NSUInteger completedCount = 0; - - for (HBQueueItem *item in self.items) - { - if (item.state == HBQueueItemStateReady) - { - pendingCount++; - } - if (item.state == HBQueueItemStateCompleted) - { - completedCount++; - } - } - - NSString *string; - if (pendingCount == 0) - { - string = NSLocalizedString(@"No encode pending", @"Queue status"); - } - else if (pendingCount == 1) - { - string = [NSString stringWithFormat: NSLocalizedString(@"%d encode pending", @"Queue status"), pendingCount]; - } - else - { - string = [NSString stringWithFormat: NSLocalizedString(@"%d encodes pending", @"Queue status"), pendingCount]; - } - - self.countTextField.stringValue = string; - - self.pendingItemsCount = pendingCount; - self.completedItemsCount = completedCount; -} - -#pragma mark - Queue Job Processing + NSParameterAssert(item); + [self.queue.items beginTransaction]; -- (BOOL)_isDiskSpaceLowAtURL:(NSURL *)url -{ - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"HBQueuePauseIfLowSpace"]) + if (item != self.queue.currentItem) { - NSURL *volumeURL = nil; - NSDictionary<NSURLResourceKey, id> *attrs = [url resourceValuesForKeys:@[NSURLIsVolumeKey, NSURLVolumeURLKey] error:NULL]; - long long minCapacity = [[[NSUserDefaults standardUserDefaults] stringForKey:@"HBQueueMinFreeSpace"] longLongValue] * 1000000000; + item.state = HBQueueItemStateWorking; - volumeURL = [attrs[NSURLIsVolumeKey] boolValue] ? url : attrs[NSURLVolumeURLKey]; + // NSUInteger row = [self.queue.items indexOfObject:item]; + //FIXME + //[self reloadQueueItemAtIndex:row]; - if (volumeURL) - { - [volumeURL removeCachedResourceValueForKey:NSURLVolumeAvailableCapacityKey]; - attrs = [volumeURL resourceValuesForKeys:@[NSURLVolumeAvailableCapacityKey] error:NULL]; - - if (attrs[NSURLVolumeAvailableCapacityKey]) + [self.delegate openJob:[item.job copy] completionHandler:^(BOOL result) { + [self.queue.items beginTransaction]; + if (result) { - if ([attrs[NSURLVolumeAvailableCapacityKey] longLongValue] < minCapacity) - { - return YES; - } + // Now that source is loaded and settings applied, delete the queue item from the queue + NSInteger index = [self.queue.items indexOfObject:item]; + item.state = HBQueueItemStateReady; + [self.queue removeQueueItemAtIndex:index]; } - } - } - - return NO; -} - -/** - * Used to get the next pending queue item and return it if found - */ -- (HBQueueItem *)getNextPendingQueueItem -{ - for (HBQueueItem *item in self.items) - { - if (item.state == HBQueueItemStateReady) - { - return item; - } - } - return nil; -} - -/** - * Starts the queue - */ -- (void)encodeNextQueueItem -{ - [self.items beginTransaction]; - self.currentItem = nil; - - // since we have completed an encode, we go to the next - if (self.stop) - { - [HBUtilities writeToActivityLog:"Queue manually stopped"]; - - self.stop = NO; - [self.core allowSleep]; - } - else - { - // Check to see if there are any more pending items in the queue - HBQueueItem *nextItem = [self getNextPendingQueueItem]; - - if (nextItem && [self _isDiskSpaceLowAtURL:nextItem.outputURL]) - { - // Disk space is low, show an alert - [HBUtilities writeToActivityLog:"Queue Stopped, low space on destination disk"]; - - [self queueLowDiskSpaceAlert]; - } - // If we still have more pending items in our queue, lets go to the next one - else if (nextItem) - { - // now we mark the queue item as working so another instance can not come along and try to scan it while we are scanning - nextItem.state = HBQueueItemStateWorking; - - // Tell HB to output a new activity log file for this encode - self.currentLog = [[HBJobOutputFileWriter alloc] initWithJob:nextItem.job]; - if (self.currentLog) + else { - [[HBOutputRedirect stderrRedirect] addListener:self.currentLog]; - [[HBOutputRedirect stdoutRedirect] addListener:self.currentLog]; + item.state = HBQueueItemStateFailed; + NSBeep(); } - - self.currentItem = nextItem; - [self reloadQueueItemAtIndex:[self.items indexOfObject:nextItem]]; - - // now we can go ahead and scan the new pending queue item - [self encodeItem:nextItem]; - - // erase undo manager history - [self.window.undoManager removeAllActions]; - } - else - { - [HBUtilities writeToActivityLog:"Queue Done, there are no more pending encodes"]; - - // Since there are no more items to encode, go to queueCompletedAlerts - // for user specified alerts after queue completed - [self queueCompletedAlerts]; - - [self.core allowSleep]; - } - } - [self.items commit]; -} - -- (void)completedItem:(HBQueueItem *)item result:(HBCoreResult)result; -{ - NSParameterAssert(item); - [self.items beginTransaction]; - - // Since we are done with this encode, tell output to stop writing to the - // individual encode log. - [[HBOutputRedirect stderrRedirect] removeListener:self.currentLog]; - [[HBOutputRedirect stdoutRedirect] removeListener:self.currentLog]; - - self.currentLog = nil; - - // Check to see if the encode state has not been canceled - // to determine if we should send it to external app. - if (result != HBCoreResultCanceled) - { - // Send to tagger - [self sendToExternalApp:item]; - } - - // Mark the encode just finished - switch (result) { - case HBCoreResultDone: - item.state = HBQueueItemStateCompleted; - break; - case HBCoreResultCanceled: - item.state = HBQueueItemStateCanceled; - break; - default: - item.state = HBQueueItemStateFailed; - break; + [self.queue.items commit]; + }]; } - - if ([self.items containsObject:item]) + else { - [self reloadQueueItemAtIndex:[self.items indexOfObject:item]]; - } - [self.window.toolbar validateVisibleItems]; - [self.items commit]; - - // Update UI - NSString *info = nil; - switch (result) { - case HBCoreResultDone: - info = NSLocalizedString(@"Encode Finished.", @"Queue status"); - [self itemCompletedAlerts:item result:result]; - break; - case HBCoreResultCanceled: - info = NSLocalizedString(@"Encode Canceled.", @"Queue status"); - break; - default: - info = NSLocalizedString(@"Encode Failed.", @"Queue status"); - [self itemCompletedAlerts:item result:result]; - break; + NSBeep(); } - [self updateProgress:info progress:1.0 hidden:YES]; - // Restore dock icon - [self.dockTile updateDockIcon:-1.0 withETA:@""]; - self.dockIconProgress = 0; + [self.queue.items commit]; } /** - * Here we actually tell hb_scan to perform the source scan, using the path to source and title number + * Send the selected queue item back to the main window for rescan and possible edit. */ -- (void)encodeItem:(HBQueueItem *)item +- (void)editQueueItem:(HBQueueItem *)item { - NSParameterAssert(item); - - // Progress handler - void (^progressHandler)(HBState state, HBProgress progress, NSString *info) = ^(HBState state, HBProgress progress, NSString *info) + if ([self.queue.items beginTransaction] == HBDistributedArrayContentReload) { - [self updateProgress:info progress:0 hidden:NO]; - }; + // Do not execture the action if the array changed. + [self.queue.items commit]; + return; + } - // Completion handler - void (^completionHandler)(HBCoreResult result) = ^(HBCoreResult result) + // if this is a currently encoding item, we need to be sure to alert the user, + // to let them decide to cancel it first, then if they do, we can come back and + // remove it + if (item == self.queue.currentItem) { - if (result == HBCoreResultDone) - { - [self realEncodeItem:item]; - } - else - { - [self completedItem:item result:result]; - [self encodeNextQueueItem]; - } - }; - - // Only scan 10 previews before an encode - additional previews are - // only useful for autocrop and static previews, which are already taken care of at this point - [self.core scanURL:item.fileURL - titleIndex:item.job.titleIdx - previews:10 - minDuration:0 - progressHandler:progressHandler - completionHandler:completionHandler]; -} - -/** - * This assumes that we have re-scanned and loaded up a new queue item to send to libhb - */ -- (void)realEncodeItem:(HBQueueItem *)item -{ - NSParameterAssert(item); + NSString *alertTitle = [NSString stringWithFormat:NSLocalizedString(@"Stop This Encode and Edit It?", @"Queue Edit Alert -> stop and edit message")]; - HBJob *job = item.job; + // Which window to attach the sheet to? + NSWindow *docWindow = self.window; - // Reset the title in the job. - job.title = self.core.titles.firstObject; - - NSParameterAssert(job); - - HBStateFormatter *formatter = [[HBStateFormatter alloc] init]; - formatter.title = job.outputFileName; - self.core.stateFormatter = formatter; - - // Progress handler - void (^progressHandler)(HBState state, HBProgress progress, NSString *info) = ^(HBState state, HBProgress progress, NSString *info) - { - if (state == HBStateWorking) - { - // Update dock icon - if (self.dockIconProgress < 100.0 * progress.percent) + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:alertTitle]; + [alert setInformativeText:NSLocalizedString(@"Your movie will be lost if you don't continue encoding.", @"Queue Edit Alert -> stop and edit informative text")]; + [alert addButtonWithTitle:NSLocalizedString(@"Keep Encoding", @"Queue Edit Alert -> stop and edit first button")]; + [alert addButtonWithTitle:NSLocalizedString(@"Stop Encoding and Edit", @"Queue Edit Alert -> stop and edit second button")]; + [alert setAlertStyle:NSAlertStyleCritical]; + + [alert beginSheetModalForWindow:docWindow completionHandler:^(NSModalResponse returnCode) { + if (returnCode == NSAlertSecondButtonReturn) { - [self.dockTile updateDockIcon:progress.percent hours:progress.hours minutes:progress.minutes seconds:progress.seconds]; - self.dockIconProgress += dockTileUpdateFrequency; + [self doEditQueueItem:item]; } - } - else if (state == HBStateMuxing) - { - [self.dockTile updateDockIcon:1.0 withETA:@""]; - } - - // Update UI - [self updateProgress:info progress:progress.percent hidden:NO]; - }; - - // Completion handler - void (^completionHandler)(HBCoreResult result) = ^(HBCoreResult result) - { - [self completedItem:item result:result]; - [self encodeNextQueueItem]; - }; - - // We should be all setup so let 'er rip - [self.core encodeJob:job progressHandler:progressHandler completionHandler:completionHandler]; - - // We are done using the title, remove it from the job - job.title = nil; -} - -/** - * Cancels the current job - */ -- (void)doCancelCurrentItem -{ - if (self.core.state == HBStateScanning) - { - [self.core cancelScan]; + }]; } - else + else if (item.state != HBQueueItemStateWorking) { - [self.core cancelEncode]; + [self doEditQueueItem:item]; } -} - -/** - * Cancels the current job and starts processing the next in queue. - */ -- (void)cancelCurrentItemAndContinue -{ - [self doCancelCurrentItem]; -} -/** - * Cancels the current job and stops libhb from processing the remaining encodes. - */ -- (void)cancelCurrentItemAndStop -{ - self.stop = YES; - [self doCancelCurrentItem]; -} -/** - * Finishes the current job and stops libhb from processing the remaining encodes. - */ -- (void)finishCurrentAndStop -{ - self.stop = YES; + [self.queue.items commit]; } #pragma mark - Encode Done Actions @@ -967,6 +454,7 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; notification.hasActionButton = YES; notification.actionButtonTitle = NSLocalizedString(@"Show", @"Notification -> Show in Finder"); notification.userInfo = @{ @"Path": fileURL.path }; + [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; } @@ -1012,18 +500,20 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; /** * Runs the alert for a single job */ -- (void)itemCompletedAlerts:(HBQueueItem *)item result:(HBCoreResult)result +- (void)itemCompletedAlerts:(HBQueueItem *)item { + NSUserDefaults *ud = NSUserDefaults.standardUserDefaults; + // Both the Notification and Sending to tagger can be done as encodes roll off the queue - if ([[NSUserDefaults standardUserDefaults] integerForKey:@"HBAlertWhenDone"] == HBDoneActionNotification || - [[NSUserDefaults standardUserDefaults] integerForKey:@"HBAlertWhenDone"] == HBDoneActionAlertAndNotification) + if ([ud integerForKey:@"HBAlertWhenDone"] == HBDoneActionNotification || + [ud integerForKey:@"HBAlertWhenDone"] == HBDoneActionAlertAndNotification) { // If Play System Alert has been selected in Preferences - bool playSound = [[NSUserDefaults standardUserDefaults] boolForKey:@"HBAlertWhenDoneSound"]; + bool playSound = [ud boolForKey:@"HBAlertWhenDoneSound"]; NSString *title; NSString *description; - if (result == HBCoreResultDone) + if (item.state == HBQueueItemStateCompleted) { title = NSLocalizedString(@"Put down that cocktail…", @"Queue notification alert message"); description = [NSString stringWithFormat:NSLocalizedString(@"Your encode %@ is done!", @"Queue done notification message"), @@ -1049,15 +539,16 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; */ - (void)queueCompletedAlerts { + NSUserDefaults *ud = NSUserDefaults.standardUserDefaults; // If Play System Alert has been selected in Preferences - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"HBAlertWhenDoneSound"] == YES) + if ([ud boolForKey:@"HBAlertWhenDoneSound"] == YES) { NSBeep(); } // If Alert Window or Window and Notification has been selected - if ([[NSUserDefaults standardUserDefaults] integerForKey:@"HBAlertWhenDone"] == HBDoneActionAlert || - [[NSUserDefaults standardUserDefaults] integerForKey:@"HBAlertWhenDone"] == HBDoneActionAlertAndNotification) + if ([ud integerForKey:@"HBAlertWhenDone"] == HBDoneActionAlert || + [ud integerForKey:@"HBAlertWhenDone"] == HBDoneActionAlertAndNotification) { // On Screen Notification NSAlert *alert = [[NSAlert alloc] init]; @@ -1068,7 +559,7 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; } // If sleep has been selected - if ([[NSUserDefaults standardUserDefaults] integerForKey:@"HBAlertWhenDone"] == HBDoneActionSleep) + if ([ud integerForKey:@"HBAlertWhenDone"] == HBDoneActionSleep) { // Sleep NSDictionary *errorDict; @@ -1077,7 +568,7 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; [scriptObject executeAndReturnError: &errorDict]; } // If Shutdown has been selected - if ([[NSUserDefaults standardUserDefaults] integerForKey:@"HBAlertWhenDone"] == HBDoneActionShutDown) + if ([ud integerForKey:@"HBAlertWhenDone"] == HBDoneActionShutDown) { // Shut Down NSDictionary *errorDict; @@ -1095,114 +586,11 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; [alert runModal]; } -#pragma mark - Queue Item Controls - -/** - * Delete encodes from the queue window and accompanying array - * Also handling first cancelling the encode if in fact its currently encoding. - */ -- (IBAction)removeSelectedQueueItem:(id)sender -{ - if ([self.items beginTransaction] == HBDistributedArrayContentReload) - { - // Do not execture the action if the array changed. - [self.items commit]; - return; - } - - NSMutableIndexSet *targetedRows = [[self.tableView targetedRowIndexes] mutableCopy]; - - if (targetedRows.count) - { - // if this is a currently encoding job, we need to be sure to alert the user, - // to let them decide to cancel it first, then if they do, we can come back and - // remove it - NSIndexSet *workingIndexes = [self.items indexesOfObjectsUsingBlock:^BOOL(HBQueueItem *item) { - return item.state == HBQueueItemStateWorking; - }]; - - if ([targetedRows containsIndexes:workingIndexes]) - { - [targetedRows removeIndexes:workingIndexes]; - NSArray<HBQueueItem *> *workingItems = [self.items filteredArrayUsingBlock:^BOOL(HBQueueItem *item) { - return item.state == HBQueueItemStateWorking; - }]; - - if ([workingItems containsObject:self.currentItem]) - { - NSString *alertTitle = [NSString stringWithFormat:NSLocalizedString(@"Stop This Encode and Remove It?", @"Queue Stop Alert -> stop and remove message")]; - - // Which window to attach the sheet to? - NSWindow *targetWindow = self.window; - if ([sender respondsToSelector: @selector(window)]) - { - targetWindow = [sender window]; - } - - NSAlert *alert = [[NSAlert alloc] init]; - [alert setMessageText:alertTitle]; - [alert setInformativeText:NSLocalizedString(@"Your movie will be lost if you don't continue encoding.", @"Queue Stop Alert -> stop and remove informative text")]; - [alert addButtonWithTitle:NSLocalizedString(@"Keep Encoding", @"Queue Stop Alert -> stop and remove first button")]; - [alert addButtonWithTitle:NSLocalizedString(@"Stop Encoding and Delete", @"Queue Stop Alert -> stop and remove second button")]; - [alert setAlertStyle:NSAlertStyleCritical]; - - [alert beginSheetModalForWindow:targetWindow completionHandler:^(NSModalResponse returnCode) { - if (returnCode == NSAlertSecondButtonReturn) - { - [self.items beginTransaction]; - - NSInteger index = [self.items indexOfObject:self.currentItem]; - [self cancelCurrentItemAndContinue]; - - [self removeQueueItemAtIndex:index]; - [self.items commit]; - } - }]; - } - } - - // remove the non working items immediately - [self removeQueueItemsAtIndexes:targetedRows]; - } - [self.items commit]; -} - -/** - * Show the finished encode in the finder - */ -- (IBAction)revealSelectedQueueItems:(id)sender -{ - NSIndexSet *targetedRows = [self.tableView targetedRowIndexes]; - NSMutableArray<NSURL *> *urls = [[NSMutableArray alloc] init]; - - NSUInteger currentIndex = [targetedRows firstIndex]; - while (currentIndex != NSNotFound) { - NSURL *url = [[self.items objectAtIndex:currentIndex] completeOutputURL]; - [urls addObject:url]; - currentIndex = [targetedRows indexGreaterThanIndex:currentIndex]; - } - - [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:urls]; -} - -- (IBAction)revealSelectedQueueItemsSources:(id)sender -{ - NSIndexSet *targetedRows = [self.tableView targetedRowIndexes]; - NSMutableArray<NSURL *> *urls = [[NSMutableArray alloc] init]; - - NSUInteger currentIndex = [targetedRows firstIndex]; - while (currentIndex != NSNotFound) { - NSURL *url = [[self.items objectAtIndex:currentIndex] fileURL]; - [urls addObject:url]; - currentIndex = [targetedRows indexGreaterThanIndex:currentIndex]; - } - - [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:urls]; -} - - (void)remindUserOfSleepOrShutdown { - if ([[NSUserDefaults standardUserDefaults] integerForKey:@"HBAlertWhenDone"] == HBDoneActionSleep) + NSUserDefaults *ud = NSUserDefaults.standardUserDefaults; + + if ([ud integerForKey:@"HBAlertWhenDone"] == HBDoneActionSleep) { // Warn that computer will sleep after encoding NSBeep(); @@ -1222,7 +610,7 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; [self promptForAppleEventAuthorization]; } - else if ([[NSUserDefaults standardUserDefaults] integerForKey:@"HBAlertWhenDone"] == HBDoneActionShutDown) + else if ([ud integerForKey:@"HBAlertWhenDone"] == HBDoneActionShutDown) { // Warn that computer will shut down after encoding NSBeep(); @@ -1253,32 +641,31 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; } } +#pragma mark - UI Actions + /** * Rip: puts up an alert before ultimately calling doRip */ -- (IBAction)rip:(id)sender +- (IBAction)toggleStartCancel:(id)sender { // Rip or Cancel ? - if (self.core.state == HBStateWorking || self.core.state == HBStatePaused || self.core.state == HBStateSearching) + if (self.queue.isEncoding) { [self cancelRip:sender]; } // If there are pending items in the queue, then this is a rip the queue - else if (self.pendingItemsCount > 0) + else if (self.queue.canEncode) { // We check to see if we need to warn the user that the computer will go to sleep // or shut down when encoding is finished [self remindUserOfSleepOrShutdown]; - - [self.core preventSleep]; - [self encodeNextQueueItem]; + [self.queue start]; } } /** +* Starts or cancels the processing of items depending on the current state * Displays an alert asking user if the want to cancel encoding of current item. - * Cancel: returns immediately after posting the alert. Later, when the user - * acknowledges the alert, doCancelCurrentItem is called. */ - (IBAction)cancelRip:(id)sender { @@ -1301,358 +688,82 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; [alert beginSheetModalForWindow:window completionHandler:^(NSModalResponse returnCode) { if (returnCode == NSAlertSecondButtonReturn) { - [self cancelCurrentItemAndContinue]; + [self.queue cancelCurrentItemAndContinue]; } else if (returnCode == NSAlertThirdButtonReturn) { - [self finishCurrentAndStop]; + [self.queue finishCurrentAndStop]; } else if (returnCode == NSAlertThirdButtonReturn + 1) { - [self cancelCurrentItemAndStop]; + [self.queue cancelCurrentItemAndStop]; } }]; } /** - * Starts or cancels the processing of items depending on the current state - */ -- (IBAction)toggleStartCancel:(id)sender -{ - HBState s = self.core.state; - if ((s == HBStatePaused) || (s == HBStateWorking) || (s == HBStateMuxing)) - { - [self cancelRip:self]; - } - else if (self.pendingItemsCount > 0) - { - [self rip:self]; - } -} - -/** * Toggles the pause/resume state of libhb */ - (IBAction)togglePauseResume:(id)sender { - HBState s = self.core.state; - if (s == HBStatePaused) + if (self.queue.canResume) { - [self.core resume]; - [self.core preventSleep]; + [self.queue resume]; } - else if (s == HBStateWorking || s == HBStateMuxing) + else if (self.queue.canPause) { - [self.core pause]; - [self.core allowSleep]; + [self.queue pause]; } } -/** - * Resets the item state to ready. - */ -- (IBAction)resetJobState:(id)sender +- (IBAction)toggleDetails:(id)sender { - if ([self.items beginTransaction] == HBDistributedArrayContentReload) - { - // Do not execture the action if the array changed. - [self.items commit]; - return; - } - - NSIndexSet *targetedRows = [self.tableView targetedRowIndexes]; - NSMutableIndexSet *updatedIndexes = [NSMutableIndexSet indexSet]; - - NSUInteger currentIndex = [targetedRows firstIndex]; - while (currentIndex != NSNotFound) { - HBQueueItem *item = self.items[currentIndex]; - - if (item.state == HBQueueItemStateCanceled || item.state == HBQueueItemStateCompleted || item.state == HBQueueItemStateFailed) - { - item.state = HBQueueItemStateReady; - [updatedIndexes addIndex:currentIndex]; - } - currentIndex = [targetedRows indexGreaterThanIndex:currentIndex]; - } - - [self reloadQueueItemsAtIndexes:updatedIndexes]; - [self.items commit]; + NSSplitViewItem *detailsItem = self.splitViewController.splitViewItems[1]; + detailsItem.animator.collapsed = !detailsItem.isCollapsed; } -- (void)editQueueItem:(HBQueueItem *)item -{ - NSParameterAssert(item); - [self.items beginTransaction]; +#pragma mark - table view controller delegate - if (item != self.currentItem) - { - item.state = HBQueueItemStateWorking; - - NSUInteger row = [self.items indexOfObject:item]; - [self reloadQueueItemAtIndex:row]; - - [self.controller openJob:[item.job copy] completionHandler:^(BOOL result) { - [self.items beginTransaction]; - if (result) - { - // Now that source is loaded and settings applied, delete the queue item from the queue - NSInteger index = [self.items indexOfObject:item]; - item.state = HBQueueItemStateReady; - [self removeQueueItemAtIndex:index]; - } - else - { - item.state = HBQueueItemStateFailed; - NSBeep(); - } - [self.items commit]; - }]; - } - else - { - NSBeep(); - } - - [self.items commit]; -} - -/** - * Send the selected queue item back to the main window for rescan and possible edit. - */ -- (IBAction)editSelectedQueueItem:(id)sender +- (void)tableViewDidSelectItem:(HBQueueItem *)item { - if ([self.items beginTransaction] == HBDistributedArrayContentReload) - { - // Do not execture the action if the array changed. - [self.items commit]; - return; - } - - NSInteger row = self.tableView.clickedRow; - if (row != NSNotFound) - { - // if this is a currently encoding item, we need to be sure to alert the user, - // to let them decide to cancel it first, then if they do, we can come back and - // remove it - HBQueueItem *item = self.items[row]; - if (item == self.currentItem) - { - NSString *alertTitle = [NSString stringWithFormat:NSLocalizedString(@"Stop This Encode and Edit It?", @"Queue Edit Alert -> stop and edit message")]; - - // Which window to attach the sheet to? - NSWindow *docWindow = self.window; - if ([sender respondsToSelector: @selector(window)]) - { - docWindow = [sender window]; - } - - NSAlert *alert = [[NSAlert alloc] init]; - [alert setMessageText:alertTitle]; - [alert setInformativeText:NSLocalizedString(@"Your movie will be lost if you don't continue encoding.", @"Queue Edit Alert -> stop and edit informative text")]; - [alert addButtonWithTitle:NSLocalizedString(@"Keep Encoding", @"Queue Edit Alert -> stop and edit first button")]; - [alert addButtonWithTitle:NSLocalizedString(@"Stop Encoding and Edit", @"Queue Edit Alert -> stop and edit second button")]; - [alert setAlertStyle:NSAlertStyleCritical]; - - [alert beginSheetModalForWindow:docWindow completionHandler:^(NSModalResponse returnCode) { - if (returnCode == NSAlertSecondButtonReturn) - { - [self editQueueItem:item]; - } - }]; - } - else if (item.state != HBQueueItemStateWorking) - { - [self editQueueItem:item]; - } - } - - [self.items commit]; + self.detailsViewController.item = item; } -- (IBAction)clearAll:(id)sender +- (void)tableViewEditItem:(HBQueueItem *)item { - [self.items beginTransaction]; - NSIndexSet *indexes = [self.items indexesOfObjectsUsingBlock:^BOOL(HBQueueItem *item) { - return (item.state != HBQueueItemStateWorking); - }]; - [self removeQueueItemsAtIndexes:indexes]; - [self.items commit]; + [self editQueueItem:item]; } -- (IBAction)clearCompleted:(id)sender -{ - [self.items beginTransaction]; - NSIndexSet *indexes = [self.items indexesOfObjectsUsingBlock:^BOOL(HBQueueItem *item) { - return (item.state == HBQueueItemStateCompleted); - }]; +- (void)tableViewRemoveItemsAtIndexes:(nonnull NSIndexSet *)indexes { [self removeQueueItemsAtIndexes:indexes]; - [self.items commit]; -} - -#pragma mark - NSTableView data source - -- (NSView *)tableView:(NSTableView *)tableView - viewForTableColumn:(NSTableColumn *)tableColumn - row:(NSInteger)row { - - HBQueueItemView *view = [tableView makeViewWithIdentifier:@"MainCell" owner:self]; - HBQueueItem *item = self.items[row]; - - view.delegate = self; - view.item = item; - - return view; -} - -- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView -{ - return self.items.count; -} - -- (NSTableCellView *)dummyCell -{ - if (!_dummyCell) { - _dummyCell = [self.tableView makeViewWithIdentifier:@"MainCellForSizing" owner: self]; - _dummyCellWidth = [NSLayoutConstraint constraintWithItem:_dummyCell - attribute:NSLayoutAttributeWidth - relatedBy:NSLayoutRelationEqual - toItem:nil - attribute:NSLayoutAttributeNotAnAttribute - multiplier:1.0f - constant:500]; - [_dummyCell addConstraint:_dummyCellWidth]; - } - return _dummyCell; -} - -- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row -{ - HBQueueItem *item = self.items[row]; - - if (item.expanded) - { - CGFloat width = tableView.frame.size.width; - self.dummyCellWidth.constant = width; - self.dummyCell.textField.preferredMaxLayoutWidth = width - 60; - self.dummyCell.textField.attributedStringValue = item.attributedDescription; - - CGFloat height = self.dummyCell.fittingSize.height; - return height; - } - else - { - return 20; - } -} - -- (void)toggleRowsAtIndexes:(NSIndexSet *)rowIndexes expand:(BOOL)expand -{ - NSMutableIndexSet *rowsToExpand = [NSMutableIndexSet indexSet]; - [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop) { - HBQueueItem *item = self.items[index]; - BOOL expanded = item.expanded; - if (expanded != expand) - { - item.expanded = !expanded; - [rowsToExpand addIndex:index]; - } - - HBQueueItemView *itemView = (HBQueueItemView *)[self.tableView viewAtColumn:0 row:index makeIfNecessary:NO]; - if (expand) - { - [itemView expand]; - } - else - { - [itemView collapse]; - } - }]; - [self.tableView noteHeightOfRowsWithIndexesChanged:rowsToExpand]; -} - -#pragma mark NSQueueItemView delegate - -- (void)removeQueueItem:(nonnull HBQueueItem *)item -{ - NSUInteger index = [self.items indexOfObject:item]; - [self removeQueueItemAtIndex:index]; } -- (void)revealQueueItem:(nonnull HBQueueItem *)item -{ - [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:@[item.completeOutputURL]]; +- (void)detailsViewEditItem:(nonnull HBQueueItem *)item { + [self editQueueItem:item]; } -- (void)toggleQueueItemHeight:(nonnull HBQueueItem *)item -{ - NSInteger row = [self.items indexOfObject:item]; - [self toggleRowsAtIndexes:[NSIndexSet indexSetWithIndex:row] expand:!item.expanded]; +- (void)detailsViewResetItem:(nonnull HBQueueItem *)item { + [self editQueueItem:item]; } -#pragma mark NSTableView delegate - -- (void)HB_deleteSelectionFromTableView:(NSTableView *)tableView +- (IBAction)resetAll:(id)sender { - [self removeSelectedQueueItem:tableView]; + [self.queue resetAllItems]; } -- (void)HB_expandSelectionFromTableView:(NSTableView *)tableView +- (IBAction)resetFailed:(id)sender { - NSIndexSet *rowIndexes = [self.tableView selectedRowIndexes]; - [self toggleRowsAtIndexes:rowIndexes expand:YES]; -} - -- (void)HB_collapseSelectionFromTableView:(NSTableView *)tableView; -{ - NSIndexSet *rowIndexes = [self.tableView selectedRowIndexes]; - [self toggleRowsAtIndexes:rowIndexes expand:NO]; -} - -- (BOOL)tableView:(NSTableView *)tableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard; -{ - NSArray<HBQueueItem *> *items = [self.items objectsAtIndexes:rowIndexes]; - // Dragging is only allowed of the pending items. - if (items[0].state != HBQueueItemStateReady) - { - return NO; - } - - self.dragNodesArray = items; - - // Provide data for our custom type, and simple NSStrings. - [pboard declareTypes:@[DragDropSimplePboardType] owner:self]; - - // the actual data doesn't matter since DragDropSimplePboardType drags aren't recognized by anyone but us!. - [pboard setData:[NSData data] forType:DragDropSimplePboardType]; - - return YES; + [self.queue resetFailedItems]; } -- (NSDragOperation)tableView:(NSTableView *)tableView validateDrop:(id<NSDraggingInfo>)info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)dropOperation +- (IBAction)removeAll:(id)sender { - // Don't allow dropping ONTO an item since they can't really contain any children. - BOOL isOnDropTypeProposal = dropOperation == NSTableViewDropOn; - if (isOnDropTypeProposal) - { - return NSDragOperationNone; - } - - // We do not let the user drop a pending item before or *above* - // already finished or currently encoding items. - NSInteger encodingRow = [self.items indexOfObject:self.currentItem]; - if (encodingRow != NSNotFound && row <= encodingRow) - { - return NSDragOperationNone; - row = MAX(row, encodingRow); - } - - return NSDragOperationMove; + [self.queue removeNotWorkingItems]; } -- (BOOL)tableView:(NSTableView *)tableView acceptDrop:(id<NSDraggingInfo>)info row:(NSInteger)row dropOperation:(NSTableViewDropOperation)dropOperation +- (IBAction)removeCompleted:(id)sender { - [self moveQueueItems:self.dragNodesArray toIndex:row]; - return YES; + [self.queue removeCompletedItems]; } @end @@ -1705,24 +816,26 @@ static NSTouchBarItemIdentifier HBTouchBarPause = @"fr.handbrake.pause"; return nil; } -- (void)_touchBar_updateButtonsStateForQueueCore:(HBState)state; +- (void)_touchBar_updateButtonsState; { NSButton *ripButton = (NSButton *)[[self.touchBar itemForIdentifier:HBTouchBarRip] view]; NSButton *pauseButton = (NSButton *)[[self.touchBar itemForIdentifier:HBTouchBarPause] view]; - if (state == HBStateScanning || state == HBStateWorking || state == HBStateSearching || state == HBStateMuxing) + if (self.queue.isEncoding) { ripButton.image = [NSImage imageNamed:NSImageNameTouchBarRecordStopTemplate]; - pauseButton.image = [NSImage imageNamed:NSImageNameTouchBarPauseTemplate]; } - else if (state == HBStatePaused) + else + { + ripButton.image = [NSImage imageNamed:NSImageNameTouchBarPlayTemplate]; + } + + if (self.queue.canResume) { - ripButton.image = [NSImage imageNamed:NSImageNameTouchBarRecordStopTemplate]; pauseButton.image = [NSImage imageNamed:NSImageNameTouchBarPlayTemplate]; } - else if (state == HBStateIdle) + else { - ripButton.image = [NSImage imageNamed:NSImageNameTouchBarPlayTemplate]; pauseButton.image = [NSImage imageNamed:NSImageNameTouchBarPauseTemplate]; } } diff --git a/macosx/HBQueueDetailsViewController.h b/macosx/HBQueueDetailsViewController.h new file mode 100644 index 000000000..8fca10a1a --- /dev/null +++ b/macosx/HBQueueDetailsViewController.h @@ -0,0 +1,27 @@ +/* HBQueueDetailsViewController.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 <Cocoa/Cocoa.h> +#import "HBQueueItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol HBQueueDetailsViewControllerDelegate + +- (void)detailsViewEditItem:(HBQueueItem *)item; +- (void)detailsViewResetItem:(HBQueueItem *)item; + +@end + +@interface HBQueueDetailsViewController : NSViewController + +- (instancetype)initWithDelegate:(id<HBQueueDetailsViewControllerDelegate>)delegate; + +@property (nonatomic, nullable) HBQueueItem *item; + +@end + +NS_ASSUME_NONNULL_END diff --git a/macosx/HBQueueDetailsViewController.m b/macosx/HBQueueDetailsViewController.m new file mode 100644 index 000000000..a779346e8 --- /dev/null +++ b/macosx/HBQueueDetailsViewController.m @@ -0,0 +1,65 @@ +/* HBQueueDetailsViewController.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 "HBQueueDetailsViewController.h" + +@interface HBQueueDetailsViewController () + +@property (weak) IBOutlet NSTextField *detailsLabel; +@property (weak) IBOutlet NSScrollView *scrollView; + +@property (weak) id<HBQueueDetailsViewControllerDelegate> delegate; + +@end + +@implementation HBQueueDetailsViewController + +- (NSString *)nibName +{ + return @"HBQueueDetailsViewController"; +} + +- (instancetype)initWithDelegate:(id<HBQueueDetailsViewControllerDelegate>)delegate +{ + self = [super init]; + if (self) + { + _delegate = delegate; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + self.item = nil; +} + +- (void)setItem:(HBQueueItem *)item +{ + _item = item; + if (item) + { + self.detailsLabel.attributedStringValue = item.attributedDescription; + [self.scrollView flashScrollers]; + } + else + { + self.detailsLabel.stringValue = NSLocalizedString(@"No job selected", @""); + } +} + +- (IBAction)editItem:(id)sender +{ + [self.delegate detailsViewEditItem:self.item]; +} + +- (IBAction)resetItem:(id)sender +{ + [self.delegate detailsViewResetItem:self.item]; +} + + +@end diff --git a/macosx/HBQueueItem.m b/macosx/HBQueueItem.m index e0806d7e7..959167731 100644 --- a/macosx/HBQueueItem.m +++ b/macosx/HBQueueItem.m @@ -11,8 +11,8 @@ @implementation HBQueueItem @synthesize job = _job; -@synthesize attributedDescription = _attributedDescription; @synthesize attributedTitleDescription = _attributedTitleDescription; +@synthesize attributedDescription = _attributedDescription; @synthesize uuid = _uuid; @@ -49,14 +49,6 @@ return _job.completeOutputURL; } -- (NSAttributedString *)attributedDescription -{ - if (_attributedDescription == nil) { - _attributedDescription = _job.attributedDescription; - } - return _attributedDescription; -} - - (NSAttributedString *)attributedTitleDescription { if (_attributedTitleDescription == nil) { @@ -65,6 +57,14 @@ return _attributedTitleDescription; } +- (NSAttributedString *)attributedDescription +{ + if (_attributedDescription == nil) { + _attributedDescription = _job.attributedDescription; + } + return _attributedDescription; +} + #pragma mark - NSSecureCoding + (BOOL)supportsSecureCoding diff --git a/macosx/HBQueueItemView.h b/macosx/HBQueueItemView.h index 4dedf3766..e79ee51c8 100644 --- a/macosx/HBQueueItemView.h +++ b/macosx/HBQueueItemView.h @@ -23,9 +23,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, weak, nullable) HBQueueItem *item; @property (nonatomic, weak, nullable) id <HBQueueItemViewDelegate> delegate; -- (void)expand; -- (void)collapse; - @end NS_ASSUME_NONNULL_END diff --git a/macosx/HBQueueItemView.m b/macosx/HBQueueItemView.m index d736fe59f..39fbdf4ce 100644 --- a/macosx/HBQueueItemView.m +++ b/macosx/HBQueueItemView.m @@ -29,22 +29,11 @@ [self HB_updateRightButton]; self.removeButton.target = self; - self.expandButton.target = self; - self.expandButton.action = @selector(toggleHeight:); } - (void)HB_updateLabel { - if (_item.expanded) - { - self.textField.attributedStringValue = _item.attributedDescription; - self.expandButton.state = NSOnState; - } - else - { - self.textField.attributedStringValue = _item.attributedTitleDescription; - self.expandButton.state = NSOffState; - } + self.textField.stringValue = _item.outputFileName; } - (void)HB_updateState @@ -114,18 +103,6 @@ [self HB_updateRightButton]; } -- (void)expand -{ - self.expandButton.state = NSOnState; - self.textField.attributedStringValue = _item.attributedDescription; -} - -- (void)collapse -{ - self.expandButton.state = NSOffState; - self.textField.attributedStringValue = _item.attributedTitleDescription; -} - - (BOOL)isFlipped { return YES; diff --git a/macosx/HBQueueTableViewController.h b/macosx/HBQueueTableViewController.h new file mode 100644 index 000000000..95ab53ac7 --- /dev/null +++ b/macosx/HBQueueTableViewController.h @@ -0,0 +1,29 @@ +/* HBQueueTableViewController.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 <Cocoa/Cocoa.h> + +#import "HBQueue.h" +#import "HBDistributedArray.h" +#import "HBQueueItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol HBQueueTableViewControllerDelegate + +- (void)tableViewDidSelectItem:(nullable HBQueueItem *)item; +- (void)tableViewEditItem:(HBQueueItem *)item; +- (void)tableViewRemoveItemsAtIndexes:(NSIndexSet *)indexes; + +@end + +@interface HBQueueTableViewController : NSViewController + +- (instancetype)initWithQueue:(HBQueue *)queue delegate:(id<HBQueueTableViewControllerDelegate>)delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/macosx/HBQueueTableViewController.m b/macosx/HBQueueTableViewController.m new file mode 100644 index 000000000..d0b4753cb --- /dev/null +++ b/macosx/HBQueueTableViewController.m @@ -0,0 +1,339 @@ +/* HBQueueTableViewController.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 "HBQueueTableViewController.h" + +#import "HBTableView.h" +#import "HBQueueItemView.h" + +// Pasteboard type for or drag operations +#define HBQueueDragDropPboardType @"HBQueueCustomTableViewPboardType" + +@interface HBQueueTableViewController () <NSTableViewDataSource, NSTableViewDelegate, HBQueueItemViewDelegate> + +@property (nonatomic, readonly) HBQueue *queue; +@property (nonatomic) NSArray<HBQueueItem *> *dragNodesArray; + +@property (strong) id<HBQueueTableViewControllerDelegate> delegate; + +@property (weak) IBOutlet HBTableView *tableView; + +@end + +@implementation HBQueueTableViewController + +- (instancetype)initWithQueue:(HBQueue *)state delegate:(id<HBQueueTableViewControllerDelegate>)delegate +{ + self = [super init]; + if (self) + { + _queue = state; + _delegate = delegate; + } + return self; +} + +- (NSString *)nibName +{ + return @"HBQueueTableViewController"; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + // lets setup our queue list table view for drag and drop here + [self.tableView registerForDraggedTypes:@[HBQueueDragDropPboardType]]; + [self.tableView setDraggingSourceOperationMask:NSDragOperationEvery forLocal:YES]; + [self.tableView setVerticalMotionCanBeginDrag:YES]; + + // Reloads the queue, this is called + // when another HandBrake instances modifies the queue + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueReloadItemsNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + [self.tableView reloadData]; + }]; + + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueDidAddItemNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + NSIndexSet *indexes = note.userInfo[HBQueueItemNotificationIndexesKey]; + [self.tableView insertRowsAtIndexes:indexes withAnimation:NSTableViewAnimationSlideDown]; + }]; + + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueDidRemoveItemNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + NSIndexSet *indexes = note.userInfo[HBQueueItemNotificationIndexesKey]; + [self.tableView removeRowsAtIndexes:indexes withAnimation:NSTableViewAnimationSlideUp]; + [self.tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:indexes.firstIndex] byExtendingSelection:NO]; + }]; + + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueDidMoveItemNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + NSArray<NSNumber *> *source = note.userInfo[HBQueueItemNotificationSourceIndexesKey]; + NSArray<NSNumber *> *target = note.userInfo[HBQueueItemNotificationTargetIndexesKey]; + + [self.tableView beginUpdates]; + for (NSInteger idx = 0; idx < source.count; idx++) + { + [self.tableView moveRowAtIndex:source[idx].integerValue toIndex:target[idx].integerValue]; + } + [self.tableView endUpdates]; + }]; + + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueDidChangeItemNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + NSIndexSet *indexes = note.userInfo[HBQueueItemNotificationIndexesKey]; + NSIndexSet *columnIndexes = [NSIndexSet indexSetWithIndex:0]; + [self.tableView reloadDataForRowIndexes:indexes columnIndexes:columnIndexes]; + }]; + + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueDidCompleteItemNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + NSIndexSet *indexes = note.userInfo[HBQueueItemNotificationIndexesKey]; + NSIndexSet *columnIndexes = [NSIndexSet indexSetWithIndex:0]; + if (indexes.count) + { + [self.tableView reloadDataForRowIndexes:indexes columnIndexes:columnIndexes]; + } + }]; +} + +#pragma mark - UI Actions + +/** + * Delete encodes from the queue window and accompanying array + * Also handling first cancelling the encode if in fact its currently encoding. + */ +- (IBAction)removeSelectedQueueItem:(id)sender +{ + NSMutableIndexSet *targetedRows = [[self.tableView targetedRowIndexes] mutableCopy]; + [self.delegate tableViewRemoveItemsAtIndexes:targetedRows]; +} + +/** + * Show the finished encode in the finder + */ +- (IBAction)revealSelectedQueueItems:(id)sender +{ + NSIndexSet *targetedRows = [self.tableView targetedRowIndexes]; + NSMutableArray<NSURL *> *urls = [[NSMutableArray alloc] init]; + + NSUInteger currentIndex = [targetedRows firstIndex]; + while (currentIndex != NSNotFound) { + NSURL *url = [[self.queue.items objectAtIndex:currentIndex] completeOutputURL]; + [urls addObject:url]; + currentIndex = [targetedRows indexGreaterThanIndex:currentIndex]; + } + + [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:urls]; +} + +- (IBAction)revealSelectedQueueItemsSources:(id)sender +{ + NSIndexSet *targetedRows = [self.tableView targetedRowIndexes]; + NSMutableArray<NSURL *> *urls = [[NSMutableArray alloc] init]; + + NSUInteger currentIndex = [targetedRows firstIndex]; + while (currentIndex != NSNotFound) { + NSURL *url = [[self.queue.items objectAtIndex:currentIndex] fileURL]; + [urls addObject:url]; + currentIndex = [targetedRows indexGreaterThanIndex:currentIndex]; + } + + [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:urls]; +} + +/** + * Resets the item state to ready. + */ +- (IBAction)resetJobState:(id)sender +{ + NSIndexSet *targetedRows = [self.tableView targetedRowIndexes]; + if (targetedRows.count) + { + [self.queue resetItemsStateAtIndexes:targetedRows]; + } +} + +/** + * Send the selected queue item back to the main window for rescan and possible edit. + */ +- (IBAction)editSelectedQueueItem:(id)sender +{ + NSInteger row = self.tableView.clickedRow; + HBQueueItem *item = [self.queue.items objectAtIndex:row]; + if (item) + { + [self.delegate tableViewEditItem:item]; + } +} + +- (IBAction)removeAll:(id)sender +{ + [self.queue removeNotWorkingItems]; +} + +- (IBAction)removeCompleted:(id)sender +{ + [self.queue removeCompletedItems]; +} + +#pragma mark - UI Validation + +- (BOOL)validateMenuItem:(NSMenuItem *)menuItem +{ + SEL action = menuItem.action; + + if (action == @selector(editSelectedQueueItem:) || + action == @selector(removeSelectedQueueItem:) || + action == @selector(revealSelectedQueueItems:) || + action == @selector(revealSelectedQueueItemsSources:)) + { + return (self.tableView.selectedRow != -1 || self.tableView.clickedRow != -1); + } + + if (action == @selector(resetJobState:)) + { + return self.tableView.targetedRowIndexes.count > 0; + } + + if (action == @selector(removeAll:)) + { + return self.queue.items.count > 0; + } + + if (action == @selector(removeCompleted:)) + { + return self.queue.completedItemsCount > 0; + } + + return YES; +} + +#pragma mark - NSTableView data source + +- (NSView *)tableView:(NSTableView *)tableView + viewForTableColumn:(NSTableColumn *)tableColumn + row:(NSInteger)row { + + HBQueueItemView *view = [tableView makeViewWithIdentifier:@"MainSimpleCell" owner:self]; + HBQueueItem *item = self.queue.items[row]; + + view.delegate = self; + view.item = item; + + return view; +} + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + return self.queue.items.count; +} + +- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row +{ + return 22; +} + +- (void)toggleRowsAtIndexes:(NSIndexSet *)rowIndexes expand:(BOOL)expand +{ + NSMutableIndexSet *rowsToExpand = [NSMutableIndexSet indexSet]; + [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop) { + HBQueueItem *item = self.queue.items[index]; + BOOL expanded = item.expanded; + if (expanded != expand) + { + item.expanded = !expanded; + [rowsToExpand addIndex:index]; + } + + //HBQueueItemView *itemView = (HBQueueItemView *)[self.tableView viewAtColumn:0 row:index makeIfNecessary:NO]; + //if (expand) + //{ + //[itemView expand]; + //} + //else + //{ + //[itemView collapse]; + //} + }]; + [self.tableView noteHeightOfRowsWithIndexesChanged:rowsToExpand]; +} + +#pragma mark NSQueueItemView delegate + +- (void)removeQueueItem:(nonnull HBQueueItem *)item +{ + NSUInteger index = [self.queue.items indexOfObject:item]; + [self.queue removeQueueItemAtIndex:index]; +} + +- (void)revealQueueItem:(nonnull HBQueueItem *)item +{ + [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:@[item.completeOutputURL]]; +} + +- (void)toggleQueueItemHeight:(nonnull HBQueueItem *)item +{ + NSInteger row = [self.queue.items indexOfObject:item]; + [self toggleRowsAtIndexes:[NSIndexSet indexSetWithIndex:row] expand:!item.expanded]; +} + +#pragma mark NSTableView delegate + +- (void)tableViewSelectionDidChange:(NSNotification *)notification +{ + NSInteger selectedRow = self.tableView.selectedRow; + HBQueueItem *selectedItem = selectedRow > -1 ? self.queue.items[selectedRow] : nil; + [self.delegate tableViewDidSelectItem:selectedItem]; +} + +- (void)HB_deleteSelectionFromTableView:(NSTableView *)tableView +{ + [self removeSelectedQueueItem:tableView]; +} + +- (BOOL)tableView:(NSTableView *)tableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard; +{ + NSArray<HBQueueItem *> *items = [self.queue.items objectsAtIndexes:rowIndexes]; + // Dragging is only allowed of the pending items. + if (items[0].state != HBQueueItemStateReady) + { + return NO; + } + + self.dragNodesArray = items; + + // Provide data for our custom type, and simple NSStrings. + [pboard declareTypes:@[HBQueueDragDropPboardType] owner:self]; + + // the actual data doesn't matter since DragDropSimplePboardType drags aren't recognized by anyone but us!. + [pboard setData:[NSData data] forType:HBQueueDragDropPboardType]; + + return YES; +} + +- (NSDragOperation)tableView:(NSTableView *)tableView validateDrop:(id<NSDraggingInfo>)info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)dropOperation +{ + // Don't allow dropping ONTO an item since they can't really contain any children. + BOOL isOnDropTypeProposal = dropOperation == NSTableViewDropOn; + if (isOnDropTypeProposal) + { + return NSDragOperationNone; + } + + // We do not let the user drop a pending item before or *above* + // already finished or currently encoding items. + NSInteger encodingRow = [self.queue.items indexOfObject:self.queue.currentItem]; + if (encodingRow != NSNotFound && row <= encodingRow) + { + return NSDragOperationNone; + row = MAX(row, encodingRow); + } + + return NSDragOperationMove; +} + +- (BOOL)tableView:(NSTableView *)tableView acceptDrop:(id<NSDraggingInfo>)info row:(NSInteger)row dropOperation:(NSTableViewDropOperation)dropOperation +{ + [self.queue moveQueueItems:self.dragNodesArray toIndex:row]; + return YES; +} + +@end diff --git a/macosx/HandBrake.xcodeproj/project.pbxproj b/macosx/HandBrake.xcodeproj/project.pbxproj index bd8668d12..f63eec1bf 100644 --- a/macosx/HandBrake.xcodeproj/project.pbxproj +++ b/macosx/HandBrake.xcodeproj/project.pbxproj @@ -201,10 +201,14 @@ A95121E61B5F7BE700FD773D /* NSArray+HBAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = A95121E51B5F7BE700FD773D /* NSArray+HBAdditions.m */; }; A955128B1A320B02001BFC6F /* libjansson.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A95512881A320A12001BFC6F /* libjansson.a */; }; A957EBCD218DBE5900007988 /* HBAutoNamer.m in Sources */ = {isa = PBXBuildFile; fileRef = A957EBCC218DBE5900007988 /* HBAutoNamer.m */; }; + A958EAC222E24D6400D83AF4 /* HBQueueTableViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = A958EAC422E24D6400D83AF4 /* HBQueueTableViewController.xib */; }; + A958EAC522E24D6800D83AF4 /* HBQueueDetailsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = A958EAC722E24D6800D83AF4 /* HBQueueDetailsViewController.xib */; }; A95BA15D220C968500A2F9F9 /* HBQueueItem.m in Sources */ = {isa = PBXBuildFile; fileRef = A95BA15C220C968500A2F9F9 /* HBQueueItem.m */; }; A95BA161220CA5DB00A2F9F9 /* HBDistributedArray.m in Sources */ = {isa = PBXBuildFile; fileRef = A95BA160220CA5DB00A2F9F9 /* HBDistributedArray.m */; }; A95BC1E71CD2548A008D6A33 /* volHighTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = A95BC1E51CD2548A008D6A33 /* volHighTemplate.pdf */; }; A95BC1E81CD2548A008D6A33 /* volLowTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = A95BC1E61CD2548A008D6A33 /* volLowTemplate.pdf */; }; + A96127DF22E0994E0086E6DC /* HBQueueDetailsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A96127DD22E0994E0086E6DC /* HBQueueDetailsViewController.m */; }; + A96127E422E09ADD0086E6DC /* HBQueueTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A96127E222E09ADD0086E6DC /* HBQueueTableViewController.m */; }; A96664B01CCE45BF00DA4A57 /* HBPlayerHUDController.m in Sources */ = {isa = PBXBuildFile; fileRef = A96664AE1CCE45BF00DA4A57 /* HBPlayerHUDController.m */; }; A96664B51CCE48F700DA4A57 /* HBPictureHUDController.m in Sources */ = {isa = PBXBuildFile; fileRef = A96664B31CCE48F700DA4A57 /* HBPictureHUDController.m */; }; A96664BA1CCE493D00DA4A57 /* HBEncodingProgressHUDController.m in Sources */ = {isa = PBXBuildFile; fileRef = A96664B81CCE493D00DA4A57 /* HBEncodingProgressHUDController.m */; }; @@ -218,6 +222,7 @@ A973E109216E74AC00D498EC /* HBImageUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = A9CE0A931F57EC4600724577 /* HBImageUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; A973E10C216E74E900D498EC /* HBThumbnailItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = A973E10B216E74E900D498EC /* HBThumbnailItemView.m */; }; A975B02220F7AF29004675CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = A975B02020F7AF29004675CC /* Localizable.strings */; }; + A97ECB8222E1D85500570935 /* HBQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = A97ECB8122E1D85500570935 /* HBQueue.m */; }; A98036CD1CCA91DD007661AA /* HBAVPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = A98036CC1CCA91DD007661AA /* HBAVPlayer.m */; }; A98B8E241C7DD2A200B810C9 /* HBPresetCoding.h in Headers */ = {isa = PBXBuildFile; fileRef = A997D8EB1A4ABB0900E19B6F /* HBPresetCoding.h */; settings = {ATTRIBUTES = (Public, ); }; }; A98C29C41977B10600AF5DED /* HBLanguagesSelection.m in Sources */ = {isa = PBXBuildFile; fileRef = A98C29C31977B10600AF5DED /* HBLanguagesSelection.m */; }; @@ -542,6 +547,8 @@ A957EBCB218DBE5900007988 /* HBAutoNamer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HBAutoNamer.h; sourceTree = "<group>"; }; A957EBCC218DBE5900007988 /* HBAutoNamer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HBAutoNamer.m; sourceTree = "<group>"; }; A958605B2216A5E5002092B1 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = "<group>"; }; + A958EAC322E24D6400D83AF4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/HBQueueTableViewController.xib; sourceTree = "<group>"; }; + A958EAC622E24D6800D83AF4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/HBQueueDetailsViewController.xib; sourceTree = "<group>"; }; A9597A281A49749D00007771 /* HBRange+UIAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "HBRange+UIAdditions.h"; sourceTree = "<group>"; }; A9597A291A49749D00007771 /* HBRange+UIAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "HBRange+UIAdditions.m"; sourceTree = "<group>"; }; A95BA15B220C968500A2F9F9 /* HBQueueItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HBQueueItem.h; sourceTree = "<group>"; }; @@ -551,6 +558,10 @@ A95BC1E51CD2548A008D6A33 /* volHighTemplate.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = volHighTemplate.pdf; sourceTree = "<group>"; }; A95BC1E61CD2548A008D6A33 /* volLowTemplate.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = volLowTemplate.pdf; sourceTree = "<group>"; }; A95CB2FB217B6D07001E9F51 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; }; + A96127DC22E0994E0086E6DC /* HBQueueDetailsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HBQueueDetailsViewController.h; sourceTree = "<group>"; }; + A96127DD22E0994E0086E6DC /* HBQueueDetailsViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HBQueueDetailsViewController.m; sourceTree = "<group>"; }; + A96127E122E09ADD0086E6DC /* HBQueueTableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HBQueueTableViewController.h; sourceTree = "<group>"; }; + A96127E222E09ADD0086E6DC /* HBQueueTableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HBQueueTableViewController.m; sourceTree = "<group>"; }; A9637D9120F7A252001EAE7C /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/HBEncodingProgressHUDController.strings; sourceTree = "<group>"; }; A9637D9220F7A252001EAE7C /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/HBTitleSelection.strings; sourceTree = "<group>"; }; A9637D9320F7A252001EAE7C /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Presets.strings; sourceTree = "<group>"; }; @@ -600,6 +611,8 @@ A975B02120F7AF29004675CC /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; }; A975C08C1AE8C5270061870D /* HBStateFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBStateFormatter.h; sourceTree = "<group>"; }; A975C08D1AE8C5270061870D /* HBStateFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBStateFormatter.m; sourceTree = "<group>"; }; + A97ECB8022E1D85500570935 /* HBQueue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HBQueue.h; sourceTree = "<group>"; }; + A97ECB8122E1D85500570935 /* HBQueue.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HBQueue.m; sourceTree = "<group>"; }; A98036CB1CCA91DD007661AA /* HBAVPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBAVPlayer.h; sourceTree = "<group>"; }; A98036CC1CCA91DD007661AA /* HBAVPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBAVPlayer.m; sourceTree = "<group>"; }; A988AF9B1BC7C35F00932543 /* HBChapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBChapter.h; sourceTree = "<group>"; }; @@ -1076,13 +1089,21 @@ A901C2401BC7CFDC00D77735 /* Queue */ = { isa = PBXGroup; children = ( + A97ECB8022E1D85500570935 /* HBQueue.h */, + A97ECB8122E1D85500570935 /* HBQueue.m */, A95BA15B220C968500A2F9F9 /* HBQueueItem.h */, A95BA15C220C968500A2F9F9 /* HBQueueItem.m */, - A9D3634F2209C08500D8EFEA /* HBQueueItemView.h */, - A9D363502209C08500D8EFEA /* HBQueueItemView.m */, A9AA447C1970726500D7DEFC /* HBQueueController.h */, A9906B2B1A710920001D82D5 /* HBQueueController.m */, A9A96BD220CAD63000A39AFB /* Queue.xib */, + A9D3634F2209C08500D8EFEA /* HBQueueItemView.h */, + A9D363502209C08500D8EFEA /* HBQueueItemView.m */, + A96127E122E09ADD0086E6DC /* HBQueueTableViewController.h */, + A96127E222E09ADD0086E6DC /* HBQueueTableViewController.m */, + A958EAC422E24D6400D83AF4 /* HBQueueTableViewController.xib */, + A96127DC22E0994E0086E6DC /* HBQueueDetailsViewController.h */, + A96127DD22E0994E0086E6DC /* HBQueueDetailsViewController.m */, + A958EAC722E24D6800D83AF4 /* HBQueueDetailsViewController.xib */, ); name = Queue; sourceTree = "<group>"; @@ -1650,6 +1671,7 @@ D86C9DD51C6D372500F06F1B /* Assets.xcassets in Resources */, A9A96BCE20CAD61F00A39AFB /* SubtitlesDefaults.xib in Resources */, A9A96BDA20CAD64B00A39AFB /* PicturePreview.xib in Resources */, + A958EAC222E24D6400D83AF4 /* HBQueueTableViewController.xib in Resources */, A95BC1E71CD2548A008D6A33 /* volHighTemplate.pdf in Resources */, A9A96BB620CAD5D600A39AFB /* HBTitleSelection.xib in Resources */, A9E52CD8218DD52B00E17B86 /* ExceptionAlert.xib in Resources */, @@ -1670,6 +1692,7 @@ A9A96BC820CAD61000A39AFB /* AudioDefaults.xib in Resources */, A9A96BB920CAD5EE00A39AFB /* HBPictureViewController.xib in Resources */, A91943111FB5E39E001E9BB0 /* HBSummaryViewController.xib in Resources */, + A958EAC522E24D6800D83AF4 /* HBQueueDetailsViewController.xib in Resources */, A9A96BBF20CAD5F800A39AFB /* Video.xib in Resources */, A9E1468316BC2AD800C307BC /* PrevTemplate.pdf in Resources */, A937EECB1C6C7C0300EEAE6D /* dsa_pub.pem in Resources */, @@ -1745,6 +1768,7 @@ A916C9991C8449E200C7B560 /* main.mm in Sources */, A973E10C216E74E900D498EC /* HBThumbnailItemView.m in Sources */, A916C9981C8449DB00C7B560 /* HBTitleSelectionController.m in Sources */, + A96127E422E09ADD0086E6DC /* HBQueueTableViewController.m in Sources */, A903C5601CCE78060026B0ED /* NSWindow+HBAdditions.m in Sources */, A9A0CBE81CCEA3670045B3DF /* HBPlayerTrack.m in Sources */, A916C9971C8449CA00C7B560 /* HBAudioDefaultsController.m in Sources */, @@ -1785,6 +1809,8 @@ A95121E61B5F7BE700FD773D /* NSArray+HBAdditions.m in Sources */, A96664B51CCE48F700DA4A57 /* HBPictureHUDController.m in Sources */, A957EBCD218DBE5900007988 /* HBAutoNamer.m in Sources */, + A96127DF22E0994E0086E6DC /* HBQueueDetailsViewController.m in Sources */, + A97ECB8222E1D85500570935 /* HBQueue.m in Sources */, A92B148220CA9F7700146FD8 /* HBHUDView.m in Sources */, A914BCB31BC441C700157917 /* HBPreviewView.m in Sources */, 273F20B714ADBE670021BE6D /* HBPreviewController.m in Sources */, @@ -1944,6 +1970,22 @@ name = MainWindow.xib; sourceTree = "<group>"; }; + A958EAC422E24D6400D83AF4 /* HBQueueTableViewController.xib */ = { + isa = PBXVariantGroup; + children = ( + A958EAC322E24D6400D83AF4 /* Base */, + ); + name = HBQueueTableViewController.xib; + sourceTree = "<group>"; + }; + A958EAC722E24D6800D83AF4 /* HBQueueDetailsViewController.xib */ = { + isa = PBXVariantGroup; + children = ( + A958EAC622E24D6800D83AF4 /* Base */, + ); + name = HBQueueDetailsViewController.xib; + sourceTree = "<group>"; + }; A975B02020F7AF29004675CC /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( |