diff options
29 files changed, 4217 insertions, 2403 deletions
diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m index d1de87a1..09988dbe 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m @@ -87,43 +87,45 @@ typedef struct { */ - (void)_threadedKeepAlive { - @synchronized(self) { - if(keepAliveThread) { - NSLog(@"warning: overwriting existing keepAliveThread: %@, results may be unpredictable!",keepAliveThread); + @autoreleasepool { + @synchronized(self) { + if(keepAliveThread) { + NSLog(@"warning: overwriting existing keepAliveThread: %@, results may be unpredictable!",keepAliveThread); + } + keepAliveThread = [NSThread currentThread]; } - keepAliveThread = [NSThread currentThread]; - } - - [keepAliveThread setName:[NSString stringWithFormat:@"SPMySQL connection keepalive monitor thread (id=%p)", self]]; - // If the maximum number of ping failures has been reached, determine whether to reconnect. - if (keepAliveLastPingBlocked || keepAlivePingFailures >= 3) { + [keepAliveThread setName:[NSString stringWithFormat:@"SPMySQL connection keepalive monitor thread (id=%p)", self]]; - // If the connection has been used within the last fifteen minutes, - // attempt a single reconnection in the background - if (_elapsedSecondsSinceAbsoluteTime(lastConnectionUsedTime) < 60 * 15) { - [self _reconnectAfterBackgroundConnectionLoss]; - } - // Otherwise set the state to connection lost for automatic reconnect on - // next use. - else { - state = SPMySQLConnectionLostInBackground; - } + // If the maximum number of ping failures has been reached, determine whether to reconnect. + if (keepAliveLastPingBlocked || keepAlivePingFailures >= 3) { - // Return as no further ping action required this cycle. - goto end_cleanup; - } + // If the connection has been used within the last fifteen minutes, + // attempt a single reconnection in the background + if (_elapsedSecondsSinceAbsoluteTime(lastConnectionUsedTime) < 60 * 15) { + [self _reconnectAfterBackgroundConnectionLoss]; + } + // Otherwise set the state to connection lost for automatic reconnect on + // next use. + else { + state = SPMySQLConnectionLostInBackground; + } - // Otherwise, perform a background ping. - BOOL pingResult = [self _pingConnectionUsingLoopDelay:10000]; - if (pingResult) { - keepAlivePingFailures = 0; - } else { - keepAlivePingFailures++; - } + // Return as no further ping action required this cycle. + goto end_cleanup; + } + + // Otherwise, perform a background ping. + BOOL pingResult = [self _pingConnectionUsingLoopDelay:10000]; + if (pingResult) { + keepAlivePingFailures = 0; + } else { + keepAlivePingFailures++; + } end_cleanup: - @synchronized(self) { - keepAliveThread = nil; + @synchronized(self) { + keepAliveThread = nil; + } } } diff --git a/Interfaces/English.lproj/ContentPaginationView.xib b/Interfaces/English.lproj/ContentPaginationView.xib index af90ff3e..ce178ae3 100644 --- a/Interfaces/English.lproj/ContentPaginationView.xib +++ b/Interfaces/English.lproj/ContentPaginationView.xib @@ -6,39 +6,17 @@ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6751"/> </dependencies> <objects> - <customObject id="-2" userLabel="File's Owner" customClass="SPTableContent"> + <customObject id="-2" userLabel="File's Owner" customClass="ContentPaginationViewController"> <connections> - <outlet property="paginationBox" destination="2" id="plQ-Ch-kaG"/> <outlet property="paginationGoButton" destination="cXy-Rp-Edv" id="tIC-S1-mTE"/> <outlet property="paginationPageField" destination="Vtc-V2-j0v" id="cwZ-g9-Qbj"/> - <outlet property="paginationView" destination="1" id="46"/> - <outlet property="paginationViewController" destination="hrG-95-pXC" id="yiZ-Ax-bVs"/> + <outlet property="paginationPageStepper" destination="S7c-s1-KCT" id="c4Q-RA-Dny"/> + <outlet property="view" destination="ZlV-bd-j4D" id="zKu-MD-Rhn"/> </connections> </customObject> <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> - <customObject id="-3" userLabel="Application"/> - <customView id="1"> - <rect key="frame" x="0.0" y="0.0" width="330" height="89"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> - <subviews> - <box autoresizesSubviews="NO" wantsLayer="YES" boxType="custom" borderType="line" titlePosition="noTitle" id="2"> - <rect key="frame" x="0.0" y="-47" width="330" height="136"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> - <view key="contentView"> - <rect key="frame" x="1" y="1" width="328" height="134"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> - </view> - <color key="borderColor" white="0.0" alpha="0.20000000300000001" colorSpace="calibratedWhite"/> - <color key="fillColor" white="0.94758063550000005" alpha="1" colorSpace="calibratedWhite"/> - </box> - </subviews> - </customView> + <customObject id="-3" userLabel="Application" customClass="NSObject"/> <userDefaultsController representsSharedInstance="YES" id="24"/> - <viewController id="hrG-95-pXC" userLabel="Popover View Controller"> - <connections> - <outlet property="view" destination="ZlV-bd-j4D" id="zor-2S-MNt"/> - </connections> - </viewController> <customView id="ZlV-bd-j4D" userLabel="Popover Content View"> <rect key="frame" x="0.0" y="0.0" width="330" height="89"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> @@ -51,7 +29,7 @@ <font key="font" metaFont="smallSystem"/> </buttonCell> <connections> - <action selector="navigatePaginationFromButton:" target="-2" id="28B-A6-zUZ"/> + <action selector="paginationGoAction:" target="-2" id="86K-61-7Jl"/> <binding destination="24" name="enabled" keyPath="values.LimitResults" id="AkR-JA-LEs"/> </connections> </button> @@ -69,6 +47,8 @@ </textFieldCell> <connections> <action selector="performClick:" target="cXy-Rp-Edv" id="4Uu-gW-Xhz"/> + <binding destination="-2" name="maxValue" keyPath="maxPage" id="JKa-ju-VyP"/> + <binding destination="-2" name="value" keyPath="page" previousBinding="JKa-ju-VyP" id="21h-9k-7Xk"/> <binding destination="24" name="enabled" keyPath="values.LimitResults" id="rJH-8j-rCm"/> </connections> </textField> @@ -108,7 +88,8 @@ <font key="font" metaFont="smallSystem"/> </stepperCell> <connections> - <action selector="takeIntValueFrom:" target="Vtc-V2-j0v" id="JYr-Pw-YZ2"/> + <binding destination="-2" name="maxValue" keyPath="maxPage" id="h3F-DA-oCz"/> + <binding destination="-2" name="value" keyPath="page" previousBinding="h3F-DA-oCz" id="W48-Lg-Q7t"/> <binding destination="24" name="enabled" keyPath="values.LimitResults" id="Nl1-31-oLD"/> </connections> </stepper> diff --git a/Interfaces/English.lproj/DBView.xib b/Interfaces/English.lproj/DBView.xib index 8742c655..b55710d3 100644 --- a/Interfaces/English.lproj/DBView.xib +++ b/Interfaces/English.lproj/DBView.xib @@ -1,10 +1,12 @@ <?xml version="1.0" encoding="UTF-8"?> -<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6751" systemVersion="13F1507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none"> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none"> <dependencies> <deployment identifier="macosx"/> - <development version="5100" identifier="xcode"/> - <plugIn identifier="com.apple.WebKitIBPlugin" version="11762"/> - <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6751"/> + <development version="8000" identifier="xcode"/> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/> + <plugIn identifier="com.apple.WebKitIBPlugin" version="14109"/> + <capability name="box content view" minToolsVersion="7.0"/> + <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> </dependencies> <objects> <customObject id="-2" userLabel="File's Owner" customClass="SPDatabaseDocument"> @@ -470,19 +472,19 @@ <tabViewItems> <tabViewItem label="Structure" identifier="source" id="28"> <view key="view" identifier="StructureTabView" id="29"> - <rect key="frame" x="10" y="7" width="702" height="546"/> + <rect key="frame" x="10" y="7" width="706" height="546"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> <splitView dividerStyle="thin" id="674"> - <rect key="frame" x="7" y="10" width="691" height="532"/> + <rect key="frame" x="7" y="10" width="695" height="532"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> <customView identifier="TableStructureColumnsView" id="673"> - <rect key="frame" x="0.0" y="0.0" width="691" height="329"/> + <rect key="frame" x="0.0" y="0.0" width="695" height="329"/> <autoresizingMask key="autoresizingMask"/> <subviews> <button toolTip="Edit Table Details (⌘4)" id="6009"> - <rect key="frame" x="628" y="-1" width="32" height="25"/> + <rect key="frame" x="632" y="-1" width="32" height="25"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> <buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="button_edit" imagePosition="overlaps" alignment="center" enabled="NO" state="on" inset="2" id="6010"> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> @@ -495,12 +497,12 @@ </connections> </button> <imageView id="5129"> - <rect key="frame" x="-1" y="0.0" width="688" height="23"/> + <rect key="frame" x="-1" y="0.0" width="692" height="23"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="axesIndependently" image="button_bar_spacer" id="5130"/> </imageView> <popUpButton id="8025"> - <rect key="frame" x="659" y="0.0" width="36" height="23"/> + <rect key="frame" x="663" y="0.0" width="36" height="23"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> <popUpButtonCell key="cell" type="bevel" bezelStyle="regularSquare" imagePosition="right" alignment="center" lineBreakMode="truncatingTail" state="on" inset="2" pullsDown="YES" arrowPosition="noArrow" selectedItem="8028" id="8026"> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> @@ -595,14 +597,14 @@ </connections> </button> <scrollView focusRingType="none" autohidesScrollers="YES" horizontalLineScroll="18" horizontalPageScroll="10" verticalLineScroll="18" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="231"> - <rect key="frame" x="-1" y="22" width="692" height="306"/> + <rect key="frame" x="-1" y="22" width="696" height="306"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <clipView key="contentView" id="07n-i9-O0i"> - <rect key="frame" x="1" y="0.0" width="690" height="305"/> + <rect key="frame" x="1" y="0.0" width="694" height="305"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> <tableView identifier="TableStructureColumnsTableView" focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" alternatingRowBackgroundColors="YES" multipleSelection="NO" autosaveName="SPTableStructureSource" rowHeight="16" headerView="3926" id="232" customClass="SPTableView"> - <rect key="frame" x="0.0" y="0.0" width="690" height="283"/> + <rect key="frame" x="0.0" y="0.0" width="694" height="288"/> <autoresizingMask key="autoresizingMask"/> <size key="intercellSpacing" width="3" height="2"/> <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/> @@ -809,7 +811,7 @@ <autoresizingMask key="autoresizingMask"/> </scroller> <tableHeaderView key="headerView" id="3926"> - <rect key="frame" x="0.0" y="0.0" width="690" height="17"/> + <rect key="frame" x="0.0" y="0.0" width="694" height="17"/> <autoresizingMask key="autoresizingMask"/> <connections> <outlet property="menu" destination="8056" id="Uxa-5O-Q0T"/> @@ -833,11 +835,11 @@ </subviews> </customView> <customView identifier="TableStructureIndexesView" id="672"> - <rect key="frame" x="0.0" y="330" width="691" height="202"/> + <rect key="frame" x="0.0" y="330" width="695" height="202"/> <autoresizingMask key="autoresizingMask"/> <subviews> <imageView id="5148"> - <rect key="frame" x="-1" y="0.0" width="688" height="23"/> + <rect key="frame" x="-1" y="0.0" width="692" height="23"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="axesIndependently" image="button_bar_spacer" id="5149"/> </imageView> @@ -877,20 +879,20 @@ </connections> </button> <scrollView identifier="TableStructureIndexesTableScrollView" focusRingType="none" autohidesScrollers="YES" horizontalLineScroll="18" horizontalPageScroll="10" verticalLineScroll="18" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="287"> - <rect key="frame" x="-1" y="22" width="692" height="160"/> + <rect key="frame" x="-1" y="22" width="696" height="160"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <clipView key="contentView" id="N3x-Gt-GZH"> - <rect key="frame" x="1" y="0.0" width="690" height="159"/> + <rect key="frame" x="1" y="0.0" width="694" height="159"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> <tableView identifier="TableStructureIndexesTableView" focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" alternatingRowBackgroundColors="YES" multipleSelection="NO" autosaveColumns="NO" rowHeight="16" headerView="3923" id="289" customClass="SPTableView"> - <rect key="frame" x="0.0" y="0.0" width="690" height="136"/> + <rect key="frame" x="0.0" y="0.0" width="694" height="142"/> <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 identifier="Non_unique" editable="NO" width="79.5" minWidth="40" maxWidth="1000" id="288"> + <tableColumn identifier="Non_unique" editable="NO" width="80" minWidth="40" maxWidth="1000" id="288"> <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Non_unique"> <font key="font" metaFont="smallSystem"/> <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> @@ -903,7 +905,7 @@ </textFieldCell> <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/> </tableColumn> - <tableColumn identifier="Key_name" editable="NO" width="69.5" minWidth="40" maxWidth="1000" id="290"> + <tableColumn identifier="Key_name" editable="NO" width="70" minWidth="40" maxWidth="1000" id="290"> <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Key_name"> <font key="font" metaFont="smallSystem"/> <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> @@ -916,7 +918,7 @@ </textFieldCell> <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/> </tableColumn> - <tableColumn identifier="Seq_in_index" editable="NO" width="81.5" minWidth="10" maxWidth="1000" id="291"> + <tableColumn identifier="Seq_in_index" editable="NO" width="82" minWidth="10" maxWidth="1000" id="291"> <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Seq_in_index"> <font key="font" metaFont="smallSystem"/> <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> @@ -929,7 +931,7 @@ </textFieldCell> <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/> </tableColumn> - <tableColumn identifier="Column_name" editable="NO" width="88.5" minWidth="10" maxWidth="1000" id="292"> + <tableColumn identifier="Column_name" editable="NO" width="89" minWidth="10" maxWidth="1000" id="292"> <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Column_name"> <font key="font" metaFont="smallSystem"/> <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> @@ -942,7 +944,7 @@ </textFieldCell> <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/> </tableColumn> - <tableColumn identifier="Collation" editable="NO" width="59.5" minWidth="10" maxWidth="1000" id="293"> + <tableColumn identifier="Collation" editable="NO" width="60" minWidth="10" maxWidth="1000" id="293"> <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Collation"> <font key="font" metaFont="smallSystem"/> <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> @@ -955,7 +957,7 @@ </textFieldCell> <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/> </tableColumn> - <tableColumn identifier="Cardinality" editable="NO" width="69.5" minWidth="10" maxWidth="1000" id="294"> + <tableColumn identifier="Cardinality" editable="NO" width="70" minWidth="10" maxWidth="1000" id="294"> <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Cardinality"> <font key="font" metaFont="smallSystem"/> <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> @@ -968,7 +970,7 @@ </textFieldCell> <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/> </tableColumn> - <tableColumn identifier="Sub_part" editable="NO" width="59.5" minWidth="10" maxWidth="1000" id="295"> + <tableColumn identifier="Sub_part" editable="NO" width="60" minWidth="10" maxWidth="1000" id="295"> <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Sub_part"> <font key="font" metaFont="smallSystem"/> <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> @@ -981,7 +983,7 @@ </textFieldCell> <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/> </tableColumn> - <tableColumn identifier="Packed" editable="NO" width="46.5" minWidth="10" maxWidth="1000" id="296"> + <tableColumn identifier="Packed" editable="NO" width="47" minWidth="10" maxWidth="1000" id="296"> <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Packed"> <font key="font" metaFont="smallSystem"/> <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> @@ -1025,12 +1027,12 @@ <autoresizingMask key="autoresizingMask"/> </scroller> <tableHeaderView key="headerView" id="3923"> - <rect key="frame" x="0.0" y="0.0" width="690" height="17"/> + <rect key="frame" x="0.0" y="0.0" width="694" height="17"/> <autoresizingMask key="autoresizingMask"/> </tableHeaderView> </scrollView> <view id="6024"> - <rect key="frame" x="0.0" y="182" width="691" height="20"/> + <rect key="frame" x="0.0" y="182" width="695" height="20"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <subviews> <textField verticalHuggingPriority="750" id="6027"> @@ -1043,7 +1045,7 @@ </textFieldCell> </textField> <imageView id="4503"> - <rect key="frame" x="674" y="3" width="10" height="13"/> + <rect key="frame" x="678" y="3" width="10" height="13"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/> <imageCell key="cell" refusesFirstResponder="YES" alignment="left" image="grabber-horizontal" id="4504"/> </imageView> @@ -1065,302 +1067,137 @@ </tabViewItem> <tabViewItem label="Content" identifier="content" id="26"> <view key="view" identifier="ContentTabView" id="27"> - <rect key="frame" x="10" y="7" width="706" height="544"/> + <rect key="frame" x="10" y="7" width="706" height="546"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> - <imageView id="5179"> - <rect key="frame" x="6" y="10" width="696" height="23"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> - <imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="axesIndependently" image="button_bar_spacer" id="5180"/> - </imageView> - <button toolTip="Add row (⌥⌘A)" id="5175"> - <rect key="frame" x="6" y="9" width="32" height="25"/> - <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> - <buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="button_add" imagePosition="only" alignment="center" alternateImage="button_add" enabled="NO" inset="2" id="5184"> - <behavior key="behavior" lightByContents="YES"/> - <font key="font" metaFont="system"/> - <string key="keyEquivalent">a</string> - <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> - </buttonCell> - <connections> - <action selector="addRow:" target="67" id="5188"/> - </connections> - </button> - <button toolTip="Refresh table contents (⌘R)" id="5176"> - <rect key="frame" x="100" y="9" width="32" height="25"/> - <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> - <buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="button_refresh" imagePosition="overlaps" alignment="center" alternateImage="button_refresh" inset="2" id="5183"> - <behavior key="behavior" lightByContents="YES"/> - <font key="font" metaFont="system"/> - <string key="keyEquivalent">r</string> - <modifierMask key="keyEquivalentModifierMask" command="YES"/> - </buttonCell> - <connections> - <action selector="reloadTable:" target="67" id="5185"/> - </connections> - </button> - <button toolTip="Delete selected row(s) (⌫)" id="5177"> - <rect key="frame" x="37" y="9" width="32" height="25"/> - <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> - <buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="button_remove" imagePosition="only" alignment="center" alternateImage="button_remove" enabled="NO" inset="2" id="5182"> - <behavior key="behavior" lightByContents="YES"/> - <font key="font" metaFont="system"/> - <string key="keyEquivalent"></string> - </buttonCell> - <connections> - <action selector="removeRow:" target="67" id="5187"/> - </connections> - </button> - <button toolTip="Duplicate selected row (⌘D)" id="5178"> - <rect key="frame" x="68" y="9" width="32" height="25"/> - <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> - <buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="button_duplicate" imagePosition="overlaps" alignment="center" alternateImage="button_duplicate" enabled="NO" inset="2" id="5181"> - <behavior key="behavior" lightByContents="YES"/> - <font key="font" metaFont="system"/> - <string key="keyEquivalent">d</string> - <modifierMask key="keyEquivalentModifierMask" command="YES"/> - </buttonCell> - <connections> - <action selector="duplicateRow:" target="67" id="7838"/> - </connections> - </button> - <button toolTip="Toggle between editing simple text cells as a spreadsheet or in pop-up sheets" id="5201"> - <rect key="frame" x="131" y="9" width="32" height="25"/> - <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> - <buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="button_edit_mode" imagePosition="overlaps" alignment="center" alternateImage="button_edit_mode_selected" inset="2" id="5202"> - <behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/> - <font key="font" metaFont="system"/> - </buttonCell> - <connections> - <binding destination="1907" name="value" keyPath="values.EditInSheetEnabled" id="6351"/> - </connections> - </button> - <textField verticalHuggingPriority="750" id="261"> - <rect key="frame" x="166" y="6" width="421" height="22"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> - <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" id="3759"> - <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> - <button toolTip="View next page of results" id="6647"> - <rect key="frame" x="654" y="9" width="32" height="25"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> - <buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="button_right" imagePosition="only" alignment="center" alternateImage="button_right" inset="2" id="6648"> - <behavior key="behavior" lightByContents="YES"/> - <font key="font" metaFont="system"/> - </buttonCell> - <connections> - <action selector="navigatePaginationFromButton:" target="67" id="6666"/> - </connections> - </button> - <button toolTip="View previous page of results" id="6650"> - <rect key="frame" x="592" y="9" width="32" height="25"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> - <buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="button_left" imagePosition="only" alignment="center" alternateImage="button_left" inset="2" id="6651"> - <behavior key="behavior" lightByContents="YES"/> - <font key="font" metaFont="system"/> - </buttonCell> - <connections> - <action selector="navigatePaginationFromButton:" target="67" id="6665"/> - </connections> - </button> - <button toolTip="Jump to page (⌘J) or view pagination options" id="6653"> - <rect key="frame" x="623" y="9" width="32" height="25"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> - <buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="button_pagination" imagePosition="overlaps" alignment="center" inset="2" id="6654"> - <behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/> - <font key="font" metaFont="smallSystem"/> - <string key="keyEquivalent">j</string> - <modifierMask key="keyEquivalentModifierMask" command="YES"/> - </buttonCell> - <connections> - <action selector="togglePagination:" target="67" id="6662"/> - </connections> - </button> - <splitView autosaveName="" dividerStyle="thin" id="yTv-Lq-Y3H" customClass="SPSplitView"> - <rect key="frame" x="6" y="33" width="696" height="505"/> + <customView misplaced="YES" id="GZS-nP-BvF" userLabel="Content Area Runtime Container"> + <rect key="frame" x="7" y="10" width="695" height="529"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> - <customView id="gSO-0R-Xdc"> - <rect key="frame" x="0.0" y="0.0" width="696" height="25"/> - <autoresizingMask key="autoresizingMask"/> + <customView misplaced="YES" id="vcX-Xr-0cm" userLabel="Table Content Container"> + <rect key="frame" x="0.0" y="0.0" width="695" height="458"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> - <scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" usesPredominantAxisScrolling="NO" id="CIQ-tc-1Fn"> - <rect key="frame" x="0.0" y="-1" width="607" height="27"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> - <clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="kdv-Wp-s5h"> - <rect key="frame" x="0.0" y="0.0" width="607" height="27"/> - <autoresizingMask key="autoresizingMask"/> - <subviews> - <ruleEditor nestingMode="compound" canRemoveAllRows="YES" rowHeight="25" id="FF9-z2-9od"> - <rect key="frame" x="0.0" y="0.0" width="607" height="27"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES" flexibleMaxY="YES"/> - <connections> - <outlet property="delegate" destination="ki9-Po-bdr" id="rFd-07-AiC"/> - </connections> - </ruleEditor> - </subviews> - </clipView> - <scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" controlSize="small" horizontal="YES" id="dIO-Od-K6g"> - <rect key="frame" x="-100" y="-100" width="223" height="11"/> - <autoresizingMask key="autoresizingMask"/> - </scroller> - <scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" controlSize="small" horizontal="NO" id="wlW-vM-FUZ"> - <rect key="frame" x="-100" y="-100" width="11" height="133"/> - <autoresizingMask key="autoresizingMask"/> - </scroller> - </scrollView> - <button verticalHuggingPriority="750" id="gTZ-sd-Ef7"> - <rect key="frame" x="615" y="2" width="54" height="19"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> - <buttonCell key="cell" type="roundRect" title="Filter" bezelStyle="roundedRect" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="khN-PI-iEA"> - <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> - <font key="font" metaFont="smallSystem"/> + <imageView id="5179"> + <rect key="frame" x="-1" y="0.0" width="696" height="23"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> + <imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="axesIndependently" image="button_bar_spacer" id="5180"/> + </imageView> + <button toolTip="Add row (⌥⌘A)" id="5175"> + <rect key="frame" x="-1" y="-1" width="32" height="25"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> + <buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="button_add" imagePosition="only" alignment="center" alternateImage="button_add" enabled="NO" inset="2" id="5184"> + <behavior key="behavior" lightByContents="YES"/> + <font key="font" metaFont="system"/> + <string key="keyEquivalent">a</string> + <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> </buttonCell> <connections> - <action selector="filterTable:" target="67" id="RqZ-Kt-0nW"/> + <action selector="addRow:" target="67" id="5188"/> </connections> </button> - </subviews> - </customView> - <customView id="AjX-OA-b5u"> - <rect key="frame" x="0.0" y="26" width="696" height="479"/> - <autoresizingMask key="autoresizingMask"/> - <subviews> - <progressIndicator horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="100" displayedWhenStopped="NO" bezeled="NO" indeterminate="YES" controlSize="small" style="spinning" id="858"> - <rect key="frame" x="674" y="458" width="16" height="16"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/> - </progressIndicator> - <button verticalHuggingPriority="750" id="4676"> - <rect key="frame" x="614" y="455" width="54" height="19"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/> - <buttonCell key="cell" type="roundRect" title="Filter" bezelStyle="roundedRect" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="4677"> - <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <button toolTip="Refresh table contents (⌘R)" id="5176"> + <rect key="frame" x="93" y="-1" width="32" height="25"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> + <buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="button_refresh" imagePosition="overlaps" alignment="center" alternateImage="button_refresh" inset="2" id="5183"> + <behavior key="behavior" lightByContents="YES"/> + <font key="font" metaFont="system"/> + <string key="keyEquivalent">r</string> + <modifierMask key="keyEquivalentModifierMask" command="YES"/> + </buttonCell> + <connections> + <action selector="reloadTable:" target="67" id="5185"/> + </connections> + </button> + <button toolTip="Delete selected row(s) (⌫)" id="5177"> + <rect key="frame" x="30" y="-1" width="32" height="25"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> + <buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="button_remove" imagePosition="only" alignment="center" alternateImage="button_remove" enabled="NO" inset="2" id="5182"> + <behavior key="behavior" lightByContents="YES"/> + <font key="font" metaFont="system"/> + <string key="keyEquivalent"></string> + </buttonCell> + <connections> + <action selector="removeRow:" target="67" id="5187"/> + </connections> + </button> + <button toolTip="Duplicate selected row (⌘D)" id="5178"> + <rect key="frame" x="61" y="-1" width="32" height="25"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> + <buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="button_duplicate" imagePosition="overlaps" alignment="center" alternateImage="button_duplicate" enabled="NO" inset="2" id="5181"> + <behavior key="behavior" lightByContents="YES"/> + <font key="font" metaFont="system"/> + <string key="keyEquivalent">d</string> + <modifierMask key="keyEquivalentModifierMask" command="YES"/> + </buttonCell> + <connections> + <action selector="duplicateRow:" target="67" id="7838"/> + </connections> + </button> + <button toolTip="Toggle between editing simple text cells as a spreadsheet or in pop-up sheets" id="5201"> + <rect key="frame" x="124" y="-1" width="32" height="25"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> + <buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="button_edit_mode" imagePosition="overlaps" alignment="center" alternateImage="button_edit_mode_selected" inset="2" id="5202"> + <behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> + <connections> + <binding destination="1907" name="value" keyPath="values.EditInSheetEnabled" id="6351"/> + </connections> + </button> + <textField verticalHuggingPriority="750" misplaced="YES" id="261"> + <rect key="frame" x="193" y="-4" width="362" height="22"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> + <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" id="3759"> <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> + <button toolTip="View next page of results" id="6647"> + <rect key="frame" x="647" y="-1" width="32" height="25"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> + <buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="button_right" imagePosition="only" alignment="center" alternateImage="button_right" inset="2" id="6648"> + <behavior key="behavior" lightByContents="YES"/> + <font key="font" metaFont="system"/> </buttonCell> <connections> - <action selector="filterTable:" target="67" id="4678"/> + <action selector="navigatePaginationFromButton:" target="67" id="6666"/> </connections> </button> - <splitView vertical="YES" id="7777"> - <rect key="frame" x="-1" y="451" width="606" height="27"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> - <subviews> - <customView id="7778"> - <rect key="frame" x="0.0" y="0.0" width="314" height="27"/> - <autoresizingMask key="autoresizingMask"/> - <subviews> - <textField verticalHuggingPriority="750" id="3971"> - <rect key="frame" x="7" y="6" width="67" height="14"/> - <autoresizingMask key="autoresizingMask" flexibleMinY="YES"/> - <textFieldCell key="cell" controlSize="small" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Search:" id="3972"> - <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> - <popUpButton toolTip="Choose a field you want to use for your search" verticalHuggingPriority="750" id="146"> - <rect key="frame" x="80" y="3" width="119" height="20"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> - <popUpButtonCell key="cell" type="roundRect" title="field" bezelStyle="roundedRect" alignment="left" controlSize="small" lineBreakMode="truncatingTail" enabled="NO" state="on" borderStyle="borderAndBezel" inset="2" arrowPosition="arrowAtCenter" preferredEdge="maxY" selectedItem="144" id="3754"> - <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> - <font key="font" metaFont="smallSystem"/> - <menu key="menu" title="OtherViews" id="147"> - <items> - <menuItem title="field" state="on" id="144"/> - </items> - </menu> - </popUpButtonCell> - <connections> - <action selector="setCompareTypes:" target="67" id="196"/> - </connections> - </popUpButton> - <popUpButton toolTip="Choose a search operator" verticalHuggingPriority="750" id="156"> - <rect key="frame" x="206" y="0.0" width="105" height="25"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/> - <popUpButtonCell key="cell" type="roundRect" title="=" bezelStyle="roundedRect" alignment="left" controlSize="small" lineBreakMode="truncatingTail" enabled="NO" state="on" borderStyle="borderAndBezel" inset="2" arrowPosition="arrowAtCenter" preferredEdge="maxY" selectedItem="154" id="3755"> - <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> - <font key="font" metaFont="smallSystem"/> - <menu key="menu" title="OtherViews" id="157"> - <items> - <menuItem title="=" state="on" id="154"/> - </items> - </menu> - </popUpButtonCell> - <connections> - <action selector="toggleFilterField:" target="67" id="4685"/> - </connections> - </popUpButton> - </subviews> - </customView> - <customView id="7779"> - <rect key="frame" x="323" y="0.0" width="283" height="27"/> - <autoresizingMask key="autoresizingMask"/> - <subviews> - <searchField hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" id="6371"> - <rect key="frame" x="168" y="4" width="110" height="19"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMinY="YES"/> - <searchFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" bezelStyle="round" sendsSearchStringImmediately="YES" sendsWholeSearchString="YES" id="6372"> - <font key="font" metaFont="smallSystem"/> - <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> - <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> - </searchFieldCell> - <connections> - <action selector="filterTable:" target="67" id="6536"/> - </connections> - </searchField> - <searchField hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" id="6369"> - <rect key="frame" x="5" y="4" width="110" height="19"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES"/> - <searchFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" bezelStyle="round" id="6370"> - <font key="font" metaFont="smallSystem"/> - <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> - <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> - </searchFieldCell> - </searchField> - <textField hidden="YES" verticalHuggingPriority="750" id="6364"> - <rect key="frame" x="128" y="6" width="28" height="14"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/> - <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="AND" id="6365"> - <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> - <searchField wantsLayer="YES" verticalHuggingPriority="750" id="3963"> - <rect key="frame" x="5" y="4" width="276" height="19"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> - <searchFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" enabled="NO" borderStyle="bezel" usesSingleLineMode="YES" bezelStyle="round" sendsSearchStringImmediately="YES" sendsWholeSearchString="YES" id="3964"> - <font key="font" metaFont="smallSystem"/> - <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> - <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> - </searchFieldCell> - <connections> - <action selector="filterTable:" target="67" id="4680"/> - </connections> - </searchField> - </subviews> - </customView> - </subviews> - <holdingPriorities> - <real value="250"/> - <real value="250"/> - </holdingPriorities> + <button toolTip="View previous page of results" id="6650"> + <rect key="frame" x="585" y="-1" width="32" height="25"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> + <buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="button_left" imagePosition="only" alignment="center" alternateImage="button_left" inset="2" id="6651"> + <behavior key="behavior" lightByContents="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> <connections> - <outlet property="delegate" destination="67" id="7780"/> + <action selector="navigatePaginationFromButton:" target="67" id="6665"/> </connections> - </splitView> - <scrollView focusRingType="none" autohidesScrollers="YES" horizontalLineScroll="18" horizontalPageScroll="10" verticalLineScroll="18" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="35"> - <rect key="frame" x="-1" y="-2" width="696" height="450"/> + </button> + <button toolTip="Jump to page (⌘J) or view pagination options" id="6653"> + <rect key="frame" x="616" y="-1" width="32" height="25"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> + <buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="button_pagination" imagePosition="overlaps" alignment="center" inset="2" id="6654"> + <behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/> + <font key="font" metaFont="smallSystem"/> + <string key="keyEquivalent">j</string> + <modifierMask key="keyEquivalentModifierMask" command="YES"/> + </buttonCell> + <connections> + <action selector="togglePagination:" target="67" id="6662"/> + </connections> + </button> + <scrollView focusRingType="none" misplaced="YES" autohidesScrollers="YES" horizontalLineScroll="18" horizontalPageScroll="10" verticalLineScroll="18" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="35"> + <rect key="frame" x="-1" y="22" width="695" height="436"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <clipView key="contentView" id="lwO-LP-RWZ"> - <rect key="frame" x="1" y="0.0" width="694" height="449"/> - <autoresizingMask key="autoresizingMask"/> + <rect key="frame" x="1" y="0.0" width="693" height="435"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> <tableView identifier="TableContentTableView" focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="none" alternatingRowBackgroundColors="YES" autosaveColumns="NO" rowHeight="16" headerView="3920" id="36" customClass="SPCopyTable"> - <rect key="frame" x="0.0" y="0.0" width="694" height="426"/> + <rect key="frame" x="0.0" y="0.0" width="693" height="418"/> <autoresizingMask key="autoresizingMask"/> <size key="intercellSpacing" width="3" height="2"/> <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/> @@ -1397,21 +1234,78 @@ <autoresizingMask key="autoresizingMask"/> </scroller> <tableHeaderView key="headerView" id="3920"> - <rect key="frame" x="0.0" y="0.0" width="694" height="17"/> + <rect key="frame" x="0.0" y="0.0" width="693" height="17"/> <autoresizingMask key="autoresizingMask"/> </tableHeaderView> </scrollView> + <progressIndicator wantsLayer="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" misplaced="YES" maxValue="100" displayedWhenStopped="NO" bezeled="NO" indeterminate="YES" controlSize="small" style="spinning" id="858"> + <rect key="frame" x="561" y="4" width="16" height="16"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> + </progressIndicator> + <button toolTip="Show/Hide table content filters" horizontalHuggingPriority="750" verticalHuggingPriority="750" misplaced="YES" id="LYg-Ux-Lph"> + <rect key="frame" x="155" y="-1" width="32" height="25"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> + <buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="button_filter" imagePosition="overlaps" alignment="center" alternateImage="button_filter_active" inset="2" id="WNc-7G-qPQ"> + <behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> + <connections> + <action selector="toggleRuleEditorVisible:" target="67" id="Os7-vn-sUJ"/> + </connections> + </button> + </subviews> + </customView> + <customView misplaced="YES" id="9oo-u0-ndW" userLabel="Filter Rule Editor Container"> + <rect key="frame" x="0.0" y="458" width="695" height="71"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> + <subviews> + <scrollView misplaced="YES" borderType="none" autohidesScrollers="YES" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" id="CIQ-tc-1Fn"> + <rect key="frame" x="0.0" y="31" width="694" height="40"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="kdv-Wp-s5h"> + <rect key="frame" x="0.0" y="0.0" width="694" height="40"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <subviews> + <ruleEditor nestingMode="compound" rowHeight="29" id="FF9-z2-9od"> + <rect key="frame" x="0.0" y="0.0" width="694" height="40"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES" flexibleMaxY="YES"/> + <connections> + <outlet property="delegate" destination="ki9-Po-bdr" id="rFd-07-AiC"/> + </connections> + </ruleEditor> + </subviews> + </clipView> + <scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="dIO-Od-K6g"> + <rect key="frame" x="-100" y="-100" width="223" height="15"/> + <autoresizingMask key="autoresizingMask"/> + </scroller> + <scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="wlW-vM-FUZ"> + <rect key="frame" x="-100" y="-100" width="15" height="133"/> + <autoresizingMask key="autoresizingMask"/> + </scroller> + </scrollView> + <customView misplaced="YES" id="GJn-1I-e7O" customClass="SPFillView"> + <rect key="frame" x="0.0" y="30" width="695" height="1"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> + <userDefinedRuntimeAttributes> + <userDefinedRuntimeAttribute type="string" keyPath="systemColorOfName" value="gridColor"/> + </userDefinedRuntimeAttributes> + </customView> + <button verticalHuggingPriority="750" misplaced="YES" id="4676"> + <rect key="frame" x="616" y="5" width="54" height="19"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> + <buttonCell key="cell" type="roundRect" title="Filter" bezelStyle="roundedRect" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="4677"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="smallSystem"/> + </buttonCell> + <connections> + <action selector="filterTable:" target="ki9-Po-bdr" id="eAC-YD-du3"/> + </connections> + </button> </subviews> </customView> </subviews> - <holdingPriorities> - <real value="250"/> - <real value="250"/> - </holdingPriorities> - <connections> - <outlet property="delegate" destination="ki9-Po-bdr" id="28j-fv-HkI"/> - </connections> - </splitView> + </customView> </subviews> </view> </tabViewItem> @@ -1781,7 +1675,7 @@ Gw <autoresizingMask key="autoresizingMask"/> <subviews> <tableView identifier="CustomQueryResultsTableView" focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" alternatingRowBackgroundColors="YES" autosaveColumns="NO" rowHeight="16" headerView="7227" id="7224" customClass="SPCopyTable"> - <rect key="frame" x="0.0" y="0.0" width="694" height="190"/> + <rect key="frame" x="0.0" y="0.0" width="694" height="196"/> <autoresizingMask key="autoresizingMask"/> <size key="intercellSpacing" width="3" height="2"/> <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/> @@ -1958,14 +1852,14 @@ Gw <rect key="frame" x="109" y="0.0" width="554" height="72"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <clipView key="contentView" id="fhZ-nZ-AwI"> - <rect key="frame" x="1" y="1" width="552" height="70"/> + <rect key="frame" x="1" y="1" width="537" height="70"/> <autoresizingMask key="autoresizingMask"/> <subviews> <textView importsGraphics="NO" richText="NO" findStyle="panel" continuousSpellChecking="YES" smartInsertDelete="YES" id="8236"> - <rect key="frame" x="0.0" y="0.0" width="552" height="70"/> + <rect key="frame" x="0.0" y="0.0" width="537" height="70"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> - <size key="minSize" width="552" height="70"/> + <size key="minSize" width="537" height="70"/> <size key="maxSize" width="1097" height="10000000"/> <color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> <connections> @@ -2003,14 +1897,14 @@ Gw <rect key="frame" x="109" y="0.0" width="554" height="199"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <clipView key="contentView" id="nV8-ly-BSi"> - <rect key="frame" x="1" y="1" width="552" height="197"/> + <rect key="frame" x="1" y="1" width="537" height="197"/> <autoresizingMask key="autoresizingMask"/> <subviews> <textView editable="NO" importsGraphics="NO" richText="NO" id="8242" customClass="SPTextView"> - <rect key="frame" x="0.0" y="0.0" width="552" height="197"/> + <rect key="frame" x="0.0" y="0.0" width="537" height="197"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> - <size key="minSize" width="552" height="197"/> + <size key="minSize" width="537" height="197"/> <size key="maxSize" width="1097" height="10000000"/> <color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> <connections> @@ -2336,7 +2230,7 @@ Gw <autoresizingMask key="autoresizingMask"/> <subviews> <tableView identifier="TableRelationsTableView" focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" alternatingRowBackgroundColors="YES" autosaveColumns="NO" rowHeight="16" headerView="5545" id="5548" customClass="SPCopyTable"> - <rect key="frame" x="0.0" y="0.0" width="694" height="450"/> + <rect key="frame" x="0.0" y="0.0" width="694" height="456"/> <autoresizingMask key="autoresizingMask"/> <size key="intercellSpacing" width="3" height="2"/> <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/> @@ -2523,7 +2417,7 @@ Gw <autoresizingMask key="autoresizingMask"/> <subviews> <tableView identifier="TableTriggersTableView" focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" alternatingRowBackgroundColors="YES" autosaveColumns="NO" rowHeight="16" headerView="6704" id="6701" customClass="SPCopyTable"> - <rect key="frame" x="0.0" y="0.0" width="694" height="450"/> + <rect key="frame" x="0.0" y="0.0" width="694" height="456"/> <autoresizingMask key="autoresizingMask"/> <size key="intercellSpacing" width="3" height="2"/> <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/> @@ -3417,7 +3311,7 @@ Gw <box autoresizesSubviews="NO" borderType="line" title="Table:" id="5660"> <rect key="frame" x="17" y="231" width="345" height="56"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> - <view key="contentView" id="G3T-Zw-qVw"> + <view key="contentView" id="XVR-xN-dOO"> <rect key="frame" x="1" y="1" width="343" height="40"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> @@ -3454,7 +3348,7 @@ Gw <box autoresizesSubviews="NO" borderType="line" title="Name" id="7798"> <rect key="frame" x="17" y="291" width="345" height="58"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> - <view key="contentView" id="PBs-KU-8Dt"> + <view key="contentView" id="uQj-ax-2Ux"> <rect key="frame" x="1" y="1" width="343" height="42"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> @@ -3485,7 +3379,7 @@ Gw <box autoresizesSubviews="NO" borderType="line" title="Action" id="5659"> <rect key="frame" x="17" y="49" width="345" height="87"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> - <view key="contentView" id="Z9B-3e-PSv"> + <view key="contentView" id="fmY-UV-qsS"> <rect key="frame" x="1" y="1" width="343" height="71"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> @@ -3555,7 +3449,7 @@ Gw <box autoresizesSubviews="NO" borderType="line" title="References" id="5652"> <rect key="frame" x="17" y="140" width="345" height="87"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> - <view key="contentView" id="APb-Qe-OSq"> + <view key="contentView" id="pjf-Q1-6fe"> <rect key="frame" x="1" y="1" width="343" height="71"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> @@ -3674,7 +3568,7 @@ Gw <box autoresizesSubviews="NO" borderType="line" title="Trigger:" id="6770"> <rect key="frame" x="17" y="219" width="326" height="109"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> - <view key="contentView" id="Otf-WX-kyk"> + <view key="contentView" id="vJS-Ph-4at"> <rect key="frame" x="1" y="1" width="324" height="93"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> @@ -3891,14 +3785,14 @@ DQ <rect key="frame" x="-1" y="35" width="383" height="206"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <clipView key="contentView" id="w4Z-p1-GZA"> - <rect key="frame" x="1" y="1" width="381" height="204"/> + <rect key="frame" x="1" y="1" width="366" height="204"/> <autoresizingMask key="autoresizingMask"/> <subviews> <textView editable="NO" importsGraphics="NO" id="8219"> - <rect key="frame" x="0.0" y="0.0" width="381" height="204"/> + <rect key="frame" x="0.0" y="0.0" width="366" height="204"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> - <size key="minSize" width="381" height="204"/> + <size key="minSize" width="366" height="204"/> <size key="maxSize" width="753" height="10000000"/> <color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> <connections> @@ -4071,14 +3965,14 @@ Gw <rect key="frame" x="20" y="45" width="365" height="177"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <clipView key="contentView" id="aEl-zD-ga2"> - <rect key="frame" x="1" y="1" width="363" height="175"/> + <rect key="frame" x="1" y="1" width="348" height="175"/> <autoresizingMask key="autoresizingMask"/> <subviews> <textView editable="NO" importsGraphics="NO" richText="NO" id="8202"> - <rect key="frame" x="0.0" y="0.0" width="363" height="175"/> + <rect key="frame" x="0.0" y="0.0" width="348" height="175"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> - <size key="minSize" width="363" height="175"/> + <size key="minSize" width="348" height="175"/> <size key="maxSize" width="717" height="10000000"/> <color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> </textView> @@ -4293,310 +4187,6 @@ Gw <outlet property="initialFirstResponder" destination="5452" id="5525"/> </connections> </window> - <window title="Advanced Filter" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="SPTableFilterPanel" animationBehavior="default" id="8068" userLabel="Advanced Filter Table Window" customClass="NSPanel"> - <windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES" nonactivatingPanel="YES"/> - <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> - <rect key="contentRect" x="162" y="162" width="752" height="317"/> - <rect key="screenRect" x="0.0" y="0.0" width="1920" height="1178"/> - <value key="minSize" type="size" width="600" height="317"/> - <view key="contentView" id="8069" userLabel="Filter Table Window"> - <rect key="frame" x="0.0" y="0.0" width="752" height="317"/> - <autoresizingMask key="autoresizingMask"/> - <subviews> - <splitView dividerStyle="thin" id="8079" customClass="SPSplitView"> - <rect key="frame" x="0.0" y="40" width="752" height="277"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> - <subviews> - <customView id="8080"> - <rect key="frame" x="0.0" y="0.0" width="752" height="141"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> - <subviews> - <scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="8141"> - <rect key="frame" x="0.0" y="0.0" width="752" height="142"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> - <clipView key="contentView" id="e9P-dP-cyb"> - <rect key="frame" x="0.0" y="0.0" width="752" height="142"/> - <autoresizingMask key="autoresizingMask"/> - <subviews> - <tableView identifier="AdvancedFilterTableView" verticalHuggingPriority="750" allowsExpansionToolTips="YES" selectionHighlightStyle="sourceList" alternatingRowBackgroundColors="YES" emptySelection="NO" autosaveColumns="NO" headerView="8145" id="8144" customClass="SPCopyTable"> - <rect key="frame" x="0.0" y="0.0" width="752" height="119"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> - <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="748.921875" minWidth="40" maxWidth="1000" id="8146"> - <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" white="0.33333298560000002" alpha="1" colorSpace="calibratedWhite"/> - </tableHeaderCell> - <textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="8149"> - <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"/> - </tableColumn> - </tableColumns> - <connections> - <outlet property="dataSource" destination="67" id="8157"/> - <outlet property="delegate" destination="67" id="8158"/> - </connections> - </tableView> - </subviews> - </clipView> - <scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="8143"> - <rect key="frame" x="1" y="95.851600000000005" width="751.5" height="15"/> - <autoresizingMask key="autoresizingMask"/> - </scroller> - <scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="8142"> - <rect key="frame" x="224" y="17" width="15" height="102"/> - <autoresizingMask key="autoresizingMask"/> - </scroller> - <tableHeaderView key="headerView" id="8145"> - <rect key="frame" x="0.0" y="0.0" width="752" height="17"/> - <autoresizingMask key="autoresizingMask"/> - </tableHeaderView> - </scrollView> - </subviews> - </customView> - <customView id="8081"> - <rect key="frame" x="0.0" y="142" width="752" height="135"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> - <subviews> - <button toolTip="Perform filter while typing (⌘T)" id="8129"> - <rect key="frame" x="537" y="97" width="197" height="18"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/> - <buttonCell key="cell" type="check" title="Search while typing" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" inset="2" id="8130"> - <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> - <font key="font" metaFont="smallSystem"/> - <string key="keyEquivalent">t</string> - <modifierMask key="keyEquivalentModifierMask" command="YES"/> - </buttonCell> - </button> - <button toolTip="If set negate entire WHERE clause (⌘N)" id="8131"> - <rect key="frame" x="537" y="77" width="197" height="18"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/> - <buttonCell key="cell" type="check" title="Negate clause" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" inset="2" id="8132"> - <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> - <font key="font" metaFont="smallSystem"/> - <string key="keyEquivalent">n</string> - <modifierMask key="keyEquivalentModifierMask" command="YES"/> - </buttonCell> - <connections> - <action selector="toggleNegateClause:" target="67" id="8172"/> - </connections> - </button> - <button toolTip="Perform filtering by using SELECT DISTINCT" id="8133"> - <rect key="frame" x="537" y="57" width="197" height="18"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/> - <buttonCell key="cell" type="check" title="Select distinct" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" inset="2" id="8134"> - <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> - <font key="font" metaFont="smallSystem"/> - <string key="keyEquivalent">d</string> - <modifierMask key="keyEquivalentModifierMask" command="YES"/> - </buttonCell> - <connections> - <action selector="toggleDistinctSelect:" target="67" id="8170"/> - </connections> - </button> - <textField verticalHuggingPriority="750" id="8135"> - <rect key="frame" x="537" y="37" width="137" height="14"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/> - <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Default operator:" id="8136"> - <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> - <button toolTip="Set default operator which should be used if pattern doen't begin with an operator" verticalHuggingPriority="750" id="8137"> - <rect key="frame" x="678" y="35" width="55" height="16"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/> - <buttonCell key="cell" type="push" title="Edit" bezelStyle="rounded" alignment="center" controlSize="mini" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="8138"> - <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> - <font key="font" metaFont="miniSystem"/> - <string key="keyEquivalent">o</string> - <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> - </buttonCell> - <connections> - <action selector="setDefaultOperator:" target="67" id="8163"/> - </connections> - </button> - <textField verticalHuggingPriority="750" id="8139"> - <rect key="frame" x="554" y="19" width="181" height="14"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/> - <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="LIKE '%@%'" id="8140"> - <font key="font" metaFont="smallSystem"/> - <color key="textColor" name="disabledControlTextColor" catalog="System" colorSpace="catalog"/> - <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> - </textFieldCell> - <connections> - <binding destination="1907" name="value" keyPath="values.FilterTableDefaultOperator" id="8167"/> - </connections> - </textField> - <textField verticalHuggingPriority="750" id="8180"> - <rect key="frame" x="17" y="115" width="294" height="14"/> - <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> - <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="WHERE query" id="8181"> - <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="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="8204"> - <rect key="frame" x="20" y="21" width="512" height="92"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> - <clipView key="contentView" copiesOnScroll="NO" id="FLj-pH-h40"> - <rect key="frame" x="1" y="1" width="510" height="90"/> - <autoresizingMask key="autoresizingMask"/> - <subviews> - <textView editable="NO" importsGraphics="NO" findStyle="panel" allowsNonContiguousLayout="YES" id="8207" customClass="SPTextView"> - <rect key="frame" x="0.0" y="0.0" width="510" height="90"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> - <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> - <size key="minSize" width="510" height="90"/> - <size key="maxSize" width="518" height="10000000"/> - <color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> - <connections> - <outlet property="delegate" destination="67" id="8208"/> - </connections> - </textView> - </subviews> - </clipView> - <scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="8206"> - <rect key="frame" x="1" y="119" width="232" height="15"/> - <autoresizingMask key="autoresizingMask"/> - </scroller> - <scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="8205"> - <rect key="frame" x="224" y="1" width="15" height="127"/> - <autoresizingMask key="autoresizingMask"/> - </scroller> - </scrollView> - </subviews> - </customView> - </subviews> - <holdingPriorities> - <real value="250"/> - <real value="250"/> - </holdingPriorities> - </splitView> - <button toolTip="Apply Filter to current Table (⌘F)" verticalHuggingPriority="750" id="8083"> - <rect key="frame" x="595" y="12" width="143" height="32"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> - <buttonCell key="cell" type="push" title="Filter" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="8084"> - <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> - <font key="font" metaFont="system"/> - <string key="keyEquivalent">f</string> - <modifierMask key="keyEquivalentModifierMask" command="YES"/> - </buttonCell> - <connections> - <action selector="filterTable:" target="67" id="8176"/> - </connections> - </button> - <button toolTip="Create a WHERE clause to search for the last typed pattern in all fields" verticalHuggingPriority="750" id="8127"> - <rect key="frame" x="14" y="12" width="216" height="32"/> - <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> - <buttonCell key="cell" type="push" title="Search all fields" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="8128"> - <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> - <font key="font" metaFont="system"/> - <string key="keyEquivalent">a</string> - <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> - </buttonCell> - <connections> - <action selector="toggleLookAllFieldsMode:" target="67" id="8168"/> - </connections> - </button> - <button toolTip="Clear all Filter Criterias (⌘⌫)" verticalHuggingPriority="750" id="8085"> - <rect key="frame" x="452" y="12" width="143" height="32"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> - <buttonCell key="cell" type="push" title="Clear" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="8086"> - <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> - <font key="font" metaFont="system"/> - <string key="keyEquivalent"></string> - <modifierMask key="keyEquivalentModifierMask" command="YES"/> - </buttonCell> - <connections> - <action selector="tableFilterClear:" target="67" id="8175"/> - </connections> - </button> - </subviews> - </view> - <connections> - <outlet property="initialFirstResponder" destination="8144" id="8159"/> - </connections> - </window> - <window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="7609" userLabel="Filter Table Set Default Operator Sheet" customClass="NSPanel"> - <windowStyleMask key="styleMask" titled="YES"/> - <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> - <rect key="contentRect" x="235" y="418" width="251" height="102"/> - <rect key="screenRect" x="0.0" y="0.0" width="1920" height="1178"/> - <value key="minSize" type="size" width="251" height="102"/> - <value key="maxSize" type="size" width="251" height="102"/> - <view key="contentView" id="7610"> - <rect key="frame" x="0.0" y="0.0" width="251" height="102"/> - <autoresizingMask key="autoresizingMask"/> - <subviews> - <button verticalHuggingPriority="750" tag="1" id="7615"> - <rect key="frame" x="140" y="13" width="96" height="28"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> - <buttonCell key="cell" type="push" title="Save" bezelStyle="rounded" alignment="center" controlSize="small" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="7616"> - <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> - <font key="font" metaFont="smallSystem"/> - <string key="keyEquivalent" base64-UTF8="YES"> -DQ -</string> - </buttonCell> - <connections> - <action selector="closeSheet:" target="67" id="7627"/> - </connections> - </button> - <button verticalHuggingPriority="750" id="7617"> - <rect key="frame" x="46" y="13" width="96" height="28"/> - <autoresizingMask key="autoresizingMask" flexibleMinX="YES"/> - <buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" controlSize="small" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="7618"> - <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> - <font key="font" metaFont="smallSystem"/> - <string key="keyEquivalent" base64-UTF8="YES"> -Gw -</string> - </buttonCell> - <connections> - <action selector="closeSheet:" target="67" id="7628"/> - </connections> - </button> - <textField verticalHuggingPriority="750" id="7619"> - <rect key="frame" x="17" y="76" width="208" height="14"/> - <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> - <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Enter Filter Table's Default Operator:" id="7620"> - <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> - <comboBox verticalHuggingPriority="750" id="7629"> - <rect key="frame" x="20" y="51" width="214" height="22"/> - <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> - <comboBoxCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" drawsBackground="YES" completes="NO" numberOfVisibleItems="5" id="7630"> - <font key="font" metaFont="smallSystem"/> - <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> - <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> - </comboBoxCell> - </comboBox> - <button horizontalHuggingPriority="750" verticalHuggingPriority="750" id="7814"> - <rect key="frame" x="17" y="14" width="25" height="25"/> - <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> - <buttonCell key="cell" type="help" bezelStyle="helpButton" alignment="center" controlSize="small" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="7815"> - <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> - <font key="font" metaFont="smallSystem"/> - </buttonCell> - <connections> - <action selector="showDefaultOperaterHelp:" target="67" id="7816"/> - </connections> - </button> - </subviews> - </view> - </window> <window title="Query Favorite Sheet" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="6405" userLabel="Query Favorite Sheet" customClass="NSPanel"> <windowStyleMask key="styleMask" titled="YES" resizable="YES"/> <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> @@ -4821,7 +4411,7 @@ Gw <autoresizingMask key="autoresizingMask"/> <subviews> <tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" selectionHighlightStyle="sourceList" alternatingRowBackgroundColors="YES" columnReordering="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="14" headerView="6890" id="6889"> - <rect key="frame" x="0.0" y="0.0" width="358" height="133"/> + <rect key="frame" x="0.0" y="0.0" width="358" height="139"/> <autoresizingMask key="autoresizingMask"/> <size key="intercellSpacing" width="3" height="2"/> <color key="backgroundColor" name="_sourceListBackgroundColor" catalog="System" colorSpace="catalog"/> @@ -5002,54 +4592,43 @@ Gw <customObject id="67" userLabel="SPTableContent" customClass="SPTableContent"> <connections> <outlet property="addButton" destination="5175" id="5189"/> - <outlet property="argumentField" destination="3963" id="3965"/> - <outlet property="betweenTextField" destination="6364" id="6368"/> - <outlet property="compareField" destination="156" id="166"/> - <outlet property="contentSplitView" destination="yTv-Lq-Y3H" id="BQr-g6-UG3"/> + <outlet property="contentAreaContainer" destination="GZS-nP-BvF" id="deL-A4-mpc"/> <outlet property="contentViewPane" destination="27" id="6661"/> <outlet property="countText" destination="261" id="262"/> <outlet property="duplicateButton" destination="5178" id="7837"/> - <outlet property="fieldField" destination="146" id="164"/> - <outlet property="filterButton" destination="4676" id="5942"/> - <outlet property="filterControllerInstance" destination="ki9-Po-bdr" id="5od-0U-9xj"/> - <outlet property="filterTableDistinctCheckbox" destination="8133" id="8171"/> - <outlet property="filterTableFilterButton" destination="8083" id="8177"/> - <outlet property="filterTableLiveSearchCheckbox" destination="8129" id="8174"/> - <outlet property="filterTableNegateCheckbox" destination="8131" id="8173"/> - <outlet property="filterTableQueryTitle" destination="8180" id="8184"/> - <outlet property="filterTableSearchAllFields" destination="8127" id="8169"/> - <outlet property="filterTableSetDefaultOperatorSheet" destination="7609" id="7632"/> - <outlet property="filterTableSetDefaultOperatorValue" destination="7629" id="7631"/> - <outlet property="filterTableSplitView" destination="8079" id="8178"/> - <outlet property="filterTableView" destination="8144" id="8160"/> - <outlet property="filterTableWhereClause" destination="8207" id="8209"/> - <outlet property="filterTableWindow" destination="8068" id="8161"/> - <outlet property="firstBetweenField" destination="6369" id="6373"/> + <outlet property="filterRuleEditorContainer" destination="9oo-u0-ndW" id="OZb-jI-CMy"/> + <outlet property="filterTableController" destination="UBS-cN-3Af" id="FV7-by-Gy1"/> <outlet property="multipleLineEditingButton" destination="5201" id="5203"/> + <outlet property="paginationBox" destination="Cen-HW-aJR" id="cE5-7g-Pzn"/> <outlet property="paginationButton" destination="6653" id="6657"/> <outlet property="paginationNextButton" destination="6647" id="6664"/> <outlet property="paginationPreviousButton" destination="6650" id="6663"/> + <outlet property="paginationView" destination="EmC-GF-Hfu" id="l4X-ze-hG5"/> <outlet property="reloadButton" destination="5176" id="6625"/> <outlet property="removeButton" destination="5177" id="5190"/> - <outlet property="secondBetweenField" destination="6371" id="6374"/> + <outlet property="ruleFilterController" destination="ki9-Po-bdr" id="5od-0U-9xj"/> <outlet property="spHistoryControllerInstance" destination="6297" id="6316"/> + <outlet property="tableContentContainer" destination="vcX-Xr-0cm" id="Xak-w0-y6x"/> <outlet property="tableContentView" destination="36" id="142"/> <outlet property="tableDataInstance" destination="4702" id="4712"/> <outlet property="tableDocumentInstance" destination="-2" id="900"/> <outlet property="tableInfoInstance" destination="4488" id="6352"/> <outlet property="tableSourceInstance" destination="69" id="6882"/> <outlet property="tablesListInstance" destination="68" id="1026"/> + <outlet property="toggleRuleFilterButton" destination="LYg-Ux-Lph" id="9eY-dL-AhC"/> </connections> </customObject> - <customObject id="ki9-Po-bdr" userLabel="SPTableContentFilter" customClass="SPTableContentFilterController"> + <customObject id="ki9-Po-bdr" userLabel="SPRuleFilter" customClass="SPRuleFilterController"> <connections> - <outlet property="contentSplitView" destination="yTv-Lq-Y3H" id="KB1-gk-Ssi"/> + <outlet property="filterButton" destination="4676" id="9tZ-dW-BR3"/> <outlet property="filterRuleEditor" destination="FF9-z2-9od" id="RW4-XM-XQS"/> + <outlet property="tableContentViewBelow" destination="36" id="ZGh-dM-J6C"/> <outlet property="tableDataInstance" destination="4702" id="e69-W6-UwN"/> <outlet property="tableDocumentInstance" destination="-2" id="2Xe-WU-zRw"/> <outlet property="tablesListInstance" destination="68" id="byh-Eh-UAv"/> </connections> </customObject> + <customObject id="UBS-cN-3Af" customClass="SPFilterTableController"/> <customObject id="362" userLabel="TableDump" customClass="SPDataImport"> <connections> <outlet property="customQueryInstance" destination="134" id="953"/> @@ -5059,7 +4638,6 @@ Gw <outlet property="singleProgressSheet" destination="6125" id="6139"/> <outlet property="singleProgressText" destination="6130" id="6140"/> <outlet property="singleProgressTitle" destination="6132" id="6238"/> - <outlet property="tableContentInstance" destination="67" id="1025"/> <outlet property="tableDataInstance" destination="4702" id="4713"/> <outlet property="tableDocumentInstance" destination="-2" id="534"/> <outlet property="tableSourceInstance" destination="69" id="1172"/> @@ -5577,6 +5155,23 @@ Gw </view> <point key="canvasLocation" x="98" y="659.5"/> </window> + <customView id="EmC-GF-Hfu"> + <rect key="frame" x="0.0" y="0.0" width="330" height="89"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> + <subviews> + <box autoresizesSubviews="NO" wantsLayer="YES" boxType="custom" borderType="line" titlePosition="noTitle" id="Cen-HW-aJR"> + <rect key="frame" x="0.0" y="-47" width="330" height="136"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> + <view key="contentView" id="yNs-cH-TWp"> + <rect key="frame" x="1" y="1" width="328" height="134"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + </view> + <color key="borderColor" white="0.0" alpha="0.20000000300000001" colorSpace="calibratedWhite"/> + <color key="fillColor" white="0.94758063550000005" alpha="1" colorSpace="calibratedWhite"/> + </box> + </subviews> + <point key="canvasLocation" x="19" y="-25"/> + </customView> </objects> <resources> <image name="NSAdvanced" width="32" height="32"/> @@ -5594,6 +5189,8 @@ Gw <image name="button_edit" width="30" height="22"/> <image name="button_edit_mode" width="30" height="22"/> <image name="button_edit_mode_selected" width="30" height="22"/> + <image name="button_filter" width="30" height="22"/> + <image name="button_filter_active" width="30" height="22"/> <image name="button_info_pane_show" width="30" height="22"/> <image name="button_left" width="30" height="22"/> <image name="button_pagination" width="30" height="22"/> diff --git a/Interfaces/English.lproj/FilterTableWindow.xib b/Interfaces/English.lproj/FilterTableWindow.xib new file mode 100644 index 00000000..384ec859 --- /dev/null +++ b/Interfaces/English.lproj/FilterTableWindow.xib @@ -0,0 +1,337 @@ +<?xml version="1.0" encoding="UTF-8"?> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14109" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none"> + <dependencies> + <deployment identifier="macosx"/> + <development version="8000" identifier="xcode"/> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/> + <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> + </dependencies> + <objects> + <customObject id="-2" userLabel="File's Owner" customClass="SPFilterTableController"> + <connections> + <outlet property="filterTableClearButton" destination="egJ-46-zsK" id="Fq2-LD-h16"/> + <outlet property="filterTableDistinctCheckbox" destination="kkn-vg-a1j" id="TDf-pu-HAk"/> + <outlet property="filterTableFilterButton" destination="97a-9g-t3j" id="zGX-CX-g2a"/> + <outlet property="filterTableLiveSearchCheckbox" destination="eW4-hf-ige" id="PR5-mS-LUC"/> + <outlet property="filterTableNegateCheckbox" destination="hdW-5X-UDu" id="NRU-CX-c0O"/> + <outlet property="filterTableQueryTitle" destination="WnE-kO-RxG" id="sVM-Sr-Er2"/> + <outlet property="filterTableSearchAllFields" destination="dCH-wr-KbJ" id="9xp-Cs-5GB"/> + <outlet property="filterTableSetDefaultOperatorSheet" destination="bXg-MQ-c5g" id="Ph2-a6-R2H"/> + <outlet property="filterTableSetDefaultOperatorValue" destination="KZQ-WN-rtU" id="QzL-fH-WTe"/> + <outlet property="filterTableSplitView" destination="Xk5-iI-QZb" id="Bv6-90-fDH"/> + <outlet property="filterTableView" destination="8SC-Fh-D9L" id="nIF-tg-501"/> + <outlet property="filterTableWhereClause" destination="NLm-8l-MrV" id="hMK-Jf-ydK"/> + <outlet property="window" destination="nzu-CY-UY7" id="EAC-V1-Oqh"/> + </connections> + </customObject> + <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> + <customObject id="-3" userLabel="Application" customClass="NSObject"/> + <window title="Advanced Filter" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="SPTableFilterPanel" animationBehavior="default" id="nzu-CY-UY7" userLabel="Advanced Filter Table Window" customClass="NSPanel"> + <windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES" nonactivatingPanel="YES"/> + <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> + <rect key="contentRect" x="162" y="162" width="752" height="317"/> + <rect key="screenRect" x="0.0" y="0.0" width="1920" height="1057"/> + <value key="minSize" type="size" width="600" height="317"/> + <view key="contentView" id="mgs-0s-XcR" userLabel="Filter Table Window"> + <rect key="frame" x="0.0" y="0.0" width="752" height="317"/> + <autoresizingMask key="autoresizingMask"/> + <subviews> + <splitView dividerStyle="thin" id="Xk5-iI-QZb" customClass="SPSplitView"> + <rect key="frame" x="0.0" y="40" width="752" height="277"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <subviews> + <customView id="P9T-cb-A1O"> + <rect key="frame" x="0.0" y="0.0" width="752" height="141"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <subviews> + <scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="Zmg-mY-I5z"> + <rect key="frame" x="0.0" y="0.0" width="752" height="142"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <clipView key="contentView" id="PO1-a7-WBE"> + <rect key="frame" x="0.0" y="23" width="752" height="119"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <subviews> + <tableView identifier="AdvancedFilterTableView" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="none" selectionHighlightStyle="sourceList" alternatingRowBackgroundColors="YES" emptySelection="NO" autosaveColumns="NO" headerView="eWi-Z3-2Sr" id="8SC-Fh-D9L" customClass="SPCopyTable"> + <rect key="frame" x="0.0" y="0.0" width="752" height="119"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <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 identifier="" width="748.921875" minWidth="40" maxWidth="1000" id="VGf-vY-BT6"> + <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" white="0.33333298560000002" alpha="1" colorSpace="calibratedWhite"/> + </tableHeaderCell> + <textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="isR-QM-Zos"> + <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"/> + </tableColumn> + </tableColumns> + <connections> + <outlet property="dataSource" destination="-2" id="dtT-f4-3yI"/> + <outlet property="delegate" destination="-2" id="hvw-73-bmw"/> + </connections> + </tableView> + </subviews> + </clipView> + <scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="E65-Wo-BZ0"> + <rect key="frame" x="1" y="95.851600000000005" width="751.5" height="15"/> + <autoresizingMask key="autoresizingMask"/> + </scroller> + <scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="Vnq-gG-GNh"> + <rect key="frame" x="224" y="17" width="15" height="102"/> + <autoresizingMask key="autoresizingMask"/> + </scroller> + <tableHeaderView key="headerView" id="eWi-Z3-2Sr"> + <rect key="frame" x="0.0" y="0.0" width="752" height="23"/> + <autoresizingMask key="autoresizingMask"/> + </tableHeaderView> + </scrollView> + </subviews> + </customView> + <customView id="86d-uH-suk"> + <rect key="frame" x="0.0" y="142" width="752" height="135"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <subviews> + <button toolTip="Perform filter while typing (⌘T)" id="eW4-hf-ige"> + <rect key="frame" x="537" y="97" width="197" height="18"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/> + <buttonCell key="cell" type="check" title="Search while typing" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" inset="2" id="XTP-ut-VQL"> + <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> + <font key="font" metaFont="smallSystem"/> + <string key="keyEquivalent">t</string> + <modifierMask key="keyEquivalentModifierMask" command="YES"/> + </buttonCell> + </button> + <button toolTip="If set negate entire WHERE clause (⌘N)" id="hdW-5X-UDu"> + <rect key="frame" x="537" y="77" width="197" height="18"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/> + <buttonCell key="cell" type="check" title="Negate clause" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" inset="2" id="F8k-g6-s93"> + <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> + <font key="font" metaFont="smallSystem"/> + <string key="keyEquivalent">n</string> + <modifierMask key="keyEquivalentModifierMask" command="YES"/> + </buttonCell> + <connections> + <action selector="toggleNegateClause:" target="-2" id="leY-aP-oLa"/> + </connections> + </button> + <button toolTip="Perform filtering by using SELECT DISTINCT" id="kkn-vg-a1j"> + <rect key="frame" x="537" y="57" width="197" height="18"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/> + <buttonCell key="cell" type="check" title="Select distinct" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" inset="2" id="n3P-Lk-0tv"> + <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> + <font key="font" metaFont="smallSystem"/> + <string key="keyEquivalent">d</string> + <modifierMask key="keyEquivalentModifierMask" command="YES"/> + </buttonCell> + <connections> + <action selector="toggleDistinctSelect:" target="-2" id="8jm-si-8p2"/> + </connections> + </button> + <textField verticalHuggingPriority="750" id="Lir-gD-3FT"> + <rect key="frame" x="537" y="37" width="137" height="14"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/> + <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Default operator:" id="fOH-Fz-FbJ"> + <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> + <button toolTip="Set default operator which should be used if pattern doen't begin with an operator" verticalHuggingPriority="750" id="AnE-dT-3rB"> + <rect key="frame" x="678" y="35" width="55" height="16"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/> + <buttonCell key="cell" type="push" title="Edit" bezelStyle="rounded" alignment="center" controlSize="mini" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XWS-uW-aVd"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="miniSystem"/> + <string key="keyEquivalent">o</string> + <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> + </buttonCell> + <connections> + <action selector="setDefaultOperator:" target="-2" id="4EO-rc-z56"/> + </connections> + </button> + <textField verticalHuggingPriority="750" id="eRx-Pl-FwB"> + <rect key="frame" x="554" y="19" width="181" height="14"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/> + <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="LIKE '%@%'" id="Hag-Ug-A4W"> + <font key="font" metaFont="smallSystem"/> + <color key="textColor" name="disabledControlTextColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + <connections> + <binding destination="E5e-Af-qLe" name="value" keyPath="values.FilterTableDefaultOperator" id="snR-X9-Dad"/> + </connections> + </textField> + <textField verticalHuggingPriority="750" id="WnE-kO-RxG"> + <rect key="frame" x="17" y="115" width="294" height="14"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="WHERE query" id="iRJ-gQ-rJy"> + <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="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="xjR-JS-f3q"> + <rect key="frame" x="20" y="21" width="512" height="92"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <clipView key="contentView" copiesOnScroll="NO" id="TBT-Nn-Rpn"> + <rect key="frame" x="1" y="1" width="510" height="90"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <subviews> + <textView editable="NO" importsGraphics="NO" verticallyResizable="NO" findStyle="panel" allowsNonContiguousLayout="YES" id="NLm-8l-MrV" customClass="SPTextView"> + <rect key="frame" x="0.0" y="0.0" width="510" height="90"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> + <size key="minSize" width="510" height="90"/> + <size key="maxSize" width="518" height="10000000"/> + <color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> + <connections> + <outlet property="delegate" destination="-2" id="nM8-08-2Rj"/> + </connections> + </textView> + </subviews> + </clipView> + <scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="dpu-5p-anM"> + <rect key="frame" x="1" y="119" width="232" height="15"/> + <autoresizingMask key="autoresizingMask"/> + </scroller> + <scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="vhd-6q-xMy"> + <rect key="frame" x="224" y="1" width="15" height="127"/> + <autoresizingMask key="autoresizingMask"/> + </scroller> + </scrollView> + </subviews> + </customView> + </subviews> + <holdingPriorities> + <real value="250"/> + <real value="250"/> + </holdingPriorities> + </splitView> + <button toolTip="Apply Filter to current Table (⌘F)" verticalHuggingPriority="750" id="97a-9g-t3j"> + <rect key="frame" x="595" y="12" width="143" height="32"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> + <buttonCell key="cell" type="push" title="Filter" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="aso-eo-Uh0"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="system"/> + <string key="keyEquivalent">f</string> + <modifierMask key="keyEquivalentModifierMask" command="YES"/> + </buttonCell> + <connections> + <action selector="filterTable:" target="-2" id="RuC-c5-aMP"/> + </connections> + </button> + <button toolTip="Create a WHERE clause to search for the last typed pattern in all fields" verticalHuggingPriority="750" id="dCH-wr-KbJ"> + <rect key="frame" x="14" y="12" width="216" height="32"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> + <buttonCell key="cell" type="push" title="Search all fields" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Ojj-hI-KbP"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="system"/> + <string key="keyEquivalent">a</string> + <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> + </buttonCell> + <connections> + <action selector="toggleLookAllFieldsMode:" target="-2" id="S7l-Cy-VFb"/> + </connections> + </button> + <button toolTip="Clear all Filter Criteria (⌘⌫)" verticalHuggingPriority="750" id="egJ-46-zsK"> + <rect key="frame" x="452" y="12" width="143" height="32"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> + <buttonCell key="cell" type="push" title="Clear" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="0Ec-f2-4N5"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="system"/> + <string key="keyEquivalent"></string> + <modifierMask key="keyEquivalentModifierMask" command="YES"/> + </buttonCell> + <connections> + <action selector="tableFilterClear:" target="-2" id="f00-8N-Gks"/> + </connections> + </button> + </subviews> + </view> + <connections> + <outlet property="initialFirstResponder" destination="8SC-Fh-D9L" id="zBz-CD-uGL"/> + </connections> + <point key="canvasLocation" x="146" y="493"/> + </window> + <userDefaultsController representsSharedInstance="YES" id="E5e-Af-qLe"/> + <window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="bXg-MQ-c5g" userLabel="Filter Table Set Default Operator Sheet" customClass="NSPanel"> + <windowStyleMask key="styleMask" titled="YES"/> + <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> + <rect key="contentRect" x="235" y="418" width="251" height="102"/> + <rect key="screenRect" x="0.0" y="0.0" width="1920" height="1057"/> + <value key="minSize" type="size" width="251" height="102"/> + <value key="maxSize" type="size" width="251" height="102"/> + <view key="contentView" id="nxt-IK-Ukn"> + <rect key="frame" x="0.0" y="0.0" width="251" height="102"/> + <autoresizingMask key="autoresizingMask"/> + <subviews> + <button verticalHuggingPriority="750" tag="1" id="dzK-Nw-kVd"> + <rect key="frame" x="140" y="13" width="96" height="28"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> + <buttonCell key="cell" type="push" title="Save" bezelStyle="rounded" alignment="center" controlSize="small" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="lvs-yx-6iE"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="smallSystem"/> + <string key="keyEquivalent" base64-UTF8="YES"> +DQ +</string> + </buttonCell> + <connections> + <action selector="closeSheet:" target="-2" id="Szi-4E-jOM"/> + </connections> + </button> + <button verticalHuggingPriority="750" id="eKg-Fu-UA7"> + <rect key="frame" x="46" y="13" width="96" height="28"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES"/> + <buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" controlSize="small" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="FJo-lE-2oi"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="smallSystem"/> + <string key="keyEquivalent" base64-UTF8="YES"> +Gw +</string> + </buttonCell> + <connections> + <action selector="closeSheet:" target="-2" id="HtZ-Ia-92w"/> + </connections> + </button> + <textField verticalHuggingPriority="750" id="iLi-Fh-ovo"> + <rect key="frame" x="17" y="76" width="208" height="14"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Enter Filter Table's Default Operator:" id="Aps-Nn-GvU"> + <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> + <comboBox verticalHuggingPriority="750" id="KZQ-WN-rtU"> + <rect key="frame" x="20" y="51" width="214" height="22"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> + <comboBoxCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" drawsBackground="YES" completes="NO" numberOfVisibleItems="5" id="UeY-Ej-YPK"> + <font key="font" metaFont="smallSystem"/> + <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> + </comboBoxCell> + </comboBox> + <button horizontalHuggingPriority="750" verticalHuggingPriority="750" id="6wf-fR-6SB"> + <rect key="frame" x="17" y="14" width="25" height="25"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> + <buttonCell key="cell" type="help" bezelStyle="helpButton" alignment="center" controlSize="small" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="QYZ-U7-zc5"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="smallSystem"/> + </buttonCell> + <connections> + <action selector="showDefaultOperaterHelp:" target="-2" id="hCi-Lg-QN3"/> + </connections> + </button> + </subviews> + </view> + <point key="canvasLocation" x="75" y="132"/> + </window> + </objects> +</document> diff --git a/Resources/Images/button_filter.png b/Resources/Images/button_filter.png Binary files differnew file mode 100644 index 00000000..2ae6f60d --- /dev/null +++ b/Resources/Images/button_filter.png diff --git a/Resources/Images/button_filter@2x.png b/Resources/Images/button_filter@2x.png Binary files differnew file mode 100644 index 00000000..0e40ee1f --- /dev/null +++ b/Resources/Images/button_filter@2x.png diff --git a/Resources/Images/button_filter_active.png b/Resources/Images/button_filter_active.png Binary files differnew file mode 100644 index 00000000..3a39d358 --- /dev/null +++ b/Resources/Images/button_filter_active.png diff --git a/Resources/Images/button_filter_active@2x.png b/Resources/Images/button_filter_active@2x.png Binary files differnew file mode 100644 index 00000000..67084e78 --- /dev/null +++ b/Resources/Images/button_filter_active@2x.png diff --git a/Source/SPAppController.m b/Source/SPAppController.m index 2e62725f..c9523bc7 100644 --- a/Source/SPAppController.m +++ b/Source/SPAppController.m @@ -2368,7 +2368,7 @@ */ - (BOOL)application:(NSApplication *)sender delegateHandlesKey:(NSString *)key { - NSLog(@"Not yet implemented."); + NSLog(@"Not yet implemented: %@", key); return NO; } diff --git a/Source/SPConnectionController.m b/Source/SPConnectionController.m index 8380f827..b9db12ad 100644 --- a/Source/SPConnectionController.m +++ b/Source/SPConnectionController.m @@ -86,6 +86,8 @@ static NSString *SPConnectionViewNibName = @"ConnectionView"; */ static BOOL FindLinesInFile(NSData *fileData,const void *first,size_t first_len,const void *second,size_t second_len); +static BOOL isOSAtLeast10_7; + @interface SPConnectionController () // Privately redeclare as read/write to get the synthesized setter @@ -178,6 +180,10 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, @synthesize isConnecting; @synthesize isEditingConnection; ++ (void)initialize { + isOSAtLeast10_7 = [SPOSInfo isOSVersionAtLeastMajor:10 minor:7 patch:0]; +} + - (NSString *)keychainPassword { NSString *kcItemName = [self connectionKeychainItemName]; @@ -2981,10 +2987,15 @@ static NSComparisonResult _compareFavoritesUsingKey(id favorite1, id favorite2, // the division may lead to values that are not valid for the current screen size (e.g. non-integer values on a // @1x non-retina screen). The OS works something out when not using layer-backed views, but in the latter // case the result will look like garbage if we don't fix this. - // This code is taken from Apple's "BlurryView" example code. - connectionDetailsFrame = [[connectionDetailsScrollView superview] convertRectToBase:connectionDetailsFrame]; - connectionDetailsFrame.origin.y = round(connectionDetailsFrame.origin.y); - connectionDetailsFrame = [[connectionDetailsScrollView superview] convertRectFromBase:connectionDetailsFrame]; + if(isOSAtLeast10_7) { + connectionDetailsFrame = [connectionDetailsScrollView backingAlignedRect:connectionDetailsFrame options:NSAlignAllEdgesNearest]; + } + else { + // This code is taken from Apple's "BlurryView" example code. + connectionDetailsFrame = [[connectionDetailsScrollView superview] convertRectToBase:connectionDetailsFrame]; + connectionDetailsFrame.origin.y = round(connectionDetailsFrame.origin.y); + connectionDetailsFrame = [[connectionDetailsScrollView superview] convertRectFromBase:connectionDetailsFrame]; + } [connectionResizeContainer setFrame:connectionDetailsFrame]; scrollDocumentFrame.size.height = scrollViewFrame.size.height; [[connectionDetailsScrollView documentView] setFrame:scrollDocumentFrame]; diff --git a/Source/SPContentFilterManager.h b/Source/SPContentFilterManager.h index 3c170bfc..cfc5bc76 100644 --- a/Source/SPContentFilterManager.h +++ b/Source/SPContentFilterManager.h @@ -39,7 +39,7 @@ SPDatabaseDocument *tableDocumentInstance; #ifndef SP_CODA /* ivars */ - NSURL *delegatesFileURL; + NSURL *documentFileURL; #endif IBOutlet id encodingPopUp; @@ -65,7 +65,7 @@ NSString *filterType; } -- (id)initWithDelegate:(id)managerDelegate forFilterType:(NSString *)compareType; +- (id)initWithDatabaseDocument:(SPDatabaseDocument *)document forFilterType:(NSString *)compareType; // Accessors - (NSMutableArray *)contentFilterForFileURL:(NSURL *)fileURL; diff --git a/Source/SPContentFilterManager.m b/Source/SPContentFilterManager.m index d0906c27..6ab50876 100644 --- a/Source/SPContentFilterManager.m +++ b/Source/SPContentFilterManager.m @@ -33,7 +33,6 @@ #import "RegexKitLite.h" #import "SPQueryController.h" #import "SPDatabaseDocument.h" -#import "SPTableContent.h" #import "SPConnectionController.h" #import "SPSplitView.h" #import "SPAppController.h" @@ -48,31 +47,29 @@ static NSString *SPExportFilterAction = @"SPExportFilter"; @implementation SPContentFilterManager /** - * Initialize the manager with the supplied delegate + * Initialize the manager with the supplied document */ -- (id)initWithDelegate:(id)managerDelegate forFilterType:(NSString *)compareType +- (id)initWithDatabaseDocument:(SPDatabaseDocument *)document forFilterType:(NSString *)compareType { - if ((self = [super initWithWindowNibName:@"ContentFilterManager"])) { + if (document == nil) { + NSBeep(); + NSLog(@"ContentFilterManager was called without a document."); + + return nil; + } + if ((self = [super initWithWindowNibName:@"ContentFilterManager"])) { #ifndef SP_CODA prefs = [NSUserDefaults standardUserDefaults]; #endif contentFilters = [[NSMutableArray alloc] init]; - - if (managerDelegate == nil) { - NSBeep(); - NSLog(@"ContentFilterManager was called without a delegate."); - - return nil; - } - - tableDocumentInstance = [managerDelegate valueForKeyPath:@"tableDocumentInstance"]; + tableDocumentInstance = document; #ifndef SP_CODA - delegatesFileURL = [tableDocumentInstance fileURL]; + documentFileURL = [[tableDocumentInstance fileURL] copy]; #endif - filterType = [NSString stringWithString:compareType]; + filterType = [compareType copy]; } return self; @@ -89,10 +86,10 @@ static NSString *SPExportFilterAction = @"SPExportFilter"; // Add global group row to contentFilters [contentFilters addObject:@{ - @"MenuLabel" : NSLocalizedString(@"Global", @"Content Filter Manager : Filter Entry List: 'Global' Header"), - @"headerOfFileURL" : @"", - @"Clause" : @"", - @"ConjunctionLabel" : @"" + @"MenuLabel" : NSLocalizedString(@"Global", @"Content Filter Manager : Filter Entry List: 'Global' Header"), + @"headerOfFileURL" : @"", + @"Clause" : @"", + @"ConjunctionLabel" : @"" }]; #ifndef SP_CODA /* prefs access */ @@ -113,13 +110,13 @@ static NSString *SPExportFilterAction = @"SPExportFilter"; // Build doc-based filters [contentFilters addObject:[NSDictionary dictionaryWithObjectsAndKeys: - [[[delegatesFileURL absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] lastPathComponent], @"MenuLabel", - [delegatesFileURL absoluteString], @"headerOfFileURL", + [[[documentFileURL absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] lastPathComponent], @"MenuLabel", + [documentFileURL absoluteString], @"headerOfFileURL", @"", @"Clause", nil]]; - if ([[SPQueryController sharedQueryController] contentFilterForFileURL:delegatesFileURL]) { - id filters = [[SPQueryController sharedQueryController] contentFilterForFileURL:delegatesFileURL]; + if ([[SPQueryController sharedQueryController] contentFilterForFileURL:documentFileURL]) { + id filters = [[SPQueryController sharedQueryController] contentFilterForFileURL:documentFileURL]; if([filters objectForKey:filterType]) for(id fav in [filters objectForKey:filterType]) [contentFilters addObject:[[fav mutableCopy] autorelease]]; @@ -220,6 +217,7 @@ static NSString *SPExportFilterAction = @"SPExportFilter"; */ - (id)customQueryInstance { +#warning private ivar accessed from outside return [tableDocumentInstance valueForKey:@"customQueryInstance"]; } @@ -395,7 +393,7 @@ static NSString *SPExportFilterAction = @"SPExportFilter"; #ifndef SP_CODA // Update current document's content filters in the SPQueryController [[SPQueryController sharedQueryController] replaceContentFilterByArray: - [self contentFilterForFileURL:delegatesFileURL] ofType:filterType forFileURL:delegatesFileURL]; + [self contentFilterForFileURL:documentFileURL] ofType:filterType forFileURL:documentFileURL]; // Update global preferences' list id cf = [[prefs objectForKey:SPContentFilters] mutableCopy]; @@ -940,16 +938,16 @@ static NSString *SPExportFilterAction = @"SPExportFilter"; NSError *error = nil; NSData *plist = [NSPropertyListSerialization dataWithPropertyList:spfdata - format:NSPropertyListXMLFormat_v1_0 - options:0 - error:&error]; + format:NSPropertyListXMLFormat_v1_0 + options:0 + error:&error]; if(error) { NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Error while converting content filter data", @"Content filters could not be converted to plist upon export - message title (ContentFilterManager)") - defaultButton:NSLocalizedString(@"OK", @"OK button") - alternateButton:nil - otherButton:nil - informativeTextWithFormat:@"%@", [error localizedDescription]]; + defaultButton:NSLocalizedString(@"OK", @"OK button") + alternateButton:nil + otherButton:nil + informativeTextWithFormat:@"%@", [error localizedDescription]]; [alert setAlertStyle:NSCriticalAlertStyle]; [alert runModal]; @@ -968,6 +966,8 @@ static NSString *SPExportFilterAction = @"SPExportFilter"; - (void)dealloc { SPClear(contentFilters); + SPClear(filterType); + SPClear(documentFileURL); [super dealloc]; } diff --git a/Source/SPDataImport.h b/Source/SPDataImport.h index 545e02ff..ca1cd7dc 100644 --- a/Source/SPDataImport.h +++ b/Source/SPDataImport.h @@ -30,6 +30,13 @@ // More info at <https://github.com/sequelpro/sequelpro> @class SPMySQLConnection; +@class SPFieldMapperController; +@class SPFileHandle; +@class SPDatabaseDocument; +@class SPCustomQuery; +@class SPTableData; +@class SPTableStructure; +@class SPTablesList; typedef enum { SPFieldMapperInProgress = 1, @@ -37,16 +44,14 @@ typedef enum { SPFieldMapperCancelled = 3 } SPFieldMapperSheetStatus; -@class SPFieldMapperController, SPFileHandle; - @interface SPDataImport : NSObject <NSOpenSavePanelDelegate> { - IBOutlet id tableDocumentInstance; - IBOutlet id tablesListInstance; - IBOutlet id tableSourceInstance; - IBOutlet id tableContentInstance; - IBOutlet id tableDataInstance; - IBOutlet id customQueryInstance; +#warning Outlets belong to multiple xib files! + IBOutlet SPDatabaseDocument *tableDocumentInstance; + IBOutlet SPTablesList *tablesListInstance; + IBOutlet SPTableStructure *tableSourceInstance; + IBOutlet SPTableData *tableDataInstance; + IBOutlet SPCustomQuery *customQueryInstance; IBOutlet id importView; IBOutlet id importTabView; @@ -55,7 +60,6 @@ typedef enum { IBOutlet id importFieldsEnclosedField; IBOutlet id importFieldsEscapedField; IBOutlet id importLinesTerminatedField; - IBOutlet id importFieldMapperSheetWindow; IBOutlet NSPopUpButton *importFormatPopup; IBOutlet NSPopUpButton *importEncodingPopup; @@ -67,21 +71,13 @@ typedef enum { IBOutlet NSTextView *importFromClipboardTextView; - IBOutlet id addDropTableSwitch; - IBOutlet id addCreateTableSwitch; - IBOutlet id addTableContentSwitch; - IBOutlet id addErrorsSwitch; - IBOutlet id sqlFullStreamingSwitch; - IBOutlet id sqlCompressionSwitch; - IBOutlet id csvFullStreamingSwitch; - IBOutlet id multiCSVFullStreamingSwitch; - IBOutlet id multiXMLFullStreamingSwitch; - IBOutlet id errorsSheet; - IBOutlet id errorsView; - IBOutlet id singleProgressSheet; - IBOutlet id singleProgressBar; - IBOutlet id singleProgressTitle; - IBOutlet id singleProgressText; + IBOutlet NSWindow *errorsSheet; + IBOutlet NSTextView *errorsView; + + IBOutlet NSPanel *singleProgressSheet; + IBOutlet NSProgressIndicator *singleProgressBar; + IBOutlet NSTextField *singleProgressTitle; + IBOutlet NSTextField *singleProgressText; SPMySQLConnection *mySQLConnection; @@ -108,7 +104,6 @@ typedef enum { BOOL importMethodIsUpdate; BOOL importIntoNewTable; - NSUInteger exportMode; NSUserDefaults *prefs; BOOL progressCancelled; BOOL _mainNibLoaded; @@ -119,8 +114,6 @@ typedef enum { NSMutableIndexSet *bitFieldsMapIndex; NSMutableArray *nullableNumericFields; NSMutableIndexSet *nullableNumericFieldsMapIndex; - - NSSavePanel *currentExportPanel; } // IBAction methods diff --git a/Source/SPDataImport.m b/Source/SPDataImport.m index f350ae0f..f6b865e7 100644 --- a/Source/SPDataImport.m +++ b/Source/SPDataImport.m @@ -34,7 +34,6 @@ #import "SPTablesList.h" #import "SPTableStructure.h" #import "SPDatabaseStructure.h" -#import "SPTableContent.h" #import "SPCustomQuery.h" #import "SPGrowlController.h" #import "SPSQLParser.h" @@ -211,9 +210,9 @@ [NSApp beginSheet:importFromClipboardSheet modalForWindow:[tableDocumentInstance parentWindow] - modalDelegate:self + modalDelegate:self didEndSelector:@selector(importFromClipboardSheetDidEnd:returnCode:contextInfo:) - contextInfo:nil]; + contextInfo:nil]; } /** @@ -408,7 +407,11 @@ [singleProgressBar startAnimation:self]; // Open the progress sheet - [NSApp beginSheet:singleProgressSheet modalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:nil contextInfo:nil]; + [NSApp beginSheet:singleProgressSheet + modalForWindow:[tableDocumentInstance parentWindow] + modalDelegate:nil + didEndSelector:NULL + contextInfo:NULL]; [singleProgressSheet makeKeyWindow]; }); @@ -696,12 +699,12 @@ // Re-query the structure of all databases in the background [[tableDocumentInstance databaseStructureRetrieval] queryDbStructureInBackgroundWithUserInfo:@{@"forceUpdate" : @YES}]; - - // Import finished Growl notification - [[SPGrowlController sharedGrowlController] notifyWithTitle:@"Import Finished" - description:[NSString stringWithFormat:NSLocalizedString(@"Finished importing %@",@"description for finished importing growl notification"), [filename lastPathComponent]] - document:tableDocumentInstance - notificationName:@"Import Finished"]; + + // Import finished Growl notification + [[SPGrowlController sharedGrowlController] notifyWithTitle:@"Import Finished" + description:[NSString stringWithFormat:NSLocalizedString(@"Finished importing %@", @"description for finished importing growl notification"), [filename lastPathComponent]] + document:tableDocumentInstance + notificationName:@"Import Finished"]; } #pragma mark - @@ -792,7 +795,11 @@ [singleProgressBar startAnimation:self]; // Open the progress sheet - [NSApp beginSheet:singleProgressSheet modalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:nil contextInfo:nil]; + [NSApp beginSheet:singleProgressSheet + modalForWindow:[tableDocumentInstance parentWindow] + modalDelegate:nil + didEndSelector:NULL + contextInfo:NULL]; [singleProgressSheet makeKeyWindow]; }); @@ -974,7 +981,11 @@ [singleProgressBar setMaxValue:fileTotalLength]; [singleProgressBar setIndeterminate:NO]; [singleProgressBar startAnimation:self]; - [NSApp beginSheet:singleProgressSheet modalForWindow:[tableDocumentInstance parentWindow] modalDelegate:nil didEndSelector:NULL contextInfo:NULL]; + [NSApp beginSheet:singleProgressSheet + modalForWindow:[tableDocumentInstance parentWindow] + modalDelegate:nil + didEndSelector:NULL + contextInfo:NULL]; [singleProgressSheet makeKeyWindow]; }); @@ -1211,10 +1222,10 @@ } // Import finished Growl notification - [[SPGrowlController sharedGrowlController] notifyWithTitle:NSLocalizedString(@"Import Finished" , @"title for finished importing growl notification") - description:[NSString stringWithFormat:NSLocalizedString(@"Finished importing %@",@"description for finished importing growl notification"), [filename lastPathComponent]] - document:tableDocumentInstance - notificationName:@"Import Finished"]; + [[SPGrowlController sharedGrowlController] notifyWithTitle:NSLocalizedString(@"Import Finished", @"title for finished importing growl notification") + description:[NSString stringWithFormat:NSLocalizedString(@"Finished importing %@", @"description for finished importing growl notification"), [filename lastPathComponent]] + document:tableDocumentInstance + notificationName:@"Import Finished"]; SPMainQSync(^{ if(importIntoNewTable) { @@ -1295,10 +1306,10 @@ // Show field mapper sheet and set the focus to it [NSApp beginSheet:[fieldMapperController window] - modalForWindow:[tableDocumentInstance parentWindow] - modalDelegate:self - didEndSelector:@selector(fieldMapperDidEndSheet:returnCode:contextInfo:) - contextInfo:nil]; + modalForWindow:[tableDocumentInstance parentWindow] + modalDelegate:self + didEndSelector:@selector(fieldMapperDidEndSheet:returnCode:contextInfo:) + contextInfo:NULL]; [[fieldMapperController window] makeKeyWindow]; }); @@ -1720,11 +1731,11 @@ cleanup: } [errorsView setString:message]; - [NSApp beginSheet:errorsSheet - modalForWindow:[tableDocumentInstance parentWindow] - modalDelegate:self - didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) - contextInfo:nil]; + [NSApp beginSheet:errorsSheet + modalForWindow:[tableDocumentInstance parentWindow] + modalDelegate:self + didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) + contextInfo:NULL]; [errorsSheet makeKeyWindow]; } diff --git a/Source/SPDatabaseDocument.m b/Source/SPDatabaseDocument.m index ad7a2614..4bdd0aad 100644 --- a/Source/SPDatabaseDocument.m +++ b/Source/SPDatabaseDocument.m @@ -4711,7 +4711,7 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; [sessionState setObject:[NSNumber numberWithInteger:[tableContentInstance pageNumber]] forKey:@"contentPageNumber"]; [sessionState setObject:NSStringFromRect([tableContentInstance viewport]) forKey:@"contentViewport"]; NSDictionary *filterSettings = [tableContentInstance filterSettings]; - if (filterSettings) [sessionState setObject:filterSettings forKey:@"contentFilter"]; + if (filterSettings) [sessionState setObject:filterSettings forKey:@"contentFilterV2"]; NSDictionary *contentSelectedRows = [tableContentInstance selectionDetailsAllowingIndexSelection:YES]; if (contentSelectedRows) { @@ -5110,7 +5110,7 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; if([spfSession objectForKey:@"contentSortCol"]) [tableContentInstance setSortColumnNameToRestore:[spfSession objectForKey:@"contentSortCol"] isAscending:[[spfSession objectForKey:@"contentSortColIsAsc"] boolValue]]; if([spfSession objectForKey:@"contentPageNumber"]) [tableContentInstance setPageToRestore:[[spfSession objectForKey:@"pageNumber"] integerValue]]; if([spfSession objectForKey:@"contentViewport"]) [tableContentInstance setViewportToRestore:NSRectFromString([spfSession objectForKey:@"contentViewport"])]; - if([spfSession objectForKey:@"contentFilter"]) [tableContentInstance setFiltersToRestore:[spfSession objectForKey:@"contentFilter"]]; + if([spfSession objectForKey:@"contentFilterV2"]) [tableContentInstance setFiltersToRestore:[spfSession objectForKey:@"contentFilterV2"]]; // Select table [tablesListInstance selectTableAtIndex:[NSNumber numberWithInteger:[tables indexOfObject:[spfSession objectForKey:@"table"]]]]; @@ -6395,7 +6395,6 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; // Register observers for when the DisplayTableViewVerticalGridlines preference changes [prefs addObserver:self forKeyPath:SPDisplayTableViewVerticalGridlines options:NSKeyValueObservingOptionNew context:NULL]; [prefs addObserver:tableSourceInstance forKeyPath:SPDisplayTableViewVerticalGridlines options:NSKeyValueObservingOptionNew context:NULL]; - [prefs addObserver:tableContentInstance forKeyPath:SPDisplayTableViewVerticalGridlines options:NSKeyValueObservingOptionNew context:NULL]; [prefs addObserver:customQueryInstance forKeyPath:SPDisplayTableViewVerticalGridlines options:NSKeyValueObservingOptionNew context:NULL]; [prefs addObserver:tableRelationsInstance forKeyPath:SPDisplayTableViewVerticalGridlines options:NSKeyValueObservingOptionNew context:NULL]; [prefs addObserver:[SPQueryController sharedQueryController] forKeyPath:SPDisplayTableViewVerticalGridlines options:NSKeyValueObservingOptionNew context:NULL]; @@ -6404,9 +6403,6 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; [prefs addObserver:tableSourceInstance forKeyPath:SPUseMonospacedFonts options:NSKeyValueObservingOptionNew context:NULL]; [prefs addObserver:[SPQueryController sharedQueryController] forKeyPath:SPUseMonospacedFonts options:NSKeyValueObservingOptionNew context:NULL]; - [prefs addObserver:tableContentInstance forKeyPath:SPGlobalResultTableFont options:NSKeyValueObservingOptionNew context:NULL]; - [prefs addObserver:tableContentInstance forKeyPath:SPDisplayBinaryDataAsHex options:NSKeyValueObservingOptionNew context:NULL]; - // Register observers for when the logging preference changes [prefs addObserver:[SPQueryController sharedQueryController] forKeyPath:SPConsoleEnableLogging options:NSKeyValueObservingOptionNew context:NULL]; @@ -6424,13 +6420,9 @@ static int64_t SPDatabaseDocumentInstanceCounter = 0; [prefs removeObserver:tableSourceInstance forKeyPath:SPUseMonospacedFonts]; - [prefs removeObserver:tableContentInstance forKeyPath:SPGlobalResultTableFont]; - [prefs removeObserver:tableContentInstance forKeyPath:SPDisplayBinaryDataAsHex]; - [prefs removeObserver:customQueryInstance forKeyPath:SPDisplayTableViewVerticalGridlines]; [prefs removeObserver:tableRelationsInstance forKeyPath:SPDisplayTableViewVerticalGridlines]; [prefs removeObserver:tableSourceInstance forKeyPath:SPDisplayTableViewVerticalGridlines]; - [prefs removeObserver:tableContentInstance forKeyPath:SPDisplayTableViewVerticalGridlines]; [prefs removeObserver:[SPQueryController sharedQueryController] forKeyPath:SPUseMonospacedFonts]; [prefs removeObserver:[SPQueryController sharedQueryController] forKeyPath:SPConsoleEnableLogging]; diff --git a/Source/SPFillView.h b/Source/SPFillView.h new file mode 100644 index 00000000..21ea074b --- /dev/null +++ b/Source/SPFillView.h @@ -0,0 +1,47 @@ +// +// SPFillView.h +// sequel-pro +// +// Created by Max Lohrmann on 09.05.18. +// Copyright (c) 2018 Max Lohrmann. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <https://github.com/sequelpro/sequelpro> + +/** + * SPFillView is a very simple NSView that will + * fill its whole view rect with a solid color. + * The color can be set in Interface Builder. + */ +@interface SPFillView : NSView +{ + NSColor *currentColor; +} + +/** + * This method is invoked when unarchiving the View from the xib. + * The value is configured in IB under "User Defined Runtime Attributes" + */ +- (void)setSystemColorOfName:(NSString *)name; + +@end diff --git a/Source/SPFillView.m b/Source/SPFillView.m new file mode 100644 index 00000000..b2fae0fb --- /dev/null +++ b/Source/SPFillView.m @@ -0,0 +1,61 @@ +// +// SPFillView.m +// sequel-pro +// +// Created by Max Lohrmann on 09.05.18. +// Copyright (c) 2018 Max Lohrmann. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <https://github.com/sequelpro/sequelpro> + +#import "SPFillView.h" + +@implementation SPFillView + +- (void)setSystemColorOfName:(NSString *)name +{ + //TODO: xibs after 10.6 support storing colors as user defined attributes so we don't need the detour via strings anymore + NSColorList *scl = [NSColorList colorListNamed:@"System"]; + NSColor *color = [scl colorWithKey:name]; + if(color) { + [color retain]; + [currentColor release]; + currentColor = color; + [self setNeedsDisplay:YES]; + } +} + +- (void)drawRect:(NSRect)dirtyRect { + if(currentColor) { + [currentColor set]; + NSRectFill(dirtyRect); + } +} + +- (void)dealloc +{ + [currentColor release]; + [super dealloc]; +} + +@end diff --git a/Source/SPFilterTableController.h b/Source/SPFilterTableController.h new file mode 100644 index 00000000..7bf02593 --- /dev/null +++ b/Source/SPFilterTableController.h @@ -0,0 +1,129 @@ +// +// SPFilterTableController.h +// sequel-pro +// +// Created by Max Lohrmann on 07.05.18. +// Copyright (c) 2018 Max Lohrmann. All rights reserved. +// Relocated from existing files. Previous copyright applies. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <https://github.com/sequelpro/sequelpro> + +@class SPSplitView; +@class SPCopyTable; +@class SPTextView; + +@interface SPFilterTableController : NSWindowController +{ + IBOutlet SPSplitView *filterTableSplitView; + IBOutlet NSButton *filterTableFilterButton; + IBOutlet NSButton *filterTableClearButton; + IBOutlet NSButton *filterTableSearchAllFields; + + IBOutlet SPCopyTable *filterTableView; + + IBOutlet NSButton *filterTableLiveSearchCheckbox; + IBOutlet NSButton *filterTableNegateCheckbox; + IBOutlet NSButton *filterTableDistinctCheckbox; + + IBOutlet NSTextField *filterTableQueryTitle; + IBOutlet SPTextView *filterTableWhereClause; + + IBOutlet NSPanel *filterTableSetDefaultOperatorSheet; + IBOutlet NSComboBox* filterTableSetDefaultOperatorValue; + + NSUserDefaults *prefs; + + NSMutableDictionary *filterTableData; + BOOL filterTableNegate; + BOOL filterTableDistinct; + BOOL filterTableIsSwapped; + NSString *filterTableDefaultOperator; + NSString *lastEditedFilterTableValue; + + id target; + SEL action; +} + +/** + * Puts the filter table window on screen + * + * MUST BE CALLED ON THE UI THREAD! + */ +- (void)showFilterTableWindow; + +/** + * Restores filter table content state from serialized data + * + * MUST BE CALLED ON THE UI THREAD! + */ +- (void)setFilterTableData:(NSData *)arcData; + +/** + * Returns the current contents of the filter table window as serialized data + * + * MUST BE CALLED ON THE UI THREAD! + */ +- (NSData *)filterTableData; + +/** + * The SQL expression to use as filter. + * Can be nil if no filter is set! + * + * MUST BE CALLED ON THE UI THREAD! + */ +- (NSString *)tableFilterString; + +/** + * Will reconfigure the columns of the filter table view from the given array. + * Call with nil to reset the table view to its initial empty state. + * + * MUST BE CALLED ON THE UI THREAD! + */ +- (void)setColumns:(NSArray *)dataColumns; + +/** + * Will return YES if the SQL expression returned by -tableFilterString should be + * used in a "SELECT DISTINCT …" query. + * + * Results may be inconsistent if not called on the main thread! + */ +- (BOOL)isDistinct; + +/** + * Use this method to make the filter window indicate an error state after executing the filter. + * Pass 0 for error ID to indicate an OK state. + * + * MUST BE CALLED ON THE UI THREAD! + */ +- (void)setFilterError:(NSUInteger)errorID message:(NSString *)message sqlstate:(NSString *)sqlstate; + +/** + * Used when the filter table window wants to trigger filtering + * + * Results may be inconsistent if not called on the main thread! + */ +@property (assign, nonatomic) id target; +@property (assign, nonatomic) SEL action; + +@end diff --git a/Source/SPFilterTableController.m b/Source/SPFilterTableController.m new file mode 100644 index 00000000..60e67a04 --- /dev/null +++ b/Source/SPFilterTableController.m @@ -0,0 +1,706 @@ +// +// SPFilterTableController.m +// sequel-pro +// +// Created by Max Lohrmann on 07.05.18. +// Copyright (c) 2018 Max Lohrmann. All rights reserved. +// Relocated from existing files. Previous copyright applies. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <https://github.com/sequelpro/sequelpro> + +#import "SPFilterTableController.h" +#import "SPSplitView.h" +#import "SPCopyTable.h" +#import "SPTextView.h" +#import "RegexKitLite.h" +#import "SPTextAndLinkCell.h" + +static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOperator"; +static void *FilterTableKVOContext = &FilterTableKVOContext; + +@interface SPFilterTableController () <NSTableViewDataSource, NSTableViewDelegate, NSControlTextEditingDelegate> + +- (IBAction)filterTable:(id)sender; +- (IBAction)toggleLookAllFieldsMode:(id)sender; +- (IBAction)tableFilterClear:(id)sender; + +- (IBAction)toggleNegateClause:(id)sender; +- (IBAction)toggleDistinctSelect:(id)sender; +- (IBAction)setDefaultOperator:(id)sender; + +- (IBAction)closeSheet:(id)sender; +- (IBAction)showDefaultOperaterHelp:(id)sender; + +- (void)updateFilterTableClause:(id)currentValue; + ++ (NSString*)escapeFilterTableDefaultOperator:(NSString*)op; + +@end + +@implementation SPFilterTableController + +#pragma mark Public methods + +@synthesize target; +@synthesize action; + +- (instancetype)init { + if ((self = [super initWithWindowNibName:@"FilterTableWindow"])) { + target = nil; + action = NULL; + + prefs = [NSUserDefaults standardUserDefaults]; + [prefs addObserver:self + forKeyPath:SPDisplayTableViewVerticalGridlines + options:NSKeyValueObservingOptionNew + context:FilterTableKVOContext]; + + filterTableData = [[NSMutableDictionary alloc] initWithCapacity:1]; + + filterTableNegate = NO; + filterTableDistinct = NO; + filterTableIsSwapped = NO; + + lastEditedFilterTableValue = nil; + } + return self; +} + +- (void)dealloc +{ + //TODO this should be changed to the variant with …context: after 10.6 support is removed! + [prefs removeObserver:self forKeyPath:SPDisplayTableViewVerticalGridlines]; + + SPClear(filterTableData); + SPClear(lastEditedFilterTableValue); + SPClear(filterTableDefaultOperator); + [super dealloc]; +} + +- (void)showFilterTableWindow +{ + [[self window] makeKeyAndOrderFront:nil]; + [filterTableWhereClause setContinuousSpellCheckingEnabled:NO]; + [filterTableWhereClause setAutoindent:NO]; + [filterTableWhereClause setAutoindentIgnoresEnter:NO]; + [filterTableWhereClause setAutopair:[prefs boolForKey:SPCustomQueryAutoPairCharacters]]; + [filterTableWhereClause setAutohelp:NO]; + [filterTableWhereClause setAutouppercaseKeywords:[prefs boolForKey:SPCustomQueryAutoUppercaseKeywords]]; + [filterTableWhereClause setCompletionWasReinvokedAutomatically:NO]; + [filterTableWhereClause insertText:@""]; + [filterTableWhereClause didChangeText]; + + [[filterTableView window] makeFirstResponder:filterTableView]; +} + +- (void)setFilterTableData:(NSData*)arcData +{ + if(!arcData) return; + NSDictionary *filterData = [NSUnarchiver unarchiveObjectWithData:arcData]; + [filterTableData removeAllObjects]; + [filterTableData addEntriesFromDictionary:filterData]; + [[self window] makeKeyAndOrderFront:nil]; + [filterTableView reloadData]; +} + +- (NSData*) filterTableData +{ + if(![[self window] isVisible]) return nil; + + [filterTableView deselectAll:nil]; + + return [NSArchiver archivedDataWithRootObject:filterTableData]; +} + +- (NSString *)tableFilterString +{ + if([[[filterTableWhereClause textStorage] string] length]) { + if ([filterTableNegateCheckbox state] == NSOnState) { + return [NSString stringWithFormat:@"NOT (%@)", [[filterTableWhereClause textStorage] string]]; + } + else { + return [[filterTableWhereClause textStorage] string]; + } + } + else { + return nil; + } +} + +- (void)setColumns:(NSArray *)dataColumns +{ + [self window]; // make sure window is loaded + // Clear filter table + [filterTableView abortEditing]; + while ([[filterTableView tableColumns] count]) { + [NSArrayObjectAtIndex([filterTableView tableColumns], 0) setHeaderToolTip:nil]; // prevent crash #2414 + [filterTableView removeTableColumn:NSArrayObjectAtIndex([filterTableView tableColumns], 0)]; + } + // Clear filter table data + [filterTableData removeAllObjects]; + [filterTableWhereClause setString:@""]; + + // Clear error state + [self setFilterError:0 message:nil sqlstate:nil]; + + if(dataColumns) { + CGFloat totalWidth = 0; + // Add the new columns to the filterTable + for (NSDictionary *columnDefinition in dataColumns ) { + // Set up column for filterTable + NSTableColumn *filterCol = [[NSTableColumn alloc] initWithIdentifier:[columnDefinition objectForKey:@"datacolumnindex"]]; + [[filterCol headerCell] setStringValue:[columnDefinition objectForKey:@"name"]]; + [filterCol setEditable:YES]; + SPTextAndLinkCell *filterDataCell = [[[SPTextAndLinkCell alloc] initTextCell:@""] autorelease]; + [filterDataCell setEditable:YES]; + [filterDataCell setLineBreakMode:NSLineBreakByTruncatingTail]; // add ellipsis for long values (default is to simply hide words) + [filterCol setDataCell:filterDataCell]; + NSSize headerSize = [[[filterCol headerCell] attributedStringValue] size]; + CGFloat headerInitialWidth = headerSize.width + 5.0; + [filterCol setWidth:headerInitialWidth]; + totalWidth += headerInitialWidth; + [filterTableView addTableColumn:filterCol]; + [filterCol release]; + + [filterTableData setObject:[NSMutableDictionary dictionaryWithObjectsAndKeys: + [columnDefinition objectForKey:@"name"], @"name", + [columnDefinition objectForKey:@"typegrouping"], @"typegrouping", + [NSMutableArray arrayWithObjects:@"", @"", @"", @"", @"", @"", @"", @"", @"", @"", nil], SPTableContentFilterKey, + nil] forKey:[columnDefinition objectForKey:@"datacolumnindex"]]; + } + + // if the width of all columns is still less than the width of the table view resize them uniformly once to take up all horizontal space + if(totalWidth < [[filterTableView enclosingScrollView] contentSize].width) [filterTableView sizeToFit]; + } + + [filterTableView reloadData]; +} + +- (BOOL)isDistinct +{ + return filterTableDistinct; +} + +- (void)setFilterError:(NSUInteger)errorID message:(NSString *)message sqlstate:(NSString *)sqlstate +{ + if(errorID) { + [[self window] setTitle:[NSString stringWithFormat:@"%@ – %@", NSLocalizedString(@"Filter", @"filter label"), NSLocalizedString(@"WHERE clause not valid", @"WHERE clause not valid")]]; + } + else { + [[self window] setTitle:NSLocalizedString(@"Filter", @"filter label")]; + } +} + +#pragma mark - Internal methods + +- (void)windowDidLoad +{ + [filterTableView setGridStyleMask:([prefs boolForKey:SPDisplayTableViewVerticalGridlines]) ? NSTableViewSolidVerticalGridLineMask : NSTableViewGridNone]; + + // Modify the filter table split view sizes + [filterTableSplitView setMinSize:135 ofSubviewAtIndex:1]; + + // Init Filter Table GUI + [filterTableDistinctCheckbox setState:(filterTableDistinct) ? NSOnState : NSOffState]; + [filterTableNegateCheckbox setState:(filterTableNegate) ? NSOnState : NSOffState]; + [filterTableLiveSearchCheckbox setState:NSOffState]; + + filterTableDefaultOperator = [[[self class] escapeFilterTableDefaultOperator:[prefs objectForKey:SPFilterTableDefaultOperator]] retain]; +} + +- (IBAction)filterTable:(id)sender +{ + if(target && action) [target performSelector:action withObject:self]; +} + +/** + * Generate WHERE clause to look for last typed pattern in all fields + */ +- (IBAction)toggleLookAllFieldsMode:(id)sender +{ + [self updateFilterTableClause:sender]; + + // If live search is set perform filtering + if ([filterTableLiveSearchCheckbox state] == NSOnState) { + [self filterTable:filterTableFilterButton]; + } +} + +/** + * Clear the filter table + */ +- (IBAction)tableFilterClear:(id)sender +{ + [filterTableView abortEditing]; + + if(filterTableData && [filterTableData count]) { + + // Clear filter data + for(NSNumber *col in [filterTableData allKeys]) + { + [[filterTableData objectForKey:col] setObject:[NSMutableArray arrayWithObjects:@"", @"", @"", @"", @"", @"", @"", @"", @"", @"", nil] forKey:SPTableContentFilterKey]; + } + + [filterTableView reloadData]; + [filterTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO]; + [filterTableWhereClause setString:@""]; + + // Reload table + [self filterTable:nil]; + } +} + +/** + * Set filter table's Negate + */ +- (IBAction)toggleNegateClause:(id)sender +{ + filterTableNegate = !filterTableNegate; + + if (filterTableNegate) { + [filterTableQueryTitle setStringValue:NSLocalizedString(@"WHERE NOT query", @"Title of filter preview area when the query WHERE is negated")]; + } + else { + [filterTableQueryTitle setStringValue:NSLocalizedString(@"WHERE query", @"Title of filter preview area when the query WHERE is normal")]; + } + + // If live search is set perform filtering + if ([filterTableLiveSearchCheckbox state] == NSOnState) { + [self filterTable:filterTableFilterButton]; + } +} + +/** + * Set filter table's Distinct + */ +- (IBAction)toggleDistinctSelect:(id)sender +{ + filterTableDistinct = !filterTableDistinct; + + [filterTableDistinctCheckbox setState:(filterTableDistinct) ? NSOnState : NSOffState]; + + // If live search is set perform filtering + if ([filterTableLiveSearchCheckbox state] == NSOnState) { + [self filterTable:filterTableFilterButton]; + } +} + +/** + * Set filter table's default operator + */ +- (IBAction)setDefaultOperator:(id)sender +{ + [[self window] makeFirstResponder:filterTableView]; + + // Load history + if([prefs objectForKey:SPFilterTableDefaultOperatorLastItems]) { + NSMutableArray *lastItems = [NSMutableArray array]; + + [lastItems addObject:@"LIKE '%@%'"]; + + for(NSString* item in [prefs objectForKey:SPFilterTableDefaultOperatorLastItems]) + { + [lastItems addObject:item]; + } + + [filterTableSetDefaultOperatorValue removeAllItems]; + [filterTableSetDefaultOperatorValue addItemsWithObjectValues:lastItems]; + } + + [filterTableSetDefaultOperatorValue setStringValue:[prefs objectForKey:SPFilterTableDefaultOperator]]; + + [NSApp beginSheet:filterTableSetDefaultOperatorSheet + modalForWindow:[self window] + modalDelegate:self + didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) + contextInfo:SPTableFilterSetDefaultOperator]; +} + +/** + * Close an open sheet. + */ +- (void)sheetDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo +{ + [sheet orderOut:self]; + + if([contextInfo isEqualToString:SPTableFilterSetDefaultOperator]) { + if(returnCode) { + if(filterTableDefaultOperator) [filterTableDefaultOperator release]; + NSString *newOperator = [filterTableSetDefaultOperatorValue stringValue]; + filterTableDefaultOperator = [[[self class] escapeFilterTableDefaultOperator:newOperator] retain]; + [prefs setObject:newOperator forKey:SPFilterTableDefaultOperator]; + + if(![newOperator isMatchedByRegex:@"(?i)like\\s+['\"]%@%['\"]\\s*"]) { + if(![prefs objectForKey:SPFilterTableDefaultOperatorLastItems]) + [prefs setObject:[NSMutableArray array] forKey:SPFilterTableDefaultOperatorLastItems]; + + NSMutableArray *lastItems = [NSMutableArray array]; + [lastItems setArray:[prefs objectForKey:SPFilterTableDefaultOperatorLastItems]]; + + if([lastItems containsObject:newOperator]) + [lastItems removeObject:newOperator]; + if([lastItems count] > 0) + [lastItems insertObject:newOperator atIndex:0]; + else + [lastItems addObject:newOperator]; + // Remember only the last 15 items + if([lastItems count] > 15) + while([lastItems count] > 15) + [filterTableSetDefaultOperatorValue removeItemAtIndex:[lastItems count]-1]; + + [prefs setObject:lastItems forKey:SPFilterTableDefaultOperatorLastItems]; + } + [self updateFilterTableClause:nil]; + } + } +} + +/** + * Closes the current sheet and stops the modal session + */ +- (IBAction)closeSheet:(id)sender +{ + [NSApp endSheet:[sender window] returnCode:[sender tag]]; + [[sender window] orderOut:self]; +} + +/** + * Opens the content filter help page in the default browser. + */ +- (IBAction)showDefaultOperaterHelp:(id)sender +{ + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:SPLOCALIZEDURL_CONTENTFILTERHELP]]; +} + +/** + * Update WHERE clause in filter table window. + * + * @param currentValue If currentValue == nil take the data from filterTableData, if currentValue == filterTableSearchAllFields + * generate a WHERE clause to search in all given fields, if currentValue == a string take this string as table cell data of the + * currently edited table cell + */ +- (void)updateFilterTableClause:(id)currentValue +{ + NSMutableString *clause = [NSMutableString string]; + NSInteger numberOfRows = [self numberOfRowsInTableView:filterTableView]; + NSInteger numberOfCols = [[filterTableView tableColumns] count]; + NSRange opRange, defopRange; + + BOOL lookInAllFields = NO; + + NSString *re1 = @"^\\s*(<[=>]?|>=?|!?=|≠|≤|≥)\\s*(.*?)\\s*$"; + NSString *re2 = @"^\\s*(.*)\\s+(.*?)\\s*$"; + + NSInteger editedRow = [filterTableView editedRow]; + + if (currentValue == filterTableSearchAllFields) { + numberOfRows = 1; + lookInAllFields = YES; + } + + [filterTableWhereClause setString:@""]; + + for (NSInteger i = 0; i < numberOfRows; i++) + { + NSInteger numberOfValues = 0; + + for (NSInteger anIndex = 0; anIndex < numberOfCols; anIndex++) + { + NSString *filterCell = nil; + NSDictionary *filterCellData = [NSDictionary dictionaryWithDictionary:[filterTableData objectForKey:[NSString stringWithFormat:@"%ld", (long)anIndex]]]; + + // Take filterTableData + if (!currentValue) { + filterCell = NSArrayObjectAtIndex([filterCellData objectForKey:SPTableContentFilterKey], i); + } + // Take last edited value to create the OR clause + else if (lookInAllFields) { + if (lastEditedFilterTableValue && [lastEditedFilterTableValue length]) { + filterCell = lastEditedFilterTableValue; + } + else { + [filterTableWhereClause setString:@""]; + [filterTableWhereClause insertText:@""]; + [filterTableWhereClause scrollRangeToVisible:NSMakeRange(0, 0)]; + + // If live search is set perform filtering + if ([filterTableLiveSearchCheckbox state] == NSOnState) { + [self filterTable:filterTableFilterButton]; + } + } + } + // Take value from currently edited table cell + else if ([currentValue isKindOfClass:[NSString class]]) { + if (i == editedRow && anIndex == [[NSArrayObjectAtIndex([filterTableView tableColumns], [filterTableView editedColumn]) identifier] integerValue]) { + filterCell = (NSString*)currentValue; + } + else { + filterCell = NSArrayObjectAtIndex([filterCellData objectForKey:SPTableContentFilterKey], i); + } + } + + if ([filterCell length]) { + + // Recode special operators + filterCell = [filterCell stringByReplacingOccurrencesOfRegex:@"^\\s*≠" withString:@"!="]; + filterCell = [filterCell stringByReplacingOccurrencesOfRegex:@"^\\s*≤" withString:@"<="]; + filterCell = [filterCell stringByReplacingOccurrencesOfRegex:@"^\\s*≥" withString:@">="]; + + if (numberOfValues) { + [clause appendString:(lookInAllFields) ? @" OR " : @" AND "]; + } + + NSString *fieldName = [[filterCellData objectForKey:@"name"] backtickQuotedString]; + NSString *filterTableDefaultOperatorWithFieldName = [filterTableDefaultOperator stringByReplacingOccurrencesOfString:@"`@`" withString:fieldName]; + + opRange = [filterCell rangeOfString:@"`@`"]; + defopRange = [filterTableDefaultOperator rangeOfString:@"`@`"]; + + // if cell data begins with ' or " treat it as it is + // by checking if default operator by itself contains a ' or " - if so + // remove first and if given the last ' or " + if ([filterCell isMatchedByRegex:@"^\\s*['\"]"]) { + if ([filterTableDefaultOperator isMatchedByRegex:@"['\"]"]) { + NSArray *matches = [filterCell arrayOfCaptureComponentsMatchedByRegex:@"^\\s*(['\"])(.*)\\1\\s*$"]; + + if ([matches count] && [matches = NSArrayObjectAtIndex(matches, 0) count] == 3) { + [clause appendFormat:[NSString stringWithFormat:@"%%@ %@", filterTableDefaultOperatorWithFieldName], fieldName, NSArrayObjectAtIndex(matches, 2)]; + } + else { + matches = [filterCell arrayOfCaptureComponentsMatchedByRegex:@"^\\s*(['\"])(.*)\\s*$"]; + + if ([matches count] && [matches = NSArrayObjectAtIndex(matches, 0) count] == 3) { + [clause appendFormat:[NSString stringWithFormat:@"%%@ %@", filterTableDefaultOperatorWithFieldName], fieldName, NSArrayObjectAtIndex(matches, 2)]; + } + } + } + else { + [clause appendFormat:[NSString stringWithFormat:@"%%@ %@", filterTableDefaultOperatorWithFieldName], fieldName, filterCell]; + } + } + // If cell contains the field name placeholder + else if (opRange.length || defopRange.length) { + filterCell = [filterCell stringByReplacingOccurrencesOfString:@"`@`" withString:fieldName]; + + if (defopRange.length) { + [clause appendFormat:filterTableDefaultOperatorWithFieldName, [filterCell stringByReplacingOccurrencesOfString:@"`@`" withString:fieldName]]; + } + else { + [clause appendString:[filterCell stringByReplacingOccurrencesOfString:@"`@`" withString:fieldName]]; + } + } + // If cell is equal to NULL + else if ([filterCell isMatchedByRegex:@"(?i)^\\s*null\\s*$"]) { + [clause appendFormat:@"%@ IS NULL", fieldName]; + } + // If cell starts with an operator + else if ([filterCell isMatchedByRegex:re1]) { + NSArray *matches = [filterCell arrayOfCaptureComponentsMatchedByRegex:re1]; + + if ([matches count] && [matches = NSArrayObjectAtIndex(matches, 0) count] == 3) { + [clause appendFormat:@"%@ %@ %@", fieldName, NSArrayObjectAtIndex(matches, 1), NSArrayObjectAtIndex(matches, 2)]; + } + } + // If cell consists of at least two words treat the first as operator and the rest as argument + else if ([filterCell isMatchedByRegex:re2]) { + NSArray *matches = [filterCell arrayOfCaptureComponentsMatchedByRegex:re2]; + + if ([matches count] && [matches = NSArrayObjectAtIndex(matches,0) count] == 3) { + [clause appendFormat:@"%@ %@ %@", fieldName, [NSArrayObjectAtIndex(matches, 1) uppercaseString], NSArrayObjectAtIndex(matches, 2)]; + } + } + // Apply the default operator + else { + [clause appendFormat:[NSString stringWithFormat:@"%%@ %@", filterTableDefaultOperatorWithFieldName], fieldName, filterCell]; + } + + numberOfValues++; + } + } + + if (numberOfValues) { + [clause appendString:@"\nOR\n"]; + } + } + + // Remove last " OR " if any + [filterTableWhereClause setString:[clause length] > 3 ? [clause substringToIndex:([clause length] - 4)] : @""]; + + // Update syntax highlighting and uppercasing + [filterTableWhereClause insertText:@""]; + [filterTableWhereClause scrollRangeToVisible:NSMakeRange(0, 0)]; + + // If live search is set perform filtering + if ([filterTableLiveSearchCheckbox state] == NSOnState) { + [self filterTable:filterTableFilterButton]; + } +} + +#pragma mark - TableView datasource methods + +- (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ + if (filterTableIsSwapped) { + [[[filterTableData objectForKey:@(rowIndex)] objectForKey:SPTableContentFilterKey] replaceObjectAtIndex:([[tableColumn identifier] integerValue] - 1) withObject:(NSString *)object]; + } + else { + [[[filterTableData objectForKey:[tableColumn identifier]] objectForKey:SPTableContentFilterKey] replaceObjectAtIndex:rowIndex withObject:(NSString *)object]; + } + + [self updateFilterTableClause:nil]; +} + +- (NSInteger)numberOfRowsInTableView:(SPCopyTable *)tableView +{ + return filterTableIsSwapped ? [filterTableData count] : [[[filterTableData objectForKey:@"0"] objectForKey:SPTableContentFilterKey] count]; +} + +- (id)tableView:(SPCopyTable *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ + NSUInteger columnIndex = [[tableColumn identifier] integerValue]; + + if (filterTableIsSwapped) { + // First column shows the field names + if (columnIndex == 0) { + return [[[NSTableHeaderCell alloc] initTextCell:[[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:@"name"]] autorelease]; + } + + return NSArrayObjectAtIndex([[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:SPTableContentFilterKey], columnIndex - 1); + } + + return NSArrayObjectAtIndex([[filterTableData objectForKey:[tableColumn identifier]] objectForKey:SPTableContentFilterKey], rowIndex); +} + +#pragma mark - TableView delegate methods + +- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)rowIndex +{ + return YES; +} + +- (BOOL)tableView:(NSTableView *)tableView shouldEditTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ + //if ([tableDocumentInstance isWorking]) return NO; + + return (filterTableIsSwapped && [[tableColumn identifier] integerValue] == 0) ? NO : YES; +} + +- (void)tableView:(SPCopyTable *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ + if (filterTableIsSwapped && [[tableColumn identifier] integerValue] == 0) { + [cell setDrawsBackground:YES]; + [cell setBackgroundColor:[NSColor lightGrayColor]]; + } else { + [cell setDrawsBackground:NO]; + } +} + +- (NSString *)tableView:(NSTableView *)tableView toolTipForCell:(id)aCell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row mouseLocation:(NSPoint)mouseLocation +{ + return nil; +} + +#pragma mark - Control delegate methods + +- (void)controlTextDidChange:(NSNotification *)notification +{ + if ([notification object] == filterTableView) { + + NSString *string = [[[[notification userInfo] objectForKey:@"NSFieldEditor"] textStorage] string]; + + if (string && [string length]) { + if (lastEditedFilterTableValue) [lastEditedFilterTableValue release]; + + lastEditedFilterTableValue = [[NSString stringWithString:string] retain]; + } + + [self updateFilterTableClause:string]; + } +} + +- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)editor +{ + return YES; +} + +- (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)aFieldEditor +{ + return YES; +} + +- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command +{ + // Check firstly if SPCopyTable can handle command + if ([control control:control textView:textView doCommandBySelector:command]) return YES; + + // Trap the escape key + if ([[control window] methodForSelector:command] == [[control window] methodForSelector:@selector(cancelOperation:)]) { + // Abort editing + [control abortEditing]; + + return YES; + } + + return NO; +} + +#pragma mark - KVO + +/** + * This method is called as part of Key Value Observing which is used to watch for prefernce changes which effect the interface. + */ +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + // a parent class (or cocoa) can also use KVO, so we need to watch out to only catch those KVO messages we requested + if(context == FilterTableKVOContext) { + // Display table veiew vertical gridlines preference changed + if ([keyPath isEqualToString:SPDisplayTableViewVerticalGridlines]) { + [filterTableView setGridStyleMask:([[change objectForKey:NSKeyValueChangeNewKey] boolValue]) ? NSTableViewSolidVerticalGridLineMask : NSTableViewGridNone]; + } + } + else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } +} + +#pragma mark - + +/** + * Escape passed operator for usage as filterTableDefaultOperator. + */ ++ (NSString*)escapeFilterTableDefaultOperator:(NSString *)op +{ + if (!op) return @""; + + NSMutableString *newOp = [[[NSMutableString alloc] initWithCapacity:[op length]] autorelease]; + + [newOp setString:op]; + [newOp replaceOccurrencesOfRegex:@"%" withString:@"%%"]; + [newOp replaceOccurrencesOfRegex:@"(?<!`)@(?!=`)" withString:@"%@"]; + + return newOp; +} + +@end + diff --git a/Source/SPHistoryController.m b/Source/SPHistoryController.m index aab54d2b..7f1d3ffd 100644 --- a/Source/SPHistoryController.m +++ b/Source/SPHistoryController.m @@ -58,6 +58,7 @@ - (void) awakeFromNib { +#warning private ivar accessed from outside tableContentInstance = [theDocument valueForKey:@"tableContentInstance"]; tablesListInstance = [theDocument valueForKey:@"tablesListInstance"]; toolbarItemVisible = NO; @@ -250,7 +251,8 @@ NSDictionary *contentSelectedRows = [[tableContentInstance onMainThread] selectionDetailsAllowingIndexSelection:YES]; NSRect contentViewport = [[tableContentInstance onMainThread] viewport]; NSDictionary *contentFilter = [[tableContentInstance onMainThread] filterSettings]; - NSData *filterTableData = [tableContentInstance filterTableData]; + NSData *filterTableData = [[tableContentInstance onMainThread] filterTableData]; + SPTableContentFilterSource activeFilter = [[tableContentInstance onMainThread] activeFilter]; if (!theDatabase) return; // If a table is selected, save state information @@ -261,10 +263,11 @@ [NSNumber numberWithUnsignedInteger:contentPageNumber], @"page", [NSValue valueWithRect:contentViewport], @"viewport", [NSNumber numberWithBool:contentSortColIsAsc], @"sortIsAsc", + @(activeFilter), @"activeFilter", nil]; if (contentSortCol) [contentState setObject:contentSortCol forKey:@"sortCol"]; if (contentSelectedRows) [contentState setObject:contentSelectedRows forKey:@"selection"]; - if (contentFilter) [contentState setObject:contentFilter forKey:@"filter"]; + if (contentFilter) [contentState setObject:contentFilter forKey:@"filterV2"]; if (filterTableData) [contentState setObject:filterTableData forKey:@"filterTable"]; // Update the table content states with this information - used when switching tables to restore last used view. @@ -278,38 +281,45 @@ } else if (historyPosition != NSNotFound && historyPosition == [history count] - 1) { NSMutableDictionary *currentHistoryEntry = [history objectAtIndex:historyPosition]; + BOOL databaseIsTheSame = [[currentHistoryEntry objectForKey:@"database"] isEqualToString:theDatabase]; + BOOL tableIsTheSame = [[currentHistoryEntry objectForKey:@"table"] isEqualToString:theTable]; + BOOL viewIsTheSame = ([[currentHistoryEntry objectForKey:@"view"] unsignedIntegerValue] == theView); // If the table is the same, and the filter settings haven't changed, delete the // last entry so it can be replaced. This updates navigation within a table, rather than // creating a new entry every time detail is changed. - if ([[currentHistoryEntry objectForKey:@"database"] isEqualToString:theDatabase] - && [[currentHistoryEntry objectForKey:@"table"] isEqualToString:theTable] - && ([[currentHistoryEntry objectForKey:@"view"] unsignedIntegerValue] != theView - || ((![currentHistoryEntry objectForKey:@"contentFilter"] && !contentFilter) - || (![currentHistoryEntry objectForKey:@"contentFilter"] - && ![(NSString *)[contentFilter objectForKey:@"filterValue"] length] - && ![[contentFilter objectForKey:@"filterComparison"] isEqualToString:@"IS NULL"] - && ![[contentFilter objectForKey:@"filterComparison"] isEqualToString:@"IS NOT NULL"]) - || [[currentHistoryEntry objectForKey:@"contentFilter"] isEqualToDictionary:contentFilter]))) - { + if ( + databaseIsTheSame && + tableIsTheSame && + ( + !viewIsTheSame || + ( + (![currentHistoryEntry objectForKey:@"contentFilterV2"] && !contentFilter) || + [[currentHistoryEntry objectForKey:@"contentFilterV2"] isEqualToDictionary:contentFilter] + ) + ) + ) { [history removeLastObject]; - + } // If the only db/table/view are the same, but the filter settings have changed, also store the // position details on the *previous* history item - } else if ([[currentHistoryEntry objectForKey:@"database"] isEqualToString:theDatabase] - && [[currentHistoryEntry objectForKey:@"table"] isEqualToString:theTable] - && ([[currentHistoryEntry objectForKey:@"view"] unsignedIntegerValue] == theView - || ((![currentHistoryEntry objectForKey:@"contentFilter"] && contentFilter) - || ![[currentHistoryEntry objectForKey:@"contentFilter"] isEqualToDictionary:contentFilter]))) - { + else if ( + databaseIsTheSame && + tableIsTheSame && + ( + viewIsTheSame || + ( + (![currentHistoryEntry objectForKey:@"contentFilterV2"] && contentFilter) || + ![[currentHistoryEntry objectForKey:@"contentFilterV2"] isEqualToDictionary:contentFilter] + ) + ) + ) { [currentHistoryEntry setObject:[NSValue valueWithRect:contentViewport] forKey:@"contentViewport"]; if (contentSelectedRows) [currentHistoryEntry setObject:contentSelectedRows forKey:@"contentSelection"]; - + } // Special case: if the last history item is currently active, and has no table, // but the new selection does - delete the last entry, in order to replace it. // This improves history flow. - } else if ([[currentHistoryEntry objectForKey:@"database"] isEqualToString:theDatabase] - && ![currentHistoryEntry objectForKey:@"table"]) - { + else if (databaseIsTheSame && ![currentHistoryEntry objectForKey:@"table"]) { [history removeLastObject]; } } @@ -322,10 +332,11 @@ [NSNumber numberWithBool:contentSortColIsAsc], @"contentSortColIsAsc", [NSNumber numberWithInteger:contentPageNumber], @"contentPageNumber", [NSValue valueWithRect:contentViewport], @"contentViewport", + @(activeFilter), @"activeFilter", nil]; if (contentSortCol) [newEntry setObject:contentSortCol forKey:@"contentSortCol"]; if (contentSelectedRows) [newEntry setObject:contentSelectedRows forKey:@"contentSelection"]; - if (contentFilter) [newEntry setObject:contentFilter forKey:@"contentFilter"]; + if (contentFilter) [newEntry setObject:contentFilter forKey:@"contentFilterV2"]; [history addObject:newEntry]; @@ -379,7 +390,8 @@ [tableContentInstance setPageToRestore:[[historyEntry objectForKey:@"contentPageNumber"] integerValue]]; [tableContentInstance setSelectionToRestore:[historyEntry objectForKey:@"contentSelection"]]; [tableContentInstance setViewportToRestore:[[historyEntry objectForKey:@"contentViewport"] rectValue]]; - [tableContentInstance setFiltersToRestore:[historyEntry objectForKey:@"contentFilter"]]; + [tableContentInstance setFiltersToRestore:[historyEntry objectForKey:@"contentFilterV2"]]; + [tableContentInstance setActiveFilterToRestore:(SPTableContentFilterSource)[[historyEntry objectForKey:@"activeFilter"] integerValue]]; // If the database, table, and view are the same and content - just trigger a table reload (filters) if ( @@ -495,7 +507,8 @@ abort_entry_load: [tableContentInstance setPageToRestore:[[contentState objectForKey:@"page"] unsignedIntegerValue]]; [tableContentInstance setSelectionToRestore:[contentState objectForKey:@"selection"]]; [tableContentInstance setViewportToRestore:[[contentState objectForKey:@"viewport"] rectValue]]; - [tableContentInstance setFiltersToRestore:[contentState objectForKey:@"filter"]]; + [tableContentInstance setFiltersToRestore:[contentState objectForKey:@"filterV2"]]; + [tableContentInstance setActiveFilterToRestore:(SPTableContentFilterSource)[[contentState objectForKey:@"activeFilter"] integerValue]]; } #pragma mark - @@ -529,21 +542,14 @@ abort_entry_load: [theName appendFormat:@"/%@", [theEntry objectForKey:@"table"]]; - if ([theEntry objectForKey:@"contentFilter"]) { - NSDictionary *filterSettings = [theEntry objectForKey:@"contentFilter"]; - if ([filterSettings objectForKey:@"filterField"]) { - if([filterSettings objectForKey:@"menuLabel"]) { - theName = [NSMutableString stringWithFormat:NSLocalizedString(@"%@ (Filtered by %@)", @"History item filtered by values label"), - theName, [filterSettings objectForKey:@"menuLabel"]]; - } - } + if ([theEntry objectForKey:@"contentFilterV2"]) { + theName = [NSMutableString stringWithFormat:NSLocalizedString(@"%@ (Filtered)", @"History item filtered by values label"), theName]; } if ([theEntry objectForKey:@"contentPageNumber"]) { NSUInteger pageNumber = [[theEntry objectForKey:@"contentPageNumber"] unsignedIntegerValue]; if (pageNumber > 1) { - theName = [NSMutableString stringWithFormat:NSLocalizedString(@"%@ (Page %lu)", @"History item with page number label"), - theName, (unsigned long)pageNumber]; + theName = [NSMutableString stringWithFormat:NSLocalizedString(@"%@ (Page %lu)", @"History item with page number label"), theName, (unsigned long)pageNumber]; } } diff --git a/Source/SPIndexesController.m b/Source/SPIndexesController.m index ace6d882..89dd01b4 100644 --- a/Source/SPIndexesController.m +++ b/Source/SPIndexesController.m @@ -31,7 +31,6 @@ #import "SPIndexesController.h" #import "SPAlertSheets.h" #import "SPServerSupport.h" -#import "SPTableContent.h" #import "SPTableData.h" #import "SPDatabaseDocument.h" #import "SPTablesList.h" diff --git a/Source/SPRuleFilterController.h b/Source/SPRuleFilterController.h new file mode 100644 index 00000000..5dc4b07f --- /dev/null +++ b/Source/SPRuleFilterController.h @@ -0,0 +1,165 @@ +// +// SPRuleFilterController.h +// sequel-pro +// +// Created by Max Lohrmann on 04.05.18. +// Copyright (c) 2018 Max Lohrmann. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <https://github.com/sequelpro/sequelpro> + +@class SPTableData; +@class SPDatabaseDocument; +@class SPTablesList; +@class SPContentFilterManager; + +NSString * const SPRuleFilterHeightChangedNotification; + +@interface SPRuleFilterController : NSObject { + IBOutlet NSRuleEditor *filterRuleEditor; + IBOutlet SPTableData *tableDataInstance; + IBOutlet SPDatabaseDocument *tableDocumentInstance; + IBOutlet SPTablesList *tablesListInstance; + IBOutlet NSView *tableContentViewBelow; + IBOutlet NSButton *filterButton; + + NSMutableArray *columns; + NSMutableDictionary *contentFilters; + NSMutableDictionary *numberOfDefaultFilters; + + id _modelContainer; // private class + + SPContentFilterManager *contentFilterManager; + + CGFloat preferredHeight; + + id target; + SEL action; + + BOOL enabled; + + NSUInteger opNodeCacheVersion; +} + +/** + * Returns the rule editor view that is managed by this object + * + * SHOULD be called on the UI thread, or results may be inconsistent! + */ +- (NSRuleEditor *)view; + +/** + * Makes the first NSTextField found in the rule editor the first responder + * + * MUST BE CALLED ON THE UI THREAD! + */ +- (void)focusFirstInputField; + +/** + * Will reconfigure the columns of the rule editor from the given array. + * Call with nil to reset the editor to its initial empty state. + * Existing rows will be removed in any case! + * + * MUST BE CALLED ON THE UI THREAD! + */ +- (void)setColumns:(NSArray *)dataColumns; + +/** + * Converts the current filter expression displayed in the UI into an + * SQL string suitable for use in a WHERE clause. + * + * @param isBINARY Indicates that the filter should use the BINARY qualifier for ignoring + * collations during search. + * @param err Upon return contains and object describing why the SQL conversion failed, + * if it failed or nil, if no errors occured. + * + * MUST BE CALLED ON THE UI THREAD! + */ +- (NSString *)sqlWhereExpressionWithBinary:(BOOL)isBINARY error:(NSError **)err; + +/** + * Returns the current filter configuration in a serialized form that can be exported and + * reapplied later. + * + * MUST BE CALLED ON THE UI THREAD! + */ +- (NSDictionary *)serializedFilter; + +/** + * Restores the filter rule configuration from a given dictionary. + * The current column configuration must match the schema that was used when generating + * the serialized data, otherwise the invalid rules will be ignored. + * + * @param serialized A dictionary previously generated by calling -serializedFilter. + * @return A serialized filter + * + * MUST BE CALLED ON THE UI THREAD! + */ +- (void)restoreSerializedFilters:(NSDictionary *)serialized; + +/** + * Create a serialized filter from a given column, operator and operand. + * This is used when navigating foreign key links between tables to create the filter for the target table. + * + * @param colName Name of the column to filter (left side operand) + * @param opName Name of the filter (operator) + * @param values The values to filter with (right side operand) + * @return A serialized filter + * + * This method is thread-safe. + */ ++ (NSDictionary *)makeSerializedFilterForColumn:(NSString *)colName operator:(NSString *)opName values:(NSArray *)values; + +/** + * The view height the rule editor needs in order to not have to resort to scrollbars + * + * SHOULD be called on the UI thread, or results may be inconsistent! + */ +@property (readonly, assign, nonatomic) CGFloat preferredHeight; + +/** + * Indicates whether the rule editor has no filter expressions + * + * SHOULD be called on the UI thread, or results may be inconsistent! + */ +- (BOOL)isEmpty; + +/** + * Adds a new row to the rule editor + * + * MUST BE CALLED ON THE UI THREAD! + */ +- (void)addFilterExpression; + +/** + * Used when the rule editor wants to trigger filtering + * + * SHOULD be called on the UI thread, or results may be inconsistent! + */ +@property (assign, nonatomic) id target; +@property (assign, nonatomic) SEL action; + +- (BOOL)isEnabled; +- (void)setEnabled:(BOOL)enabled; + +@end diff --git a/Source/SPRuleFilterController.m b/Source/SPRuleFilterController.m new file mode 100644 index 00000000..99b7824d --- /dev/null +++ b/Source/SPRuleFilterController.m @@ -0,0 +1,1579 @@ +// +// SPRuleFilterController.m +// sequel-pro +// +// Created by Max Lohrmann on 04.05.18. +// Copyright (c) 2018 Max Lohrmann. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <https://github.com/sequelpro/sequelpro> + +#import "SPRuleFilterController.h" +#import "SPQueryController.h" +#import "SPDatabaseDocument.h" +#import "RegexKitLite.h" +#import "SPContentFilterManager.h" +#import "SPFunctions.h" +#import "SPTableFilterParser.h" + +typedef NS_ENUM(NSInteger, RuleNodeType) { + RuleNodeTypeColumn, + RuleNodeTypeString, + RuleNodeTypeOperator, + RuleNodeTypeArgument, + RuleNodeTypeConnector, +}; + +NSString * const SPRuleFilterHeightChangedNotification = @"SPRuleFilterHeightChanged"; + +/** + * The type of filter rule that the current item represents. + */ +const NSString * const SerFilterClass = @"filterClass"; +/** + * The current rule is a group row (an "AND" or "OR" expression with children) + */ +const NSString * const SerFilterClassGroup = @"groupNode"; +/** + * The current rule is a filter expression + */ +const NSString * const SerFilterClassExpression = @"expressionNode"; +/** + * Group Nodes only: + * Indicates whether the group is a conjunction. + * If YES, the children will be combined using "AND", otherwise using "OR". + */ +const NSString * const SerFilterGroupIsConjunction = @"isConjunction"; +/** + * Group Nodes only: + * An array of child filter rules (which again can be group or expression rules) + */ +const NSString * const SerFilterGroupChildren = @"children"; +/** + * Expression Nodes only: + * The name of the column to filter in (left side expression) + * + * Legacy names: + * @"filterField", fieldField + */ +const NSString * const SerFilterExprColumn = @"column"; +/** + * Expression Nodes only: + * The data type grouping of the column for applicable filters + */ +const NSString * const SerFilterExprType = @"filterType"; +/** + * Expression Nodes only: + * The title of the filter operator to apply + * + * Legacy names: + * @"filterComparison", compareField + */ +const NSString * const SerFilterExprComparison = @"filterComparison"; +/** + * Expression Nodes only: + * The values to apply the filter with (an array of 0 or more elements) + * + * Legacy names: + * @"filterValue", argumentField + * @"firstBetweenField", @"secondBetweenField", firstBetweenField, secondBetweenField + */ +const NSString * const SerFilterExprValues = @"filterValues"; +/** + * Expression Nodes only: + * the filter definition dictionary (as in ContentFilters.plist) + * for the filter represented by SerFilterExprComparison. + * + * This item is not designed to be serialized to disk + */ +const NSString * const SerFilterExprDefinition = @"_filterDefinition"; + +#pragma mark - + +@interface RuleNode : NSObject { + RuleNodeType type; +} +@property(assign, nonatomic) RuleNodeType type; +@end + +@interface ColumnNode : RuleNode { + NSString *name; + NSString *typegrouping; + NSArray *operatorCache; + NSUInteger opCacheVersion; +} +@property(copy, nonatomic) NSString *name; +@property(copy, nonatomic) NSString *typegrouping; +@property(retain, nonatomic) NSArray *operatorCache; +@property(assign, nonatomic) NSUInteger opCacheVersion; +@end + +@interface StringNode : RuleNode { + NSString *value; +} +@property(copy, nonatomic) NSString *value; +@end + +@interface OpNode : RuleNode { + // Note: The main purpose of this field is to have @"=" for column A and @"=" for column B to return NO in -isEqual: + // because otherwise NSRuleEditor will get confused and blow up. + ColumnNode *parentColumn; + NSDictionary *settings; + NSDictionary *filter; +} +@property (assign, nonatomic) ColumnNode *parentColumn; +@property (retain, nonatomic) NSDictionary *settings; +@property (retain, nonatomic) NSDictionary *filter; +@end + +@interface ArgNode : RuleNode { + NSDictionary *filter; + NSUInteger argIndex; + NSString *initialValue; +} +@property (copy, nonatomic) NSString *initialValue; +@property (retain, nonatomic) NSDictionary *filter; +@property (assign, nonatomic) NSUInteger argIndex; +@end + +@interface ConnectorNode : RuleNode { + NSDictionary *filter; + NSUInteger labelIndex; +} +@property (retain, nonatomic) NSDictionary *filter; +@property (assign, nonatomic) NSUInteger labelIndex; +@end + +#pragma mark - + +/** + * TODO: + * This class shouldn't even exist to begin with. + * Its sad story begins with this call in `-[SPRuleFilterController dealloc]`: + * + * [filterRuleEditor unbind:@"rows"]; + * + * `-dealloc` may not be the best method to undo what we did in `-awakeFromNib`, but it's the only thing we have. + * Also we have to unbind this object, or we may receive zombie calls later on because the binding is unretained. + * Which brings us to another huge mistake Apple made in the implementation of -unbind. The call looks like this: + * + * - [NSRulEditor unbind:] + * - [NSRuleEditor _rootRowsArray] + * - [NSRuleEditor->_boundArrayOwner mutableArrayValueForKeyPath:NSRuleEditor->_boundArrayKeyPath] + * + * -mutableArrayValueForKeyPath: is the culprit here since it does not return the object itself ("model") but + * instead returns an autoreleased proxy object which retains the parent object of the key. + * + * That explains why we can't put "model" into SPRuleFilterController: + * The `-[NSRuleEditor unbind:]` would cause a call to `-[SPRuleFilterController retain]` from within + * `-[SPRuleFilterController dealloc]` (which is pointless since there is no way out from -dealloc). + * This wouldn't be a problem if the proxy object was released again while dealloc is still on the stack, but + * since it is autoreleased we end up with a zombie call again. + * + * ModelContainer is a dummy intermediate to prevent this, since it is still valid when we enter -dealloc and + * trigger -unbind and thus can handle the -retain by the proxy object. + */ +@interface ModelContainer : NSObject +{ + NSMutableArray *model; +} +// This is the binding used by NSRuleEditor for the current state +@property (retain, nonatomic) NSMutableArray *model; +@end + +#pragma mark - + +@interface SPRuleFilterController () <NSRuleEditorDelegate> + +@property (readwrite, assign, nonatomic) CGFloat preferredHeight; + +- (NSArray *)_compareTypesForColumn:(ColumnNode *)colNode; +- (IBAction)_textFieldAction:(id)sender; +- (IBAction)_editFiltersAction:(id)sender; +- (void)_contentFiltersHaveBeenUpdated:(NSNotification *)notification; ++ (NSDictionary *)_flattenSerializedFilter:(NSDictionary *)in; +static BOOL SerIsGroup(NSDictionary *dict); +- (NSDictionary *)_serializedFilterIncludingFilterDefinition:(BOOL)includeDefinition; ++ (void)_writeFilterTree:(NSDictionary *)in toString:(NSMutableString *)out wrapInParenthesis:(BOOL)wrap binary:(BOOL)isBINARY error:(NSError **)err; +- (NSMutableDictionary *)_restoreSerializedFilter:(NSDictionary *)serialized; +static void _addIfNotNil(NSMutableArray *array, id toAdd); +- (ColumnNode *)_columnForName:(NSString *)name; +- (OpNode *)_operatorNamed:(NSString *)title forColumn:(ColumnNode *)col; +- (BOOL)_focusOnFieldInSubtree:(NSDictionary *)dict; +- (void)_resize; +- (void)openContentFilterManagerForFilterType:(NSString *)filterType; +- (IBAction)filterTable:(id)sender; +- (IBAction)_menuItemInRuleEditorClicked:(id)sender; +- (void)_pretendPlayRuleEditorForCriteria:(NSMutableArray *)criteria displayValues:(NSMutableArray *)displayValues inRow:(NSInteger)row; +- (void)_ensureValidOperatorCache:(ColumnNode *)col; +static BOOL _arrayContainsInViewHierarchy(NSArray *haystack, id needle); + +@end + +@implementation SPRuleFilterController + +@synthesize preferredHeight = preferredHeight; +@synthesize target = target; +@synthesize action = action; + +- (instancetype)init +{ + if((self = [super init])) { + columns = [[NSMutableArray alloc] init]; + _modelContainer = [[ModelContainer alloc] init]; + preferredHeight = 0.0; + target = nil; + action = NULL; + opNodeCacheVersion = 1; + + // Init default filters for Content Browser + contentFilters = [[NSMutableDictionary alloc] init]; + numberOfDefaultFilters = [[NSMutableDictionary alloc] init]; + + NSError *readError = nil; + NSString *filePath = [NSBundle pathForResource:@"ContentFilters.plist" ofType:nil inDirectory:[[NSBundle mainBundle] bundlePath]]; + NSData *defaultFilterData = [NSData dataWithContentsOfFile:filePath + options:NSMappedRead + error:&readError]; + + if(defaultFilterData && !readError) { + NSDictionary *defaultFilterDict = [NSPropertyListSerialization propertyListWithData:defaultFilterData + options:NSPropertyListMutableContainersAndLeaves + format:NULL + error:&readError]; + + if(defaultFilterDict && !readError) { + [contentFilters setDictionary:defaultFilterDict]; + } + } + + if (readError) { + NSLog(@"Error while reading 'ContentFilters.plist':\n%@", readError); + NSBeep(); + } + else { + [numberOfDefaultFilters setObject:[NSNumber numberWithInteger:[[contentFilters objectForKey:@"number"] count]] forKey:@"number"]; + [numberOfDefaultFilters setObject:[NSNumber numberWithInteger:[[contentFilters objectForKey:@"date"] count]] forKey:@"date"]; + [numberOfDefaultFilters setObject:[NSNumber numberWithInteger:[[contentFilters objectForKey:@"string"] count]] forKey:@"string"]; + [numberOfDefaultFilters setObject:[NSNumber numberWithInteger:[[contentFilters objectForKey:@"spatial"] count]] forKey:@"spatial"]; + } + } + return self; +} + +- (void)awakeFromNib +{ + [filterRuleEditor bind:@"rows" toObject:_modelContainer withKeyPath:@"model" options:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(_contentFiltersHaveBeenUpdated:) + name:SPContentFiltersHaveBeenUpdatedNotification + object:nil]; +} + +- (void)focusFirstInputField +{ + for(NSDictionary *rootItem in [_modelContainer model]) { + if([self _focusOnFieldInSubtree:rootItem]) return; + } +} + +- (BOOL)_focusOnFieldInSubtree:(NSDictionary *)dict +{ + //if we are a simple row we might have an input field ourself, otherwise search among our children + if([[dict objectForKey:@"rowType"] unsignedIntegerValue] == NSRuleEditorRowTypeSimple) { + for(id obj in [dict objectForKey:@"displayValues"]) { + if([obj isKindOfClass:[NSTextField class]]) { + [[(NSTextField *)obj window] makeFirstResponder:obj]; + return YES; + } + } + } + else { + for(NSDictionary *child in [dict objectForKey:@"subrows"]) { + if([self _focusOnFieldInSubtree:child]) return YES; + } + } + return NO; +} + +- (void)setColumns:(NSArray *)dataColumns; +{ + // we have to access the model in the same way the rule editor does for it to realize the changes + [[_modelContainer mutableArrayValueForKey:@"model"] removeAllObjects]; + + [columns removeAllObjects]; + + //without a table there is nothing to filter + if(dataColumns) { + //sort column names if enabled + NSArray *columnDefinitions = dataColumns; + if ([[NSUserDefaults standardUserDefaults] boolForKey:SPAlphabeticalTableSorting]) { + NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]; + columnDefinitions = [columnDefinitions sortedArrayUsingDescriptors:@[sortDescriptor]]; + } + + // get the columns + for (NSDictionary *colDef in columnDefinitions) { + ColumnNode *node = [[ColumnNode alloc] init]; + [node setName:[colDef objectForKey:@"name"]]; + [node setTypegrouping:[colDef objectForKey:@"typegrouping"]]; + [columns addObject:node]; + [node release]; + } + } + + // make the rule editor reload the criteria + [filterRuleEditor reloadCriteria]; + + // disable UI if no criteria exist + [self setEnabled:([columns count] != 0)]; +} + +- (NSInteger)ruleEditor:(NSRuleEditor *)editor numberOfChildrenForCriterion:(nullable id)criterion withRowType:(NSRuleEditorRowType)rowType +{ + // nil criterion is always the first element in a row, compound rows are only for "AND"/"OR" groups + if(!criterion && rowType == NSRuleEditorRowTypeCompound) { + return 2; + } + else if(!criterion && rowType == NSRuleEditorRowTypeSimple) { + return [columns count]; + } + else if(rowType == NSRuleEditorRowTypeSimple) { + // the children of the columns are their operators + RuleNodeType type = [(RuleNode *)criterion type]; + if(type == RuleNodeTypeColumn) { + ColumnNode *node = (ColumnNode *)criterion; + [self _ensureValidOperatorCache:node]; + return [[node operatorCache] count]; + } + // the first child of an operator is the first argument (if it has one) + else if(type == RuleNodeTypeOperator) { + OpNode *node = (OpNode *)criterion; + NSInteger numOfArgs = [[[node filter] objectForKey:@"NumberOfArguments"] integerValue]; + return (numOfArgs > 0) ? 1 : 0; + } + // the child of an argument can only be the conjunction label if more arguments follow + else if(type == RuleNodeTypeArgument) { + ArgNode *node = (ArgNode *)criterion; + NSInteger numOfArgs = [[[node filter] objectForKey:@"NumberOfArguments"] integerValue]; + return (numOfArgs > [node argIndex]+1) ? 1 : 0; + } + // the child of a conjunction is the next argument, if we have one + else if(type == RuleNodeTypeConnector) { + ConnectorNode *node = (ConnectorNode *)criterion; + NSInteger numOfArgs = [[[node filter] objectForKey:@"NumberOfArguments"] integerValue]; + return (numOfArgs > [node labelIndex]+1) ? 1 : 0; + } + } + return 0; +} + +- (id)ruleEditor:(NSRuleEditor *)editor child:(NSInteger)index forCriterion:(nullable id)criterion withRowType:(NSRuleEditorRowType)rowType +{ + // nil criterion is always the first element in a row, compound rows are only for "AND"/"OR" groups + if(!criterion && rowType == NSRuleEditorRowTypeCompound) { + StringNode *node = [[StringNode alloc] init]; + switch(index) { + case 0: [node setValue:@"AND"]; break; + case 1: [node setValue:@"OR"]; break; + } + return [node autorelease]; + } + // this is the column field + else if(!criterion && rowType == NSRuleEditorRowTypeSimple) { + return [columns objectAtIndex:index]; + } + else if(rowType == NSRuleEditorRowTypeSimple) { + // the children of the columns are their operators + RuleNodeType type = [(RuleNode *) criterion type]; + if (type == RuleNodeTypeColumn) { + return [[criterion operatorCache] objectAtIndex:index]; + } + // the first child of an operator is the first argument + else if(type == RuleNodeTypeOperator) { + NSDictionary *filter = [(OpNode *)criterion filter]; + if([[filter objectForKey:@"NumberOfArguments"] integerValue]) { + ArgNode *arg = [[ArgNode alloc] init]; + [arg setFilter:filter]; + [arg setArgIndex:0]; + return [arg autorelease]; + } + } + // the child of an argument can only be the conjunction label if more arguments follow + else if(type == RuleNodeTypeArgument) { + NSDictionary *filter = [(ArgNode *)criterion filter]; + NSUInteger argIndex = [(ArgNode *)criterion argIndex]; + if([[filter objectForKey:@"NumberOfArguments"] integerValue] > argIndex +1) { + ConnectorNode *node = [[ConnectorNode alloc] init]; + [node setFilter:filter]; + [node setLabelIndex:argIndex]; // label 0 follows argument 0 + return [node autorelease]; + } + } + // the child of a conjunction is the next argument, if we have one + else if(type == RuleNodeTypeConnector) { + ConnectorNode *node = (ConnectorNode *)criterion; + NSInteger numOfArgs = [[[node filter] objectForKey:@"NumberOfArguments"] integerValue]; + if(numOfArgs > [node labelIndex]+1) { + ArgNode *arg = [[ArgNode alloc] init]; + [arg setFilter:[node filter]]; + [arg setArgIndex:([node labelIndex]+1)]; + return [arg autorelease]; + } + } + } + return nil; +} + +- (id)ruleEditor:(NSRuleEditor *)editor displayValueForCriterion:(id)criterion inRow:(NSInteger)row +{ + switch([(RuleNode *)criterion type]) { + case RuleNodeTypeString: return [(StringNode *)criterion value]; + case RuleNodeTypeColumn: return [(ColumnNode *)criterion name]; + case RuleNodeTypeOperator: { + OpNode *node = (OpNode *)criterion; + NSMenuItem *item; + if ([[[node settings] objectForKey:@"isSeparator"] boolValue]) { + item = [NSMenuItem separatorItem]; + } + else { + /* NOTE: + * Apple's doc on NSRuleEditor says that returning NSMenuItems is supported. + * However there seems to be a major discrepancy between what Apple considers "supported" and what any + * sane person would consider supported. + * + * Basically one would expect NSMenuItems to be handled in the same way a number of NSString children of a + * row's element will be handled, but that was not Apples intention. By supported they actually mean + * "Your app won't crash immediately if you return an NSMenuItem here" - but that's about it. + * Even selecting such an NSMenuItem will already cause an exception on 10.6 and be treated as a NOOP on + * later OSes. + * So if we return NSMenuItems we have to implement the full logic of the NSRuleEditor for updating and + * displaying the row ourselves, starting with handling the target/action of the NSMenuItems! + */ + item = [[NSMenuItem alloc] initWithTitle:[[node settings] objectForKey:@"title"] action:NULL keyEquivalent:@""]; + [item setToolTip:[[node settings] objectForKey:@"tooltip"]]; + [item setTag:[[[node settings] objectForKey:@"tag"] integerValue]]; + [item setRepresentedObject:@{ + @"node": node, + // this one is needed by the "Edit filters…" item for context + @"filterType": SPBoxNil([[node settings] objectForKey:@"filterType"]), + }]; + [item setTarget:self]; + [item setAction:@selector(_menuItemInRuleEditorClicked:)]; + [item autorelease]; + } + return item; + } + case RuleNodeTypeArgument: { + //an argument is a textfield + ArgNode *node = (ArgNode *)criterion; + NSTextField *textField = [[NSTextField alloc] init]; + [[textField cell] setSendsActionOnEndEditing:YES]; + [[textField cell] setUsesSingleLineMode:YES]; + [textField setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; + [textField sizeToFit]; + [textField setTarget:self]; + [textField setAction:@selector(_textFieldAction:)]; + if([node initialValue]) [textField setStringValue:[node initialValue]]; + NSRect frame = [textField frame]; + //adjust width, to make the field wider + frame.size.width = 500; //TODO determine a good width (possibly from the field type size) - how to access the rule editors bounds? + [textField setFrame:frame]; + return [textField autorelease]; + } + case RuleNodeTypeConnector: { + // a simple string for once + ConnectorNode *node = (ConnectorNode *)criterion; + NSArray* labels = [[node filter] objectForKey:@"ConjunctionLabels"]; + return (labels && [labels count] == 1)? [labels objectAtIndex:0] : @""; + } + } + + return nil; +} + +- (IBAction)_textFieldAction:(id)sender +{ + // if the action was caused by pressing return or enter, trigger filtering + NSEvent *event = [NSApp currentEvent]; + if(event && [event type] == NSKeyDown && ([event keyCode] == 36 || [event keyCode] == 76)) { + [self filterTable:nil]; + } +} + +- (IBAction)_menuItemInRuleEditorClicked:(id)sender +{ + if(!sender) return; // NSRuleEditor will throw on nil + + NSInteger row = [filterRuleEditor rowForDisplayValue:sender]; + + if(row == NSNotFound) return; // unknown display values + + OpNode *node = [[(NSMenuItem *)sender representedObject] objectForKey:@"node"]; + + // if the row has an explicit handler, pass on the action and do nothing + id _target = [[node settings] objectForKey:@"target"]; + SEL _action = (SEL)[(NSValue *)[[node settings] objectForKey:@"action"] pointerValue]; + if(_target && _action) { + [_target performSelector:_action withObject:sender]; + return; + } + + /* now comes the painful part, where we'd have to find out where exactly in the row this + * displayValue should appear. + * + * Luckily we know that this method will only be invoked by the displayValues of OpNode + * and currently OpNode can only appear as the second node in a row (after the column). + * + * Annoyingly we can't tell the rule editor to just replace a single element. We actually + * have to recalculate the whole row starting with the element we replaced - a task the + * rule editor would normally do for us when using NSStrings! + */ + NSMutableArray *criteria = [[filterRuleEditor criteriaForRow:row] mutableCopy]; + NSMutableArray *displayValues = [[filterRuleEditor displayValuesForRow:row] mutableCopy]; + + // find the position of the previous opnode (just for safety) + NSUInteger opIndex = NSNotFound; + NSUInteger i = 0; + for(RuleNode *obj in criteria) { + if([obj type] == RuleNodeTypeOperator) { + opIndex = i; + break; + } + i++; + } + + if(opIndex < [criteria count]) { + // yet another uglyness: if one of the displayValues is an input and currently the first responder + // we have to manually restore that for the new input we create for UX reasons. + // However an NSTextField is seldom a first responder, usually it's an invisible subview of the text field... + id firstResponder = [[filterRuleEditor window] firstResponder]; + BOOL hasFirstResponderInRow = _arrayContainsInViewHierarchy(displayValues, firstResponder); + + //remove previous opnode and everything that follows and append new opnode + NSRange stripRange = NSMakeRange(opIndex, ([criteria count] - opIndex)); + [criteria removeObjectsInRange:stripRange]; + [criteria addObject:node]; + + //remove the display value for the old op node and everything that followed + [displayValues removeObjectsInRange:stripRange]; + + //now we'll fill in everything again + [self _pretendPlayRuleEditorForCriteria:criteria displayValues:displayValues inRow:row]; + + //and update the row to its new state + [filterRuleEditor setCriteria:criteria andDisplayValues:displayValues forRowAtIndex:row]; + + if(hasFirstResponderInRow) { + // make the next possible object after the opnode the new next responder (since the previous one is gone now) + for (NSUInteger j = stripRange.location + 1; j < [displayValues count]; ++j) { + id obj = [displayValues objectAtIndex:j]; + if([obj respondsToSelector:@selector(acceptsFirstResponder)] && [obj acceptsFirstResponder]) { + [[filterRuleEditor window] makeFirstResponder:obj]; + break; + } + } + } + } + + [criteria release]; + [displayValues release]; +} + +BOOL _arrayContainsInViewHierarchy(NSArray *haystack, id needle) +{ + //first, try it the easy way + if([haystack indexOfObjectIdenticalTo:needle] != NSNotFound) return YES; + + // otherwise, if needle is a view, check if it appears as a desencdant of some other view in haystack + Class NSViewClass = [NSView class]; + if([needle isKindOfClass:NSViewClass]) { + for(id obj in haystack) { + if([obj isKindOfClass:NSViewClass] && [needle isDescendantOf:obj]) return YES; + } + } + + return NO; +} + +/** + * This method recursively fills up the passed-in criteria and displayValues arrays with objects in the way the + * NSRuleEditor would, so they can be used with the -setCriteria:andDisplayValues:forRowAtIndex: call. + * + * Assumptions made: + * - row is a valid row within the bounds of the rule editor + * - criteria contains at least one object + * - displayValues contains exactly one less object than criteria + */ +- (void)_pretendPlayRuleEditorForCriteria:(NSMutableArray *)criteria displayValues:(NSMutableArray *)displayValues inRow:(NSInteger)row +{ + id curCriterion = [criteria lastObject]; + + //first fill in the display value for the current criterion + id display = [self ruleEditor:filterRuleEditor displayValueForCriterion:curCriterion inRow:row]; + if(!display) return; // abort if unset + [displayValues addObject:display]; + + // now let's check if we have to go deeper + NSRuleEditorRowType rowType = [filterRuleEditor rowTypeForRow:row]; + if([self ruleEditor:filterRuleEditor numberOfChildrenForCriterion:curCriterion withRowType:rowType]) { + // we only care for the first child, though + id nextCriterion = [self ruleEditor:filterRuleEditor child:0 forCriterion:curCriterion withRowType:rowType]; + if(nextCriterion) { + [criteria addObject:nextCriterion]; + [self _pretendPlayRuleEditorForCriteria:criteria displayValues:displayValues inRow:row]; + } + } +} + +- (IBAction)filterTable:(id)sender +{ + if(target && action) [target performSelector:action withObject:self]; +} + +- (void)_resize +{ + // The situation with the sizing is a bit f'ed up: + // - When -ruleEditorRowsDidChange: is invoked the NSRuleEditor has not yet updated its required frame size + // - We can't use KVO on -frame either, because SPTableContent will update the container size which + // ultimately also updates the NSRuleEditor's frame, causing a loop + // - Calling -sizeToFit works, but only when the NSRuleEditor is growing. It won't shrink + // after removing rows. + // - -intrinsicContentSize is what we want, but that method is 10.7+, so on 10.6 let's do the + // easiest workaround (note that both -intrinsicContentSize and -sizeToFit internally use -[NSRuleEditor _minimumFrameHeight]) + CGFloat wantsHeight; + if([filterRuleEditor respondsToSelector:@selector(intrinsicContentSize)]) { + NSSize sz = [filterRuleEditor intrinsicContentSize]; + wantsHeight = sz.height; + } + else { + wantsHeight = [filterRuleEditor rowHeight] * [filterRuleEditor numberOfRows]; + } + if(wantsHeight != preferredHeight) { + [self setPreferredHeight:wantsHeight]; + [[NSNotificationCenter defaultCenter] postNotificationName:SPRuleFilterHeightChangedNotification object:self]; + } +} + +- (void)ruleEditorRowsDidChange:(NSNotification *)notification +{ + //TODO find a better way to trigger resize + // We can't do this here, because it will cause rows to jump around when removing them (the add case works fine, though) + [self performSelector:@selector(_resize) withObject:nil afterDelay:0.2]; + //[self _resize]; +} + +- (void)dealloc +{ + [filterRuleEditor unbind:@"rows"]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + // WARNING: THIS MUST COME AFTER -unbind:! See the class comment on ModelContainer for the reasoning + SPClear(_modelContainer); + SPClear(columns); + SPClear(contentFilters); + SPClear(numberOfDefaultFilters); + [super dealloc]; +} + +/** + * Sets the compare types for the filter and the appropriate formatter for the textField + */ +- (NSArray *)_compareTypesForColumn:(ColumnNode *)colNode +{ + if(contentFilters == nil + || ![contentFilters objectForKey:@"number"] + || ![contentFilters objectForKey:@"string"] + || ![contentFilters objectForKey:@"date"]) { + NSLog(@"Error while setting filter types."); + NSBeep(); + return @[]; + } + + NSString *fieldTypeGrouping; + if([colNode typegrouping]) { + fieldTypeGrouping = [NSString stringWithString:[colNode typegrouping]]; + } + else { + return @[]; + } + + NSMutableArray *compareItems = [NSMutableArray array]; + + NSString *compareType; + + if ( [fieldTypeGrouping isEqualToString:@"date"] ) { + compareType = @"date"; + + /* + if ([fieldType isEqualToString:@"timestamp"]) { + [argumentField setFormatter:[[NSDateFormatter alloc] + initWithDateFormat:@"%Y-%m-%d %H:%M:%S" allowNaturalLanguage:YES]]; + } + if ([fieldType isEqualToString:@"datetime"]) { + [argumentField setFormatter:[[NSDateFormatter alloc] initWithDateFormat:@"%Y-%m-%d %H:%M:%S" allowNaturalLanguage:YES]]; + } + if ([fieldType isEqualToString:@"date"]) { + [argumentField setFormatter:[[NSDateFormatter alloc] initWithDateFormat:@"%Y-%m-%d" allowNaturalLanguage:YES]]; + } + if ([fieldType isEqualToString:@"time"]) { + [argumentField setFormatter:[[NSDateFormatter alloc] initWithDateFormat:@"%H:%M:%S" allowNaturalLanguage:YES]]; + } + if ([fieldType isEqualToString:@"year"]) { + [argumentField setFormatter:[[NSDateFormatter alloc] initWithDateFormat:@"%Y" allowNaturalLanguage:YES]]; + } + */ + + // TODO: A bug in the framework previously meant enum fields had to be treated as string fields for the purposes + // of comparison - this can now be split out to support additional comparison fucntionality if desired. + } + else if ([fieldTypeGrouping isEqualToString:@"string"] || [fieldTypeGrouping isEqualToString:@"binary"] + || [fieldTypeGrouping isEqualToString:@"textdata"] || [fieldTypeGrouping isEqualToString:@"blobdata"] + || [fieldTypeGrouping isEqualToString:@"enum"]) { + + compareType = @"string"; + // [argumentField setFormatter:nil]; + + } + else if ([fieldTypeGrouping isEqualToString:@"bit"] || [fieldTypeGrouping isEqualToString:@"integer"] + || [fieldTypeGrouping isEqualToString:@"float"]) { + compareType = @"number"; + // [argumentField setFormatter:numberFormatter]; + + } + else if ([fieldTypeGrouping isEqualToString:@"geometry"]) { + compareType = @"spatial"; + + } + else { + compareType = @""; + NSBeep(); + NSLog(@"ERROR: unknown type for comparision: in %@", fieldTypeGrouping); + } + + // Add IS NULL and IS NOT NULL as they should always be available + // [compareField addItemWithTitle:@"IS NULL"]; + // [compareField addItemWithTitle:@"IS NOT NULL"]; + + // Remove user-defined filters first + if([numberOfDefaultFilters objectForKey:compareType]) { + NSUInteger cycles = [[contentFilters objectForKey:compareType] count] - [[numberOfDefaultFilters objectForKey:compareType] integerValue]; + while(cycles > 0) { + [[contentFilters objectForKey:compareType] removeLastObject]; + cycles--; + } + } + + NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; + +#ifndef SP_CODA /* content filters */ + // Load global user-defined content filters + if([prefs objectForKey:SPContentFilters] + && [contentFilters objectForKey:compareType] + && [[prefs objectForKey:SPContentFilters] objectForKey:compareType]) + { + [[contentFilters objectForKey:compareType] addObjectsFromArray:[[prefs objectForKey:SPContentFilters] objectForKey:compareType]]; + } + + // Load doc-based user-defined content filters + if([[SPQueryController sharedQueryController] contentFilterForFileURL:[tableDocumentInstance fileURL]]) { + id filters = [[SPQueryController sharedQueryController] contentFilterForFileURL:[tableDocumentInstance fileURL]]; + if([filters objectForKey:compareType]) + [[contentFilters objectForKey:compareType] addObjectsFromArray:[filters objectForKey:compareType]]; + } +#endif + + NSUInteger i = 0; + if([contentFilters objectForKey:compareType]) { + for (id filter in [contentFilters objectForKey:compareType]) { + // Create the tooltip + NSString *tooltip; + if ([filter objectForKey:@"Tooltip"]) + tooltip = [filter objectForKey:@"Tooltip"]; + else { + NSMutableString *tip = [[NSMutableString alloc] init]; + if ([filter objectForKey:@"Clause"] && [(NSString *) [filter objectForKey:@"Clause"] length]) { + [tip setString:[[filter objectForKey:@"Clause"] stringByReplacingOccurrencesOfRegex:@"(?<!\\\\)(\\$\\{.*?\\})" withString:@"[arg]"]]; + if ([tip isMatchedByRegex:@"(?<!\\\\)\\$BINARY"]) { + [tip replaceOccurrencesOfRegex:@"(?<!\\\\)\\$BINARY" withString:@""]; + [tip appendString:NSLocalizedString(@"\n\nPress ⇧ for binary search (case-sensitive).", @"\n\npress shift for binary search tooltip message")]; + } + [tip flushCachedRegexData]; + [tip replaceOccurrencesOfRegex:@"(?<!\\\\)\\$CURRENT_FIELD" withString:[[colNode name] backtickQuotedString]]; + [tip flushCachedRegexData]; + tooltip = [NSString stringWithString:tip]; + } else { + tooltip = @""; + } + [tip release]; + } + + OpNode *node = [[OpNode alloc] init]; + [node setParentColumn:colNode]; + [node setSettings:@{ + @"title": ([filter objectForKey:@"MenuLabel"] ? [filter objectForKey:@"MenuLabel"] : @"not specified"), + @"tooltip": tooltip, + @"tag": @(i), + @"filterType": compareType, + }]; + [node setFilter:filter]; + [compareItems addObject:node]; + [node release]; + i++; + } + } + + { + OpNode *node = [[OpNode alloc] init]; + [node setParentColumn:colNode]; + [node setSettings:@{ + @"isSeparator": @YES, + }]; + [compareItems addObject:node]; + [node release]; + } + + { + OpNode *node = [[OpNode alloc] init]; + [node setParentColumn:colNode]; + [node setSettings:@{ + @"title": NSLocalizedString(@"Edit Filters…", @"edit filter"), + @"tooltip": NSLocalizedString(@"Edit user-defined Filters…", @"edit user-defined filter"), + @"tag": @(i), + @"target": self, + @"action": [NSValue valueWithPointer:@selector(_editFiltersAction:)], + @"filterType": compareType, + }]; + [compareItems addObject:node]; + [node release]; + } + + return compareItems; +} + +- (IBAction)_editFiltersAction:(id)sender +{ + if([sender isKindOfClass:[NSMenuItem class]]) { + NSMenuItem *menuItem = (NSMenuItem *)sender; + NSString *filterType = [(NSDictionary *)[menuItem representedObject] objectForKey:@"filterType"]; + if([filterType unboxNull]) [self openContentFilterManagerForFilterType:filterType]; + } +} + +- (void)openContentFilterManagerForFilterType:(NSString *)filterType +{ + // init query favorites controller +#ifndef SP_CODA + [[NSUserDefaults standardUserDefaults] synchronize]; +#endif + if(contentFilterManager) [contentFilterManager release]; + contentFilterManager = [[SPContentFilterManager alloc] initWithDatabaseDocument:tableDocumentInstance forFilterType:filterType]; + + // Open query favorite manager + [NSApp beginSheet:[contentFilterManager window] + modalForWindow:[tableDocumentInstance parentWindow] + modalDelegate:contentFilterManager + didEndSelector:nil + contextInfo:nil]; +} + +- (void)_contentFiltersHaveBeenUpdated:(NSNotification *)notification +{ + // invalidate our OpNode caches + opNodeCacheVersion++; + //tell the rule editor to reload its criteria + [filterRuleEditor reloadCriteria]; +} + +- (void)_ensureValidOperatorCache:(ColumnNode *)col +{ + if(![col operatorCache] || [col opCacheVersion] != opNodeCacheVersion) { + NSArray *ops = [self _compareTypesForColumn:col]; + [col setOperatorCache:ops]; + [col setOpCacheVersion:opNodeCacheVersion]; + } +} + +- (BOOL)isEmpty +{ + return ([[_modelContainer model] count] == 0); +} + +- (void)addFilterExpression +{ + [filterRuleEditor insertRowAtIndex:0 withType:NSRuleEditorRowTypeSimple asSubrowOfRow:-1 animate:NO]; +} + +- (NSRuleEditor *)view +{ + return filterRuleEditor; +} + +- (BOOL)isEnabled +{ + return enabled; +} + +- (void)setEnabled:(BOOL)_enabled +{ + enabled = _enabled; + [filterButton setEnabled:_enabled]; + [filterRuleEditor setEnabled:_enabled]; +} + +- (NSString *)sqlWhereExpressionWithBinary:(BOOL)isBINARY error:(NSError **)err +{ + NSMutableString *filterString = [[NSMutableString alloc] init]; + NSError *innerError = nil; + + @autoreleasepool { + //get the serialized filter and try to optimise it + NSDictionary *filterTree = [[self class] _flattenSerializedFilter:[self _serializedFilterIncludingFilterDefinition:YES]]; + + // build it recursively + [[self class] _writeFilterTree:filterTree toString:filterString wrapInParenthesis:NO binary:isBINARY error:&innerError]; + + [innerError retain]; // carry the error (if any) outside of the scope of the autoreleasepool + } + + if(innerError) { + [filterString release]; + if(err) *err = [innerError autorelease]; + return nil; + } + + if(err) *err = nil; + + NSString *out = [filterString copy]; + [filterString release]; + + return [out autorelease]; +} + +- (NSDictionary *)serializedFilter +{ + return [self _serializedFilterIncludingFilterDefinition:NO]; +} + +- (NSDictionary *)_serializedFilterIncludingFilterDefinition:(BOOL)includeDefinition +{ + NSMutableArray *rootItems = [NSMutableArray arrayWithCapacity:[[_modelContainer model] count]]; + for(NSDictionary *item in [_modelContainer model]) { + [rootItems addObject:[self _serializeSubtree:item includingDefinition:includeDefinition]]; + } + //the root serialized filter can either be an AND of multiple root items or a single root item + if([rootItems count] == 1) { + return [rootItems objectAtIndex:0]; + } + else { + return @{ + SerFilterClass: SerFilterClassGroup, + SerFilterGroupIsConjunction: @YES, + SerFilterGroupChildren: rootItems, + }; + } +} + +- (NSDictionary *)_serializeSubtree:(NSDictionary *)item includingDefinition:(BOOL)includeDefinition +{ + NSRuleEditorRowType rowType = (NSRuleEditorRowType)[[item objectForKey:@"rowType"] unsignedIntegerValue]; + // check if we have an AND or OR compound row + if(rowType == NSRuleEditorRowTypeCompound) { + // process all children + NSArray *subrows = [item objectForKey:@"subrows"]; + NSMutableArray *children = [[NSMutableArray alloc] initWithCapacity:[subrows count]]; + for(NSDictionary *subitem in subrows) { + [children addObject:[self _serializeSubtree:subitem includingDefinition:includeDefinition]]; + } + StringNode *node = [[item objectForKey:@"criteria"] objectAtIndex:0]; + BOOL isConjunction = [@"AND" isEqualToString:[node value]]; + NSDictionary *out = @{ + SerFilterClass: SerFilterClassGroup, + SerFilterGroupIsConjunction: @(isConjunction), + SerFilterGroupChildren: children, + }; + [children release]; + return out; + } + else { + NSArray *criteria = [item objectForKey:@"criteria"]; + NSArray *displayValues = [item objectForKey:@"displayValues"]; + ColumnNode *col = [criteria objectAtIndex:0]; + OpNode *op = [criteria objectAtIndex:1]; + NSMutableArray *filterValues = [[NSMutableArray alloc] initWithCapacity:2]; + for (NSUInteger i = 2; i < [criteria count]; ++i) { // the first two must always be column and operator + if([(RuleNode *)[criteria objectAtIndex:i] type] != RuleNodeTypeArgument) continue; + // if we found an argument, the displayValue will be an NSTextField we can ask for the value + NSString *value = [(NSTextField *)[displayValues objectAtIndex:i] stringValue]; + [filterValues addObject:value]; + } + NSDictionary *out = @{ + SerFilterClass: SerFilterClassExpression, + SerFilterExprColumn: [col name], + SerFilterExprType: [[op settings] objectForKey:@"filterType"], + SerFilterExprComparison: [[op filter] objectForKey:@"MenuLabel"], + SerFilterExprValues: filterValues, + }; + if(includeDefinition) { + out = [NSMutableDictionary dictionaryWithDictionary:out]; + [(NSMutableDictionary *)out setObject:[op filter] forKey:SerFilterExprDefinition]; + } + [filterValues release]; + return out; + } +} + +void _addIfNotNil(NSMutableArray *array, id toAdd) +{ + if(toAdd != nil) [array addObject:toAdd]; +} + +- (void)restoreSerializedFilters:(NSDictionary *)serialized +{ + if(!serialized) return; + + NSMutableArray *newModel = [[NSMutableArray alloc] init]; + @autoreleasepool { + // if the root object is an AND group directly restore its contents, otherwise restore the object + if(SerIsGroup(serialized) && [[serialized objectForKey:SerFilterGroupIsConjunction] boolValue]) { + for(NSDictionary *child in [serialized objectForKey:SerFilterGroupChildren]) { + _addIfNotNil(newModel, [self _restoreSerializedFilter:child]); + } + } + else { + _addIfNotNil(newModel, [self _restoreSerializedFilter:serialized]); + } + } + + // we have to access the model in the same way the rule editor does for it to realize the changes + NSMutableArray *proxy = [_modelContainer mutableArrayValueForKey:@"model"]; + [proxy setArray:newModel]; + + [newModel release]; +} + +- (NSMutableDictionary *)_restoreSerializedFilter:(NSDictionary *)serialized +{ + NSMutableDictionary *obj = [[NSMutableDictionary alloc] initWithCapacity:4]; + + if(SerIsGroup(serialized)) { + [obj setObject:@(NSRuleEditorRowTypeCompound) forKey:@"rowType"]; + + StringNode *sn = [[StringNode alloc] init]; + [sn setValue:([[serialized objectForKey:SerFilterGroupIsConjunction] boolValue] ? @"AND" : @"OR")]; + // those have to be mutable arrays for the rule editor to work + NSMutableArray *criteria = [NSMutableArray arrayWithObject:sn]; + [obj setObject:criteria forKey:@"criteria"]; + + id displayValue = [self ruleEditor:filterRuleEditor displayValueForCriterion:sn inRow:-1]; + NSMutableArray *displayValues = [NSMutableArray arrayWithObject:displayValue]; + [obj setObject:displayValues forKey:@"displayValues"]; + [sn release]; + + NSArray *children = [serialized objectForKey:SerFilterGroupChildren]; + NSMutableArray *subrows = [[NSMutableArray alloc] initWithCapacity:[children count]]; + for(NSDictionary *child in children) { + _addIfNotNil(subrows, [self _restoreSerializedFilter:child]); + } + [obj setObject:subrows forKey:@"subrows"]; + [subrows release]; + } + else { + [obj setObject:@(NSRuleEditorRowTypeSimple) forKey:@"rowType"]; + //simple rows can't have child rows + [obj setObject:[NSMutableArray array] forKey:@"subrows"]; + + NSMutableArray *criteria = [NSMutableArray arrayWithCapacity:5]; + + //first look up the column, bail if it doesn't exist anymore or types changed + NSString *columnName = [serialized objectForKey:SerFilterExprColumn]; + ColumnNode *col = [self _columnForName:columnName]; + if(!col) { + SPLog(@"cannot deserialize unknown column: %@", columnName); + goto fail; + } + [criteria addObject:col]; + + //next try to find the given operator + NSString *operatorName = [serialized objectForKey:SerFilterExprComparison]; + OpNode *op = [self _operatorNamed:operatorName forColumn:col]; + if(!op) { + SPLog(@"cannot deserialize unknown operator: %@",operatorName); + goto fail; + } + [criteria addObject:op]; + + // we still have to check if the current column type is the same as when we serialized because an operator + // with the same name can still act differently for different types + NSString *curFilterType = [[op settings] objectForKey:@"filterType"]; + NSString *serFilterType = [serialized objectForKey:SerFilterExprType]; // this is optional + if(serFilterType && ![curFilterType isEqualToString:serFilterType]) { + SPLog(@"mismatch in filter types for operator %@: current=%@, serialized=%@",op,curFilterType,serFilterType); + goto fail; + } + + //now we have to create the argument node(s) + NSInteger numOfArgs = [[[op filter] objectForKey:@"NumberOfArguments"] integerValue]; + //fail if the current op requires more arguments than we have stored values for + NSArray *values = [serialized objectForKey:SerFilterExprValues]; + if(numOfArgs > [values count]) { + SPLog(@"filter operator %@ requires %ld arguments, but only have %ld stored values!",op,numOfArgs,[values count]); + goto fail; + } + + // otherwise add them + for (NSUInteger i = 0; i < numOfArgs; ++i) { + // insert connector node between args? + if(i > 0) { + ConnectorNode *node = [[ConnectorNode alloc] init]; + [node setFilter:[op filter]]; + [node setLabelIndex:(i-1)]; // label 0 follows argument 0 + [criteria addObject:node]; + [node release]; + } + ArgNode *arg = [[ArgNode alloc] init]; + [arg setArgIndex:i]; + [arg setFilter:[op filter]]; + [arg setInitialValue:[values objectAtIndex:i]]; + [criteria addObject:arg]; + [arg release]; + } + + [obj setObject:criteria forKey:@"criteria"]; + + //the last thing that remains is creating the displayValues for all criteria + NSMutableArray *displayValues = [NSMutableArray arrayWithCapacity:[criteria count]]; + for(id criterion in criteria) { + id dispValue = [self ruleEditor:filterRuleEditor displayValueForCriterion:criterion inRow:-1]; + if(!dispValue) { + SPLog(@"got nil displayValue for criterion %@ on deserialization!",criterion); + goto fail; + } + [displayValues addObject:dispValue]; + } + [obj setObject:displayValues forKey:@"displayValues"]; + } + + return [obj autorelease]; + +fail: + [obj release]; + return nil; +} + ++ (NSDictionary *)makeSerializedFilterForColumn:(NSString *)colName operator:(NSString *)opName values:(NSArray *)values +{ + return @{ + SerFilterClass: SerFilterClassExpression, + SerFilterExprColumn: colName, + SerFilterExprComparison: opName, + SerFilterExprValues: values, + }; +} + +- (ColumnNode *)_columnForName:(NSString *)name +{ + if([name length]) { + for (ColumnNode *col in columns) { + if ([name isEqualToString:[col name]]) return col; + } + } + return nil; +} + +- (OpNode *)_operatorNamed:(NSString *)title forColumn:(ColumnNode *)col +{ + if([title length]) { + // check if we have the operator cache, otherwise build it + [self _ensureValidOperatorCache:col]; + // try to find it in the operator cache + for(OpNode *node in [col operatorCache]) { + if([[[node filter] objectForKey:@"MenuLabel"] isEqualToString:title]) return node; + } + } + return nil; +} + +BOOL SerIsGroup(NSDictionary *dict) +{ + return [SerFilterClassGroup isEqual:[dict objectForKey:SerFilterClass]]; +} + +/** + * This method looks at the given serialized filter in a recursive manner and + * when it encounters + * - a group node with only a single child or + * - a child that is a group node of the same kind as the parent one + * it will pull the child(ren) up + * + * So for example: + * AND(expr1) => expr1 + * AND(expr1,AND(expr2,expr3)) => AND(expr1,expr2,expr3) + * + * The input dict is not modified, the returned dict will be equal to the input + * dict or have parts of it removed or replaced with new dicts. + */ ++ (NSDictionary *)_flattenSerializedFilter:(NSDictionary *)in +{ + // return non-group-nodes as is + if(!SerIsGroup(in)) return in; + + NSNumber *inIsConjunction = [in objectForKey:SerFilterGroupIsConjunction]; + + // first give all children the chance to flatten (depth first) + NSArray *children = [in objectForKey:SerFilterGroupChildren]; + NSMutableArray *flatChildren = [NSMutableArray arrayWithCapacity:[children count]]; + NSUInteger changed = 0; + for(NSDictionary *child in children) { + NSDictionary *flattened = [self _flattenSerializedFilter:child]; + //take a closer look at the (possibly changed) child - is it a group node of the same kind as us? + if(SerIsGroup(flattened) && [inIsConjunction isEqual:[flattened objectForKey:SerFilterGroupIsConjunction]]) { + [flatChildren addObjectsFromArray:[flattened objectForKey:SerFilterGroupChildren]]; + changed++; + } + else if(flattened != child) { + changed++; + } + [flatChildren addObject:flattened]; + } + // if there is only a single child, return it (flattening) + if([flatChildren count] == 1) return [flatChildren objectAtIndex:0]; + // if none of the children changed return the original input + if(!changed) return in; + // last variant: some of our children changed, but we remain + return @{ + SerFilterClass: SerFilterClassGroup, + SerFilterGroupIsConjunction: inIsConjunction, + SerFilterGroupChildren: flatChildren + }; +} + ++ (void)_writeFilterTree:(NSDictionary *)in toString:(NSMutableString *)out wrapInParenthesis:(BOOL)wrap binary:(BOOL)isBINARY error:(NSError **)err +{ + NSError *myErr = nil; + + if(wrap) [out appendString:@"("]; + + if(SerIsGroup(in)) { + BOOL isConjunction = [[in objectForKey:SerFilterGroupIsConjunction] boolValue]; + NSString *connector = isConjunction ? @"AND" : @"OR"; + BOOL first = YES; + NSArray *children = [in objectForKey:SerFilterGroupChildren]; + for(NSDictionary *child in children) { + if(!first) [out appendFormat:@" %@ ",connector]; + else first = NO; + // if the child is a group node but of a different kind we want to wrap it in order to prevent operator precedence confusion + // expression children will always be wrapped for clarity, except if there is only a single one and we are already wrapped + BOOL wrapChild = YES; + if(SerIsGroup(child)) { + BOOL childIsConjunction = [[child objectForKey:SerFilterGroupIsConjunction] boolValue]; + if(isConjunction == childIsConjunction) wrapChild = NO; + } + else { + if(wrap && [children count] == 1) wrapChild = NO; + } + [self _writeFilterTree:child toString:out wrapInParenthesis:wrapChild binary:isBINARY error:&myErr]; + if(myErr) { + if(err) *err = myErr; + return; + } + } + } + else { + // finally - build a SQL filter expression + NSDictionary *filter = [in objectForKey:SerFilterExprDefinition]; + if(!filter) { + if(err) *err = [NSError errorWithDomain:SPErrorDomain code:0 userInfo:@{ + NSLocalizedDescriptionKey: NSLocalizedString(@"Fatal error while retrieving content filter. No filter definition found.", @"filter to sql conversion : internal error : 0"), + }]; + return; + } + + if(![filter objectForKey:@"NumberOfArguments"]) { + if(err) *err = [NSError errorWithDomain:SPErrorDomain code:1 userInfo:@{ + NSLocalizedDescriptionKey: NSLocalizedString(@"Error while retrieving filter clause. No “NumberOfArguments” key found.", @"filter to sql conversion : internal error : invalid filter definition (1)"), + }]; + return; + } + + if(![filter objectForKey:@"Clause"] || ![(NSString *)[filter objectForKey:@"Clause"] length]) { + if(err) *err = [NSError errorWithDomain:SPErrorDomain code:2 userInfo:@{ + NSLocalizedDescriptionKey: NSLocalizedString(@"Content Filter clause is empty.", @"filter to sql conversion : internal error : invalid filter definition (2)"), + }]; + return; + } + + NSArray *values = [in objectForKey:SerFilterExprValues]; + + SPTableFilterParser *parser = [[SPTableFilterParser alloc] initWithFilterClause:[filter objectForKey:@"Clause"] + numberOfArguments:[[filter objectForKey:@"NumberOfArguments"] integerValue]]; + [parser setArgument:[values objectOrNilAtIndex:0]]; + [parser setFirstBetweenArgument:[values objectOrNilAtIndex:0]]; + [parser setSecondBetweenArgument:[values objectOrNilAtIndex:1]]; + [parser setSuppressLeadingTablePlaceholder:[[filter objectForKey:@"SuppressLeadingFieldPlaceholder"] boolValue]]; + [parser setCaseSensitive:isBINARY]; + [parser setCurrentField:[in objectForKey:SerFilterExprColumn]]; + + NSString *sql = [parser filterString]; + // SPTableFilterParser will return nil if it doesn't like the arguments and NSMutableString doesn't like nil + if(!sql) { + if(err) *err = [NSError errorWithDomain:SPErrorDomain code:3 userInfo:@{ + NSLocalizedDescriptionKey: NSLocalizedString(@"No valid SQL expression could be generated. Make sure that you have filled in all required fields.", @"filter to sql conversion : internal error : SPTableFilterParser failed"), + }]; + [parser release]; + return; + } + [out appendString:sql]; + + [parser release]; + } + + if(wrap) [out appendString:@")"]; +} + +@end + +#pragma mark - + +@implementation RuleNode + +@synthesize type = type; + +- (NSUInteger)hash { + return type; +} + +- (BOOL)isEqual:(id)other { + if (other == self) return YES; + if (other && [[other class] isEqual:[self class]] && [(RuleNode *)other type] == type) return YES; + + return NO; +} + +@end + +@implementation ColumnNode + +@synthesize name = name; +@synthesize typegrouping = typegrouping; +@synthesize operatorCache = operatorCache; +@synthesize opCacheVersion = opCacheVersion; + +- (instancetype)init +{ + if((self = [super init])) { + type = RuleNodeTypeColumn; + opCacheVersion = 0; + } + return self; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"ColumnNode<%@@%p>",[self name],self]; +} + +- (NSUInteger)hash { + return ([name hash] ^ [typegrouping hash] ^ [super hash]); +} + +- (BOOL)isEqual:(id)other { + if (other == self) return YES; + if (other && [[other class] isEqual:[self class]] && [name isEqualToString:[other name]] && [typegrouping isEqualToString:[other typegrouping]]) return YES; + + return NO; +} + +@end + + +@implementation StringNode + +@synthesize value = value; + +- (instancetype)init +{ + if((self = [super init])) { + type = RuleNodeTypeString; + } + return self; +} + +- (NSUInteger)hash { + return ([value hash] ^ [super hash]); +} + +- (BOOL)isEqual:(id)other { + if (other == self) return YES; + if (other && [[other class] isEqual:[self class]] && [value isEqualToString:[(StringNode *)other value]]) return YES; + + return NO; +} + +@end + +@implementation OpNode + +@synthesize parentColumn = parentColumn; +@synthesize settings = settings; +@synthesize filter = filter; + +- (instancetype)init +{ + if((self = [super init])) { + type = RuleNodeTypeOperator; + } + return self; +} + +- (void)dealloc +{ + [self setFilter:nil]; + [self setSettings:nil]; + [super dealloc]; +} + +- (NSUInteger)hash { + return (([parentColumn hash] << 16) ^ [settings hash] ^ [super hash]); +} + +- (BOOL)isEqual:(id)other { + if (other == self) return YES; + if (other && [[other class] isEqual:[self class]] && [settings isEqualToDictionary:[(OpNode *)other settings]] && [parentColumn isEqual:[other parentColumn]]) return YES; + + return NO; +} + +@end + +@implementation ArgNode + +@synthesize filter = filter; +@synthesize argIndex = argIndex; +@synthesize initialValue = initialValue; + +- (instancetype)init +{ + if((self = [super init])) { + type = RuleNodeTypeArgument; + } + return self; +} + +- (void)dealloc +{ + [self setInitialValue:nil]; + [self setFilter:nil]; + [super dealloc]; +} + +- (NSUInteger)hash { + // initialValue does not count towards hash because two Args are not different if only the initialValue differs + return ((argIndex << 16) ^ [filter hash] ^ [super hash]); +} + +- (BOOL)isEqual:(id)other { + // initialValue does not count towards isEqual: because two Args are not different if only the initialValue differs + if (other == self) return YES; + if (other && [[other class] isEqual:[self class]] && [filter isEqualToDictionary:[(ArgNode *)other filter]] && argIndex == [(ArgNode *)other argIndex]) return YES; + + return NO; +} + +@end + +@implementation ConnectorNode + +@synthesize filter = filter; +@synthesize labelIndex = labelIndex; + +- (instancetype)init +{ + if((self = [super init])) { + type = RuleNodeTypeConnector; + } + return self; +} + +- (void)dealloc +{ + [self setFilter:nil]; + [super dealloc]; +} + +- (NSUInteger)hash { + return ((labelIndex << 16) ^ [filter hash] ^ [super hash]); +} + +- (BOOL)isEqual:(id)other { + if (other == self) return YES; + if (other && [[other class] isEqual:[self class]] && [filter isEqualToDictionary:[(ConnectorNode *)other filter]] && labelIndex == [(ConnectorNode *)other labelIndex]) return YES; + + return NO; +} + +@end + +#pragma mark - + +@implementation ModelContainer + +@synthesize model = model; + +- (instancetype)init +{ + if (self = [super init]) { + model = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void)dealloc +{ + [self setModel:nil]; + [super dealloc]; +} + +// KVO + +- (void)insertObject:(id)obj inModelAtIndex:(NSUInteger)idx +{ + [model insertObject:obj atIndex:idx]; +} + +- (void)removeObjectFromModelAtIndex:(NSUInteger)idx +{ + [model removeObjectAtIndex:idx]; +} + +- (void)insertModel:(NSArray *)array atIndexes:(NSIndexSet *)indexes +{ + [model insertObjects:array atIndexes:indexes]; +} + +- (void)removeModelAtIndexes:(NSIndexSet *)indexes +{ + [model removeObjectsAtIndexes:indexes]; +} + +@end diff --git a/Source/SPTableContent.h b/Source/SPTableContent.h index 4c6627de..039fd099 100644 --- a/Source/SPTableContent.h +++ b/Source/SPTableContent.h @@ -43,21 +43,26 @@ @class SPDatabaseDocument; @class SPTablesList; @class SPTableStructure; -@class SPTableList; -@class SPContentFilterManager; -#ifndef SP_CODA -@class SPSplitView; -#endif -@class SPTableContentFilterController; +@class SPRuleFilterController; +@class SPFilterTableController; + +@class ContentPaginationViewController; //private + +typedef NS_ENUM(NSInteger, SPTableContentFilterSource) { + SPTableContentFilterSourceNone = -1, + SPTableContentFilterSourceRuleFilter = 0, + SPTableContentFilterSourceTableFilter = 1, + SPTableContentFilterSourceURLScheme = 2, +}; #import "SPDatabaseContentViewDelegate.h" @interface SPTableContent : NSObject <NSTableViewDelegate, NSTableViewDataSource, NSComboBoxDataSource, NSComboBoxDelegate, SPDatabaseContentViewDelegate> { IBOutlet SPDatabaseDocument *tableDocumentInstance; - IBOutlet id tablesListInstance; + IBOutlet SPTablesList *tablesListInstance; IBOutlet SPTableData* tableDataInstance; - IBOutlet id tableSourceInstance; + IBOutlet SPTableStructure *tableSourceInstance; #ifndef SP_CODA IBOutlet SPTableInfo *tableInfoInstance; @@ -65,95 +70,66 @@ #endif IBOutlet SPCopyTable *tableContentView; - IBOutlet NSPopUpButton *fieldField; - IBOutlet id compareField; - IBOutlet id argumentField; - IBOutlet id filterButton; - IBOutlet id addButton; - IBOutlet id duplicateButton; - IBOutlet id removeButton; - IBOutlet id reloadButton; + + IBOutlet NSButton *toggleRuleFilterButton; + IBOutlet NSButton *addButton; + IBOutlet NSButton *duplicateButton; + IBOutlet NSButton *removeButton; + IBOutlet NSButton *reloadButton; #ifndef SP_CODA IBOutlet NSButton *multipleLineEditingButton; - IBOutlet id countText; - IBOutlet id limitRowsField; - IBOutlet id limitRowsButton; - IBOutlet id limitRowsStepper; + IBOutlet NSTextField *countText; #endif - IBOutlet id firstBetweenField; - IBOutlet id secondBetweenField; - IBOutlet id betweenTextField; IBOutlet NSButton *paginationPreviousButton; #ifndef SP_CODA IBOutlet NSButton *paginationButton; - IBOutlet NSButton *paginationGoButton; #endif IBOutlet NSButton *paginationNextButton; #ifndef SP_CODA IBOutlet NSView *contentViewPane; - IBOutlet NSViewController *paginationViewController; + ContentPaginationViewController *paginationViewController; + NSPopover *paginationPopover; IBOutlet NSView *paginationView; IBOutlet NSBox *paginationBox; - NSPopover *paginationPopover; -#endif - IBOutlet NSTextField *paginationPageField; -#ifndef SP_CODA - IBOutlet NSStepper *paginationPageStepper; - - IBOutlet SPCopyTable *filterTableView; - IBOutlet NSPanel *filterTableWindow; - IBOutlet SPSplitView *filterTableSplitView; - IBOutlet NSTextField *filterTableQueryTitle; - IBOutlet NSButton *filterTableFilterButton; - IBOutlet NSButton *filterTableClearButton; - IBOutlet SPTextView *filterTableWhereClause; - IBOutlet NSButton *filterTableNegateCheckbox; - IBOutlet NSButton *filterTableDistinctCheckbox; - IBOutlet NSButton *filterTableLiveSearchCheckbox; - IBOutlet NSButton *filterTableSearchAllFields; - IBOutlet NSPanel *filterTableSetDefaultOperatorSheet; - IBOutlet NSComboBox* filterTableSetDefaultOperatorValue; - - // Temporary to avoid nib conflicts during WIP - IBOutlet SPSplitView *contentSplitView; - - IBOutlet SPTableContentFilterController *filterControllerInstance; + + IBOutlet SPRuleFilterController *ruleFilterController; + IBOutlet SPFilterTableController *filterTableController; + BOOL scrollViewHasRubberbandScrolling; #endif SPMySQLConnection *mySQLConnection; BOOL _mainNibLoaded; BOOL isWorking; pthread_mutex_t tableValuesLock; -#ifndef SP_CODA - NSMutableArray *nibObjectsToRelease; -#endif - NSString *selectedTable, *usedQuery; + NSString *selectedTable; + NSString *usedQuery; SPDataStorage *tableValues; - NSMutableArray *dataColumns, *keys, *oldRow; - NSUInteger tableRowsCount, previousTableRowsCount; - NSString *compareType; + NSMutableArray *dataColumns; + NSMutableArray *keys; + NSMutableArray *oldRow; + NSUInteger tableRowsCount; + NSUInteger previousTableRowsCount; NSNumber *sortCol; - BOOL isEditingRow, isEditingNewRow, isSavingRow, isDesc, setLimit; - BOOL isFiltered, isLimited, isInterruptedLoad, maxNumRowsIsEstimate; + BOOL isEditingRow; + BOOL isEditingNewRow; + BOOL isSavingRow; + BOOL isDesc; + BOOL setLimit; + BOOL isFiltered; + BOOL isLimited; + BOOL isInterruptedLoad; + BOOL maxNumRowsIsEstimate; NSUserDefaults *prefs; - NSInteger currentlyEditingRow, maxNumRows; + NSInteger currentlyEditingRow; + NSInteger maxNumRows; - NSMutableDictionary *contentFilters; - NSMutableDictionary *numberOfDefaultFilters; - NSUInteger lastSelectedContentFilterIndex; - SPContentFilterManager *contentFilterManager; NSUInteger contentPage; #ifndef SP_CODA - NSMutableDictionary *filterTableData; - BOOL filterTableNegate; - BOOL filterTableDistinct; - BOOL filterTableIsSwapped; - NSString *filterTableDefaultOperator; - NSString *lastEditedFilterTableValue; - NSInteger activeFilter; // 0 = default filter; 1 = filter table; 2 = sequelpro url scheme + SPTableContentFilterSource activeFilter; + SPTableContentFilterSource activeFilterToRestore; NSString *schemeFilter; #endif @@ -163,17 +139,18 @@ NSUInteger pageToRestore; NSDictionary *selectionToRestore; NSRect selectionViewportToRestore; - NSString *filterFieldToRestore, *filterComparisonToRestore, *filterValueToRestore, *firstBetweenValueToRestore, *secondBetweenValueToRestore; #ifndef SP_CODA NSInteger paginationViewHeight; #endif NSTimer *tableLoadTimer; - NSUInteger tableLoadInterfaceUpdateInterval, tableLoadTimerTicksSinceLastUpdate, tableLoadLastRowCount, tableLoadTargetRowCount; + NSUInteger tableLoadInterfaceUpdateInterval; + NSUInteger tableLoadTimerTicksSinceLastUpdate; + NSUInteger tableLoadLastRowCount; + NSUInteger tableLoadTargetRowCount; NSArray *cqColumnDefinition; - NSString *fieldIDQueryString; BOOL isFirstChangeInView; NSString *kCellEditorErrorNoMatch; @@ -186,24 +163,25 @@ NSColor *whiteColor; SPFieldEditorController *fieldEditor; - NSRange fieldEditorSelectedRange; + + // this represents the visible area of the whole content view at runtime. + // we use it as a positioning aide for the other two views below + IBOutlet NSView *contentAreaContainer; + IBOutlet NSView *filterRuleEditorContainer; + IBOutlet NSView *tableContentContainer; + + BOOL showFilterRuleEditor; + + NSDictionary *filtersToRestore; } #ifdef SP_CODA /* glue */ -@property (assign) id filterButton; -@property (assign) id fieldField; -@property (assign) id compareField; -@property (assign) id betweenTextField; -@property (assign) id firstBetweenField; -@property (assign) id secondBetweenField; -@property (assign) id argumentField; @property (assign) NSButton* addButton; @property (assign) NSButton* duplicateButton; @property (assign) NSButton* removeButton; @property (assign) NSButton* reloadButton; @property (assign) NSButton* paginationNextButton; @property (assign) NSButton* paginationPreviousButton; -@property (assign) NSTextField* paginationPageField; @property (assign) SPDatabaseDocument* tableDocumentInstance; @property (assign) SPTablesList* tablesListInstance; @property (assign) SPCopyTable* tableContentView; @@ -229,9 +207,10 @@ - (IBAction)reloadTable:(id)sender; - (void)reloadTableTask; - (IBAction)filterTable:(id)sender; +- (IBAction)toggleRuleEditorVisible:(id)sender; - (void)filterTableTask; -- (IBAction)toggleFilterField:(id)sender; - (void)setUsedQuery:(NSString *)query; +- (NSString *)selectedTable; // Pagination - (IBAction)navigatePaginationFromButton:(id)sender; @@ -245,17 +224,10 @@ - (IBAction)addRow:(id)sender; - (IBAction)duplicateRow:(id)sender; - (IBAction)removeRow:(id)sender; -- (void)removeRowSheetDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo; +- (void)removeRowSheetDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo; // Filter Table -- (IBAction)tableFilterClear:(id)sender; - (IBAction)showFilterTable:(id)sender; -- (IBAction)toggleNegateClause:(id)sender; -- (IBAction)toggleDistinctSelect:(id)sender; -- (IBAction)setDefaultOperator:(id)sender; -- (IBAction)toggleLookAllFieldsMode:(id)sender; -- (IBAction)closeSheet:(id)sender; -- (IBAction)showDefaultOperaterHelp:(id)sender; // Data accessors - (NSArray *)currentResult; @@ -269,7 +241,6 @@ - (void)setConnection:(SPMySQLConnection *)theConnection; - (void)clickLinkArrow:(SPTextAndLinkCell *)theArrowCell; - (void)clickLinkArrowTask:(SPTextAndLinkCell *)theArrowCell; -- (IBAction)setCompareTypes:(id)sender; - (void)updateResultStore:(SPMySQLStreamingResultStore *)theResultStore approximateRowCount:(NSUInteger)targetRowCount; - (BOOL)saveRowToTable; - (void) addRowErrorSheetDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo; @@ -301,11 +272,12 @@ - (void)setFiltersToRestore:(NSDictionary *)filterSettings; - (void)storeCurrentDetailsForRestoration; - (void)clearDetailsToRestore; +- (void)setActiveFilterToRestore:(SPTableContentFilterSource)filter; +- (SPTableContentFilterSource)activeFilter; - (void)setFilterTableData:(NSData *)arcData; - (NSData *)filterTableData; //- (NSString *)escapeFilterArgument:(NSString *)argument againstClause:(NSString *)clause; -- (void)openContentFilterManager; - (NSArray *)fieldEditStatusForRow:(NSInteger)rowIndex andColumn:(NSInteger)columnIndex; @@ -316,7 +288,5 @@ #pragma mark - SPTableContentFilter - (void)makeContentFilterHaveFocus; -- (void)updateFilterTableClause:(id)currentValue; -- (NSString*)escapeFilterTableDefaultOperator:(NSString*)op; @end diff --git a/Source/SPTableContent.m b/Source/SPTableContent.m index b29ebf7a..9ac8fd61 100644 --- a/Source/SPTableContent.m +++ b/Source/SPTableContent.m @@ -40,13 +40,9 @@ #import "SPTableData.h" #import "SPQueryController.h" #import "SPTextAndLinkCell.h" -#ifndef SP_CODA -#import "SPSplitView.h" -#endif #import "SPFieldEditorController.h" #import "SPTooltip.h" #import "RegexKitLite.h" -#import "SPContentFilterManager.h" #import "SPDataStorage.h" #import "SPAlertSheets.h" #import "SPHistoryController.h" @@ -60,20 +56,60 @@ #import "SPThreadAdditions.h" #import "SPTableFilterParser.h" #import "SPFunctions.h" +#import "SPRuleFilterController.h" +#import "SPFilterTableController.h" #import <pthread.h> #import <SPMySQL/SPMySQL.h> #include <stdlib.h> -#ifndef SP_CODA -static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOperator"; -#endif +/** + * This is the unique KVO context of code that resides in THIS class. + * Do not try to give it to other classes, ESPECIALLY NOT child classes! + */ +static void *TableContentKVOContext = &TableContentKVOContext; + +/** + * TODO: + * This class is a temporary workaround, because before SPTableContent was both a child class in one xib + * and an owner class in another xib, which is bad style and causes other complications. + */ +@interface ContentPaginationViewController : NSViewController +{ + id target; + SEL action; + + NSNumber *page; + NSNumber *maxPage; + + IBOutlet NSButton *paginationGoButton; + IBOutlet NSTextField *paginationPageField; + IBOutlet NSStepper *paginationPageStepper; +} +- (IBAction)paginationGoAction:(id)sender; +- (void)makeInputFirstResponder; +- (BOOL)isFirstResponderInside; + +@property (assign, nonatomic) id target; +@property (assign, nonatomic) SEL action; + +// IB Bindings +@property (copy, nonatomic) NSNumber *page; +@property (copy, nonatomic) NSNumber *maxPage; + +@end @interface SPTableContent () - (BOOL)cancelRowEditing; - (void)documentWillClose:(NSNotification *)notification; -- (void)contentFiltersHaveBeenUpdated:(NSNotification *)notification; + +- (void)updateFilterRuleEditorSize:(CGFloat)requestedHeight animate:(BOOL)animate; +- (void)filterRuleEditorPreferredSizeChanged:(NSNotification *)notification; +- (void)contentViewSizeChanged:(NSNotification *)notification; +- (void)setRuleEditorVisible:(BOOL)show animate:(BOOL)animate; + +- (void)_setViewBlankState; #pragma mark - SPTableContentDataSource_Private_API @@ -85,19 +121,11 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper #ifdef SP_CODA @synthesize addButton; -@synthesize argumentField; -@synthesize betweenTextField; -@synthesize compareField; @synthesize duplicateButton; -@synthesize fieldField; -@synthesize filterButton; -@synthesize firstBetweenField; @synthesize paginationNextButton; -@synthesize paginationPageField; @synthesize paginationPreviousButton; @synthesize reloadButton; @synthesize removeButton; -@synthesize secondBetweenField; @synthesize tableContentView; @synthesize tableDataInstance; @synthesize tableDocumentInstance; @@ -114,27 +142,18 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper isWorking = NO; pthread_mutex_init(&tableValuesLock, NULL); -#ifndef SP_CODA - nibObjectsToRelease = [[NSMutableArray alloc] init]; -#endif tableValues = [[SPDataStorage alloc] init]; dataColumns = [[NSMutableArray alloc] init]; oldRow = [[NSMutableArray alloc] init]; -#ifndef SP_CODA - filterTableData = [[NSMutableDictionary alloc] initWithCapacity:1]; -#endif tableRowsCount = 0; previousTableRowsCount = 0; #ifndef SP_CODA - filterTableNegate = NO; - filterTableDistinct = NO; - filterTableIsSwapped = NO; - lastEditedFilterTableValue = nil; - activeFilter = 0; + activeFilter = SPTableContentFilterSourceNone; schemeFilter = nil; + paginationViewController = [[ContentPaginationViewController alloc] init]; // the view itself is lazily loaded paginationPopover = nil; #endif @@ -151,15 +170,13 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper pageToRestore = 1; selectionToRestore = nil; selectionViewportToRestore = NSZeroRect; - filterFieldToRestore = nil; - filterComparisonToRestore = nil; - filterValueToRestore = nil; - firstBetweenValueToRestore = nil; - secondBetweenValueToRestore = nil; + filtersToRestore = nil; + activeFilterToRestore = SPTableContentFilterSourceNone; tableRowsSelectable = YES; - contentFilterManager = nil; isFirstChangeInView = YES; + showFilterRuleEditor = NO; + isFiltered = NO; isLimited = NO; isInterruptedLoad = NO; @@ -175,38 +192,6 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper blueColor = [NSColor blueColor]; whiteColor = [NSColor whiteColor]; - // Init default filters for Content Browser - contentFilters = [[NSMutableDictionary alloc] init]; - numberOfDefaultFilters = [[NSMutableDictionary alloc] init]; - - NSError *readError = nil; - NSString *filePath = [NSBundle pathForResource:@"ContentFilters.plist" ofType:nil inDirectory:[[NSBundle mainBundle] bundlePath]]; - NSData *defaultFilterData = [NSData dataWithContentsOfFile:filePath - options:NSMappedRead - error:&readError]; - - if(defaultFilterData && !readError) { - NSDictionary *defaultFilterDict = [NSPropertyListSerialization propertyListWithData:defaultFilterData - options:NSPropertyListMutableContainersAndLeaves - format:NULL - error:&readError]; - - if(defaultFilterDict && !readError) { - [contentFilters setDictionary:defaultFilterDict]; - } - } - - if (readError) { - NSLog(@"Error while reading 'ContentFilters.plist':\n%@", readError); - NSBeep(); - } - else { - [numberOfDefaultFilters setObject:[NSNumber numberWithInteger:[[contentFilters objectForKey:@"number"] count]] forKey:@"number"]; - [numberOfDefaultFilters setObject:[NSNumber numberWithInteger:[[contentFilters objectForKey:@"date"] count]] forKey:@"date"]; - [numberOfDefaultFilters setObject:[NSNumber numberWithInteger:[[contentFilters objectForKey:@"string"] count]] forKey:@"string"]; - [numberOfDefaultFilters setObject:[NSNumber numberWithInteger:[[contentFilters objectForKey:@"spatial"] count]] forKey:@"spatial"]; - } - kCellEditorErrorNoMatch = NSLocalizedString(@"Field is not editable. No matching record found.\nReload table, check the encoding, or try to add\na primary key field or more fields\nin the view declaration of '%@' to identify\nfield origin unambiguously.", @"Table Content result editing error - could not identify original row"); kCellEditorErrorNoMultiTabDb = NSLocalizedString(@"Field is not editable. Field has no or multiple table or database origin(s).",@"field is not editable due to no table/database"); kCellEditorErrorTooManyMatches = NSLocalizedString(@"Field is not editable. Couldn't identify field origin unambiguously (%ld matches).", @"Query result editing error - could not match row being edited uniquely"); @@ -221,30 +206,19 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper _mainNibLoaded = YES; #ifndef SP_CODA /* ui manipulation */ - // Temporary to avoid nib conflicts during WIP - [contentSplitView setCollapsibleSubviewIndex:0]; - [contentSplitView setCollapsibleSubviewCollapsed:YES animate:NO]; - [contentSplitView setMaxSize:0.f ofSubviewAtIndex:0]; + + // initially hide the filter rule editor + [self updateFilterRuleEditorSize:0.0 animate:NO]; // Set the table content view's vertical gridlines if required [tableContentView setGridStyleMask:([prefs boolForKey:SPDisplayTableViewVerticalGridlines]) ? NSTableViewSolidVerticalGridLineMask : NSTableViewGridNone]; - [filterTableView setGridStyleMask:([prefs boolForKey:SPDisplayTableViewVerticalGridlines]) ? NSTableViewSolidVerticalGridLineMask : NSTableViewGridNone]; // Set the double-click action in blank areas of the table to create new rows [tableContentView setEmptyDoubleClickAction:@selector(addRow:)]; - // Load the pagination view, keeping references to the top-level objects for later release - NSArray *paginationViewTopLevelObjects = nil; - NSNib *nibLoader = [[NSNib alloc] initWithNibNamed:@"ContentPaginationView" bundle:[NSBundle mainBundle]]; - - if (![nibLoader instantiateNibWithOwner:self topLevelObjects:&paginationViewTopLevelObjects]) { - NSLog(@"Content pagination nib could not be loaded; pagination will not function correctly."); - } - else { - [nibObjectsToRelease addObjectsFromArray:paginationViewTopLevelObjects]; - } - - [nibLoader release]; + [paginationViewController setTarget:self]; + [paginationViewController setAction:@selector(navigatePaginationFromButton:)]; + [paginationViewController view]; // make sure the nib is actually loaded //let's see if we can use the NSPopover (10.7+) or have to make do with our legacy clone. //this is using reflection right now, as our SDK is 10.8 but our minimum supported version is 10.6 @@ -266,44 +240,47 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper paginationViewFrame.origin.y = paginationButtonFrame.origin.y + paginationButtonFrame.size.height - 2; paginationViewFrame.size.height = 0; [paginationView setFrame:paginationViewFrame]; - [contentViewPane addSubview:paginationView]; + [[paginationButton superview] addSubview:paginationView]; } - - // Modify the filter table split view sizes - [filterTableSplitView setMinSize:135 ofSubviewAtIndex:1]; #endif [tableContentView setFieldEditorSelectedRange:NSMakeRange(0,0)]; -#ifndef SP_CODA - // Init Filter Table GUI - [filterTableDistinctCheckbox setState:(filterTableDistinct) ? NSOnState : NSOffState]; - [filterTableNegateCheckbox setState:(filterTableNegate) ? NSOnState : NSOffState]; - [filterTableLiveSearchCheckbox setState:NSOffState]; -#endif -#ifndef SP_CODA /* patch */ - filterTableDefaultOperator = [[self escapeFilterTableDefaultOperator:[prefs objectForKey:SPFilterTableDefaultOperator]] retain]; -#else -// filterTableDefaultOperator = [[self escapeFilterTableDefaultOperator:nil] retain]; -#endif + [prefs addObserver:self forKeyPath:SPDisplayTableViewVerticalGridlines options:NSKeyValueObservingOptionNew context:TableContentKVOContext]; + [prefs addObserver:self forKeyPath:SPGlobalResultTableFont options:NSKeyValueObservingOptionNew context:TableContentKVOContext]; + [prefs addObserver:self forKeyPath:SPDisplayBinaryDataAsHex options:NSKeyValueObservingOptionNew context:TableContentKVOContext]; + + // Add observer to change view sizes with filter rule editor + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(filterRuleEditorPreferredSizeChanged:) + name:SPRuleFilterHeightChangedNotification + object:ruleFilterController]; + [contentAreaContainer setPostsFrameChangedNotifications:YES]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(contentViewSizeChanged:) + name:NSViewFrameDidChangeNotification + object:contentAreaContainer]; + [ruleFilterController setTarget:self]; + [ruleFilterController setAction:@selector(filterTable:)]; + + [filterTableController setTarget:self]; + [filterTableController setAction:@selector(filterTable:)]; + //TODO This is only needed for 10.6 compatibility + scrollViewHasRubberbandScrolling = [[[ruleFilterController view] enclosingScrollView] respondsToSelector:@selector(setVerticalScrollElasticity:)]; // Add observers for document task activity [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(startDocumentTaskForTab:) - name:SPDocumentTaskStartNotification - object:tableDocumentInstance]; + selector:@selector(startDocumentTaskForTab:) + name:SPDocumentTaskStartNotification + object:tableDocumentInstance]; [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(endDocumentTaskForTab:) - name:SPDocumentTaskEndNotification - object:tableDocumentInstance]; + selector:@selector(endDocumentTaskForTab:) + name:SPDocumentTaskEndNotification + object:tableDocumentInstance]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(documentWillClose:) name:SPDocumentWillCloseNotification object:tableDocumentInstance]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(contentFiltersHaveBeenUpdated:) - name:SPContentFiltersHaveBeenUpdatedNotification - object:nil]; } #pragma mark - @@ -374,6 +351,60 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper } /** + * This configures the table content view in the way it should look like when no valid table is selected + */ +- (void)_setViewBlankState +{ + // Remove existing columns from the table + while ([[tableContentView tableColumns] count]) { + [NSArrayObjectAtIndex([tableContentView tableColumns], 0) setHeaderToolTip:nil]; // prevent crash #2414 + [tableContentView removeTableColumn:NSArrayObjectAtIndex([tableContentView tableColumns], 0)]; + } + + // Empty the stored data arrays, including emptying the tableValues array + // by ressignment for thread safety. + previousTableRowsCount = 0; + [self clearTableValues]; + [tableContentView reloadData]; + isFiltered = NO; + isLimited = NO; +#ifndef SP_CODA + [countText setStringValue:@""]; +#endif + + // Reset sort column + if (sortCol) SPClear(sortCol); + isDesc = NO; + + // Empty and disable filter options + [self setRuleEditorVisible:NO animate:NO]; + [toggleRuleFilterButton setEnabled:NO]; + [toggleRuleFilterButton setState:NSOffState]; + [ruleFilterController setColumns:nil]; + + // Disable pagination + [paginationPreviousButton setEnabled:NO]; +#ifndef SP_CODA + [paginationButton setEnabled:NO]; + [paginationButton setTitle:@""]; +#endif + [paginationNextButton setEnabled:NO]; + + // Disable table action buttons + [addButton setEnabled:NO]; + [duplicateButton setEnabled:NO]; + [removeButton setEnabled:NO]; + + // Clear restoration settings + [self clearDetailsToRestore]; + +#ifndef SP_CODA + [filterTableController setColumns:nil]; + activeFilter = SPTableContentFilterSourceNone; +#endif +} + +/** * Update stored table details and update the interface to match the supplied * table details. * Should be called on the main thread. @@ -387,10 +418,8 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper #endif NSArray *columnNames; NSMutableDictionary *preservedColumnWidths = nil; - NSTableColumn *theCol; -#ifndef SP_CODA - NSTableColumn *filterCol; -#endif + NSTableColumn *theCol; + BOOL enableInteraction = #ifndef SP_CODA /* checking toolbar state */ ![[tableDocumentInstance selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarTableContent] || @@ -432,7 +461,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper if (newTableName) selectedTable = [[NSString alloc] initWithString:newTableName]; previousTableRowsCount = 0; contentPage = 1; - [paginationPageField setStringValue:@"1"]; + [paginationViewController setPage:@1]; // Clear the selection [tableContentView deselectAll:self]; @@ -453,75 +482,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper // If no table has been supplied, reset the view to a blank table and disabled elements. if (!newTableName) { - // Remove existing columns from the table - while ([[tableContentView tableColumns] count]) { - [NSArrayObjectAtIndex([tableContentView tableColumns], 0) setHeaderToolTip:nil]; // prevent crash #2414 - [tableContentView removeTableColumn:NSArrayObjectAtIndex([tableContentView tableColumns], 0)]; - } - - // Empty the stored data arrays, including emptying the tableValues array - // by ressignment for thread safety. - previousTableRowsCount = 0; - [self clearTableValues]; - [tableContentView reloadData]; - isFiltered = NO; - isLimited = NO; -#ifndef SP_CODA - [countText setStringValue:@""]; -#endif - - // Reset sort column - if (sortCol) SPClear(sortCol); - isDesc = NO; - - // Empty and disable filter options - [fieldField setEnabled:NO]; - [fieldField removeAllItems]; - [fieldField addItemWithTitle:NSLocalizedString(@"field", @"popup menuitem for field (showing only if disabled)")]; - [compareField setEnabled:NO]; - [compareField removeAllItems]; - [compareField addItemWithTitle:@"="]; - [argumentField setHidden:NO]; - [argumentField setEnabled:NO]; - [firstBetweenField setEnabled:NO]; - [secondBetweenField setEnabled:NO]; - [firstBetweenField setStringValue:@""]; - [secondBetweenField setStringValue:@""]; - [argumentField setStringValue:@""]; - [filterButton setEnabled:NO]; - - // Hide BETWEEN operator controls - [firstBetweenField setHidden:YES]; - [secondBetweenField setHidden:YES]; - [betweenTextField setHidden:YES]; - - // Disable pagination - [paginationPreviousButton setEnabled:NO]; -#ifndef SP_CODA - [paginationButton setEnabled:NO]; - [paginationButton setTitle:@""]; -#endif - [paginationNextButton setEnabled:NO]; - - // Disable table action buttons - [addButton setEnabled:NO]; - [duplicateButton setEnabled:NO]; - [removeButton setEnabled:NO]; - - // Clear restoration settings - [self clearDetailsToRestore]; - -#ifndef SP_CODA - // Clear filter table - while ([[filterTableView tableColumns] count]) { - [NSArrayObjectAtIndex([filterTableView tableColumns], 0) setHeaderToolTip:nil]; // prevent crash #2414 - [filterTableView removeTableColumn:NSArrayObjectAtIndex([filterTableView tableColumns], 0)]; - } - // Clear filter table data - [filterTableData removeAllObjects]; - [filterTableWhereClause setString:@""]; - activeFilter = 0; -#endif + [self _setViewBlankState]; return; } @@ -534,15 +495,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper } #ifndef SP_CODA // Remove existing columns from the filter table - [filterTableView abortEditing]; - while ([[filterTableView tableColumns] count]) { - [NSArrayObjectAtIndex([filterTableView tableColumns], 0) setHeaderToolTip:nil]; // prevent crash #2414 - [filterTableView removeTableColumn:NSArrayObjectAtIndex([filterTableView tableColumns], 0)]; - } - // Clear filter table data - [filterTableData removeAllObjects]; - [filterTableWhereClause setString:@""]; - activeFilter = 0; + [filterTableController setColumns:nil]; #endif // Retrieve the field names and types for this table from the data cache. This is used when requesting all data as part @@ -578,7 +531,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper #endif [tableContentView setRowHeight:2.0f+NSSizeToCGSize([@"{ǞṶḹÜ∑zgyf" sizeWithAttributes:@{NSFontAttributeName : tableFont}]).height]; - // Add the new columns to the table and filterTable + // Add the new columns to the table for (NSDictionary *columnDefinition in dataColumns ) { // Set up the column @@ -599,24 +552,6 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper [theCol setEditable:YES]; -#ifndef SP_CODA - // Set up column for filterTable - filterCol = [[NSTableColumn alloc] initWithIdentifier:[columnDefinition objectForKey:@"datacolumnindex"]]; - [[filterCol headerCell] setStringValue:[columnDefinition objectForKey:@"name"]]; - [filterCol setEditable:YES]; - SPTextAndLinkCell *filterDataCell = [[[SPTextAndLinkCell alloc] initTextCell:@""] autorelease]; - [filterDataCell setEditable:YES]; - [filterCol setDataCell:filterDataCell]; - [filterTableView addTableColumn:filterCol]; - [filterCol release]; - - [filterTableData setObject:[NSMutableDictionary dictionaryWithObjectsAndKeys: - [columnDefinition objectForKey:@"name"], @"name", - [columnDefinition objectForKey:@"typegrouping"], @"typegrouping", - [NSMutableArray arrayWithObjects:@"", @"", @"", @"", @"", @"", @"", @"", @"", @"", nil], SPTableContentFilterKey, - nil] forKey:[columnDefinition objectForKey:@"datacolumnindex"]]; -#endif - // Set up the data cell depending on the column type id dataCell; if ([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"enum"]) { @@ -686,12 +621,6 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper [theCol release]; } -#ifndef SP_CODA - [filterTableView setDelegate:self]; - [filterTableView setDataSource:self]; - [filterTableView reloadData]; -#endif - // If the table has been reloaded and the previously selected sort column is still present, reselect it. if (sortColumnNumberToRestore != NSNotFound) { theCol = [tableContentView tableColumnWithIdentifier:[NSString stringWithFormat:@"%lld", (long long)sortColumnNumberToRestore]]; @@ -721,42 +650,27 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper currentFirstResponder = [currentFirstResponder delegate]; } + [filterTableController setColumns:dataColumns]; // Enable and initialize filter fields (with tags for position of menu item and field position) - [fieldField setEnabled:YES]; - [fieldField removeAllItems]; - NSArray *columnTitles = ([prefs boolForKey:SPAlphabeticalTableSorting])? [columnNames sortedArrayUsingSelector:@selector(compare:)] : columnNames; - [fieldField addItemsWithTitles:columnTitles]; - [compareField setEnabled:YES]; - [self setCompareTypes:self]; - [argumentField setEnabled:YES]; - [argumentField setStringValue:@""]; - [filterButton setEnabled:enableInteraction]; - + [ruleFilterController setColumns:dataColumns]; // Restore preserved filter settings if appropriate and valid - if (filterFieldToRestore) { - [fieldField selectItemWithTitle:filterFieldToRestore]; - [self setCompareTypes:self]; - - if ([fieldField itemWithTitle:filterFieldToRestore] - && ((!filterComparisonToRestore && filterValueToRestore) - || (filterComparisonToRestore && [compareField itemWithTitle:filterComparisonToRestore]))) - { - if (filterComparisonToRestore) [compareField selectItemWithTitle:filterComparisonToRestore]; - if([filterComparisonToRestore isEqualToString:@"BETWEEN"]) { - [argumentField setHidden:YES]; - if (firstBetweenValueToRestore) [firstBetweenField setStringValue:firstBetweenValueToRestore]; - if (secondBetweenValueToRestore) [secondBetweenField setStringValue:secondBetweenValueToRestore]; - } else { - if (filterValueToRestore) [argumentField setStringValue:filterValueToRestore]; - } - [self toggleFilterField:self]; - - } + [ruleFilterController restoreSerializedFilters:filtersToRestore]; + // hide/show the rule filter editor, based on its previous state (so that it says visible when switching tables, if someone has enabled it and vice versa) + if(showFilterRuleEditor) { + [self setRuleEditorVisible:YES animate:NO]; + [toggleRuleFilterButton setState:NSOnState]; + } + else { + [self setRuleEditorVisible:NO animate:NO]; + [toggleRuleFilterButton setState:NSOffState]; } + [ruleFilterController setEnabled:enableInteraction]; + [toggleRuleFilterButton setEnabled:enableInteraction]; + // restore the filter to the previously choosen one for the table + activeFilter = activeFilterToRestore; // Restore page number if limiting is set - if ([prefs boolForKey:SPLimitResults]) - contentPage = pageToRestore; + if ([prefs boolForKey:SPLimitResults]) contentPage = pageToRestore; // Restore first responder [[tableDocumentInstance parentWindow] makeFirstResponder:currentFirstResponder]; @@ -771,10 +685,11 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper if (!previousTableRowsCount) { [self clearTableValues]; } -#ifndef SP_CODA - [filterTableView reloadData]; -#endif +} +- (NSString *)selectedTable +{ + return selectedTable; } /** @@ -819,18 +734,18 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper // Notify any listeners that a query has started [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:@"SMySQLQueryWillBePerformed" object:tableDocumentInstance]; + // Add a filter string if appropriate + filterString = [[self onMainThread] tableFilterString]; + // Start construction of the query string queryString = [NSMutableString stringWithFormat:@"SELECT %@%@ FROM %@", #ifndef SP_CODA - (activeFilter == 1 && [self tableFilterString] && filterTableDistinct) ? @"DISTINCT " : + (activeFilter == SPTableContentFilterSourceTableFilter && filterString && [filterTableController isDistinct]) ? @"DISTINCT " : #endif @"", [self fieldListForQuery], [selectedTable backtickQuotedString]]; - // Add a filter string if appropriate - filterString = [[self onMainThread] tableFilterString]; - - if (filterString) { + if ([filterString length]) { [queryString appendFormat:@" WHERE %@", filterString]; isFiltered = YES; } else { @@ -1004,10 +919,10 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper if ([mySQLConnection queryErrored] && ![mySQLConnection lastQueryWasCancelled]) { #ifndef SP_CODA - if(activeFilter == 0) { + if(activeFilter == SPTableContentFilterSourceRuleFilter || activeFilter == SPTableContentFilterSourceNone) { #endif NSString *errorDetail; - if(filterString) + if([filterString length]) errorDetail = [NSString stringWithFormat:NSLocalizedString(@"The table data couldn't be loaded presumably due to used filter clause. \n\nMySQL said: %@", @"message of panel when loading of table failed and presumably due to used filter argument"), [mySQLConnection lastErrorMessage]]; else errorDetail = [NSString stringWithFormat:NSLocalizedString(@"The table data couldn't be loaded.\n\nMySQL said: %@", @"message of panel when loading of table failed"), [mySQLConnection lastErrorMessage]]; @@ -1016,8 +931,10 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper } #ifndef SP_CODA // Filter task came from filter table - else if(activeFilter == 1){ - [filterTableWindow setTitle:[NSString stringWithFormat:@"%@ – %@", NSLocalizedString(@"Filter", @"filter label"), NSLocalizedString(@"WHERE clause not valid", @"WHERE clause not valid")]]; + else if(activeFilter == SPTableContentFilterSourceTableFilter) { + [[filterTableController onMainThread] setFilterError:[mySQLConnection lastErrorID] + message:[mySQLConnection lastErrorMessage] + sqlstate:[mySQLConnection lastSqlstate]]; } } #endif @@ -1026,7 +943,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper #ifndef SP_CODA // Trigger a full reload if required if (fullTableReloadRequired) [self reloadTable:self]; - [[filterTableWindow onMainThread] setTitle:NSLocalizedString(@"Filter", @"filter label")]; + [[filterTableController onMainThread] setFilterError:0 message:nil sqlstate:nil]; #endif } } @@ -1051,6 +968,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper [theResultStore startDownload]; #ifndef SP_CODA +#warning private ivar accessed from outside NSProgressIndicator *dataLoadingIndicator = [tableDocumentInstance valueForKey:@"queryProgressBar"]; #else NSProgressIndicator *dataLoadingIndicator = [tableDocumentInstance queryProgressBar]; @@ -1098,70 +1016,35 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper #ifndef SP_CODA // If filter command was passed by sequelpro url scheme - if(activeFilter == 2) { - if(schemeFilter) - return schemeFilter; + if(activeFilter == SPTableContentFilterSourceURLScheme) { + if(schemeFilter) return schemeFilter; } // Call did come from filter table and is filter table window still open? - if(activeFilter == 1 && [filterTableWindow isVisible]) { - - if([[[filterTableWhereClause textStorage] string] length]) - if([filterTableNegateCheckbox state] == NSOnState) - return [NSString stringWithFormat:@"NOT (%@)", [[filterTableWhereClause textStorage] string]]; - else - return [[filterTableWhereClause textStorage] string]; - else - return nil; - + if(activeFilter == SPTableContentFilterSourceTableFilter && [[filterTableController window] isVisible]) { + return [filterTableController tableFilterString]; } #endif - - // If the clause has the placeholder $BINARY that placeholder will be replaced - // by BINARY if the user pressed ⇧ while invoking 'Filter' otherwise it will - // replaced by @"". - BOOL caseSensitive = (([[[NSApp onMainThread] currentEvent] modifierFlags] & NSShiftKeyMask) > 0); - - if(contentFilters == nil) { - NSLog(@"Fatal error while retrieving content filters. No filters found."); - NSBeep(); - return nil; - } - - // Current selected filter type - if(![contentFilters objectForKey:compareType]) { - NSLog(@"Error while retrieving filters. Filter type “%@” unknown.", compareType); - NSBeep(); - return nil; - } - NSDictionary *filter = [[contentFilters objectForKey:compareType] objectAtIndex:[[compareField selectedItem] tag]]; - - if(![filter objectForKey:@"NumberOfArguments"]) { - NSLog(@"Error while retrieving filter clause. No “NumberOfArguments” key found."); - NSBeep(); - return nil; + if(activeFilter == SPTableContentFilterSourceRuleFilter && showFilterRuleEditor) { + // If the clause has the placeholder $BINARY that placeholder will be replaced + // by BINARY if the user pressed ⇧ while invoking 'Filter' otherwise it will + // replaced by @"". + BOOL caseSensitive = (([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) > 0); + + NSError *err = nil; + NSString *filter = [ruleFilterController sqlWhereExpressionWithBinary:caseSensitive error:&err]; + if(err) { + SPOnewayAlertSheet( + NSLocalizedString(@"Invalid Filter", @"table content : apply filter : invalid filter message title"), + [tableDocumentInstance parentWindow], + [err localizedDescription] + ); + return nil; + } + return filter; } - if(![filter objectForKey:@"Clause"] || ![(NSString *)[filter objectForKey:@"Clause"] length]) { - - SPOnewayAlertSheet( - NSLocalizedString(@"Warning", @"warning"), - [tableDocumentInstance parentWindow], - NSLocalizedString(@"Content Filter clause is empty.", @"content filter clause is empty tooltip.") - ); - - return nil; - } - - SPTableFilterParser *parser = [[[SPTableFilterParser alloc] initWithFilterClause:[filter objectForKey:@"Clause"] numberOfArguments:[[filter objectForKey:@"NumberOfArguments"] integerValue]] autorelease]; - [parser setArgument:[argumentField stringValue]]; - [parser setFirstBetweenArgument:[firstBetweenField stringValue]]; - [parser setSecondBetweenArgument:[secondBetweenField stringValue]]; - [parser setSuppressLeadingTablePlaceholder:[[filter objectForKey:@"SuppressLeadingFieldPlaceholder"] boolValue]]; - [parser setCaseSensitive:caseSensitive]; - [parser setCurrentField:[fieldField titleOfSelectedItem]]; - - return [parser filterString]; + return nil; } /** @@ -1364,7 +1247,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper { BOOL senderIsPaginationButton = (sender == paginationPreviousButton || sender == paginationNextButton #ifndef SP_CODA - || sender == paginationGoButton + || sender == paginationViewController #endif ); @@ -1375,22 +1258,25 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper #ifndef SP_CODA + BOOL resetPaging = NO; // if filtering was triggered by pressing the "Filter" button, reset to page 1 + // If the filter table is being used - the advanced filter - switch type - if(sender == filterTableFilterButton) { - activeFilter = 1; + if(sender == filterTableController) { + activeFilter = SPTableContentFilterSourceTableFilter; + resetPaging = YES; } - // If a string was supplied, use a custom query from that URL scheme else if([sender isKindOfClass:[NSString class]] && [(NSString *)sender length]) { if(schemeFilter) SPClear(schemeFilter); schemeFilter = [sender retain]; - activeFilter = 2; + activeFilter = SPTableContentFilterSourceURLScheme; + resetPaging = YES; } - // If a button other than the pagination buttons was used, set the active filter type to // the standard filter field. - else if (!senderIsPaginationButton) { - activeFilter = 0; + else if (sender == ruleFilterController) { + activeFilter = SPTableContentFilterSourceRuleFilter; + resetPaging = YES; } #endif @@ -1416,16 +1302,18 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper // Select the correct pagination value. // If the filter button was used, or if pagination is disabled, reset to page one - if (!senderIsPaginationButton && ([sender isKindOfClass:[NSButton class]] || [sender isKindOfClass:[NSTextField class]] || ![prefs boolForKey:SPLimitResults] || [paginationPageField integerValue] <= 0)) + NSInteger paginationViewPage = [[paginationViewController page] integerValue]; + if (resetPaging || ![prefs boolForKey:SPLimitResults] || paginationViewPage <= 0) { contentPage = 1; - + } // If the current page is out of bounds, move it within bounds - else if (([paginationPageField integerValue] - 1) * [prefs integerForKey:SPLimitResultsValue] >= maxNumRows) - contentPage = ceilf((CGFloat)maxNumRows / [prefs floatForKey:SPLimitResultsValue]); - + else if ((paginationViewPage - 1) * [prefs integerForKey:SPLimitResultsValue] >= maxNumRows) { + contentPage = ceilf((CGFloat) maxNumRows / [prefs floatForKey:SPLimitResultsValue]); + } // Otherwise, use the pagination value - else - contentPage = [paginationPageField integerValue]; + else { + contentPage = paginationViewPage; + } if ([self tableFilterString]) { taskString = NSLocalizedString(@"Filtering table...", @"Filtering table task description"); @@ -1438,11 +1326,15 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper [tableDocumentInstance startTaskWithDescription:taskString]; if ([NSThread isMainThread]) { - [NSThread detachNewThreadWithName:SPCtxt(@"SPTableContent filter table task", tableDocumentInstance) target:self selector:@selector(filterTableTask) object:nil]; + [NSThread detachNewThreadWithName:SPCtxt(@"SPTableContent filter table task", tableDocumentInstance) + target:self + selector:@selector(filterTableTask) + object:nil]; } else { [self filterTableTask]; } } + - (void)filterTableTask { @autoreleasepool { @@ -1452,7 +1344,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper #endif // Reset and reload data using the new filter settings - [self setSelectionToRestore:[self selectionDetailsAllowingIndexSelection:NO]]; + [self setSelectionToRestore:[[self onMainThread] selectionDetailsAllowingIndexSelection:NO]]; previousTableRowsCount = 0; [self clearTableValues]; [self loadTableValues]; @@ -1462,61 +1354,32 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper } } -/** - * Enables or disables the filter input field based on the selected filter type. - */ -- (IBAction)toggleFilterField:(id)sender +- (IBAction)toggleRuleEditorVisible:(id)sender { - - // Check if user called "Edit Filter…" - if([[compareField selectedItem] tag] == (NSInteger)[[contentFilters objectForKey:compareType] count]) { - [self openContentFilterManager]; - return; + BOOL shouldShow = !showFilterRuleEditor; + [self setRuleEditorVisible:shouldShow animate:YES]; + // if this was the active filter before, it no longer can be the active filter when it is hidden + if(activeFilter == SPTableContentFilterSourceRuleFilter && !shouldShow) { + activeFilter = SPTableContentFilterSourceNone; } +} - // Remember last selection for "Edit filter…" - lastSelectedContentFilterIndex = [[compareField selectedItem] tag]; - - NSDictionary *filter = [[contentFilters objectForKey:compareType] objectAtIndex:lastSelectedContentFilterIndex]; - NSUInteger numOfArgs = [[filter objectForKey:@"NumberOfArguments"] integerValue]; - if (numOfArgs == 2) { - [argumentField setHidden:YES]; - - if([filter objectForKey:@"ConjunctionLabels"] && [[filter objectForKey:@"ConjunctionLabels"] count] == 1) - [betweenTextField setStringValue:[[filter objectForKey:@"ConjunctionLabels"] objectAtIndex:0]]; - else - [betweenTextField setStringValue:@""]; - - [betweenTextField setHidden:NO]; - [firstBetweenField setHidden:NO]; - [secondBetweenField setHidden:NO]; - - [firstBetweenField setEnabled:YES]; - [secondBetweenField setEnabled:YES]; - [firstBetweenField selectText:self]; - } - else if (numOfArgs == 1){ - [argumentField setHidden:NO]; - [argumentField setEnabled:YES]; - [argumentField selectText:self]; - - [betweenTextField setHidden:YES]; - [firstBetweenField setHidden:YES]; - [secondBetweenField setHidden:YES]; +- (void)setRuleEditorVisible:(BOOL)show animate:(BOOL)animate +{ + // we can't change the state of the button here, because the mouse click already changed it + if(show) { + if([ruleFilterController isEmpty]) { + [ruleFilterController addFilterExpression]; + // the sizing will be updated automatically by adding a row + } + else { + [self updateFilterRuleEditorSize:[ruleFilterController preferredHeight] animate:animate]; + } } else { - [argumentField setHidden:NO]; - [argumentField setEnabled:NO]; - - [betweenTextField setHidden:YES]; - [firstBetweenField setHidden:YES]; - [secondBetweenField setHidden:YES]; - - // Start search if no argument is required - if(numOfArgs == 0) - [self filterTable:self]; + [self updateFilterRuleEditorSize:0.0 animate:animate]; } - + showFilterRuleEditor = show; } - (void)setUsedQuery:(NSString *)query @@ -1622,10 +1485,10 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper if (sender == paginationPreviousButton) { if (contentPage <= 1) return; - [paginationPageField setIntegerValue:(contentPage - 1)]; + [paginationViewController setPage:@(contentPage - 1)]; } else if (sender == paginationNextButton) { if ((NSInteger)contentPage * [prefs integerForKey:SPLimitResultsValue] >= maxNumRows) return; - [paginationPageField setIntegerValue:(contentPage + 1)]; + [paginationViewController setPage:@(contentPage + 1)]; } [self filterTable:sender]; @@ -1659,18 +1522,17 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper if(makeVisible) { [paginationButton setState:NSOnState]; [paginationButton setImage:[NSImage imageNamed:@"button_action"]]; - [[paginationPageField window] makeFirstResponder:paginationPageField]; + [paginationViewController makeInputFirstResponder]; } else { [paginationButton setState:NSOffState]; [paginationButton setImage:[NSImage imageNamed:@"button_pagination"]]; - if ([[paginationPageField window] firstResponder] == paginationPageField - || ([[[paginationPageField window] firstResponder] respondsToSelector:@selector(superview)] - && [(id)[[paginationPageField window] firstResponder] superview] - && [[(id)[[paginationPageField window] firstResponder] superview] respondsToSelector:@selector(superview)] - && [[(id)[[paginationPageField window] firstResponder] superview] superview] == paginationPageField)) - { - [[paginationPageField window] makeFirstResponder:nil]; + // TODO This is only relevant in 10.6 legacy mode. + // When using a modern NSPopover, the view controller's parent window is an _NSPopoverWindow, + // not the SP window and we don't care what the first responder in the popover is. + // (when it is not being displayed anyway). + if (!paginationPopover && [paginationViewController isFirstResponderInside]) { + [[[paginationViewController view] window] makeFirstResponder:nil]; } } @@ -1710,33 +1572,24 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper } BOOL enabledMode = ![tableDocumentInstance isWorking]; - NSNumberFormatter *numberFormatter = [[[NSNumberFormatter alloc] init] autorelease]; - [numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle]; - + BOOL limitResults = [prefs boolForKey:SPLimitResults]; // Set up the previous page button - if ([prefs boolForKey:SPLimitResults] && contentPage > 1) - [paginationPreviousButton setEnabled:enabledMode]; - else - [paginationPreviousButton setEnabled:NO]; + [paginationPreviousButton setEnabled:(limitResults && contentPage > 1 ? enabledMode : NO)]; // Set up the next page button - if ([prefs boolForKey:SPLimitResults] && contentPage < maxPage) - [paginationNextButton setEnabled:enabledMode]; - else - [paginationNextButton setEnabled:NO]; + [paginationNextButton setEnabled:(limitResults && contentPage < maxPage ? enabledMode : NO)]; #ifndef SP_CODA // As long as a table is selected (which it will be if this is called), enable pagination detail button [paginationButton setEnabled:enabledMode]; #endif + // "1" is the minimum page, so maxPage must not be less (which it would be for empty tables) + if(maxPage < 1) maxPage = 1; + // Set the values and maximums for the text field and associated pager - [paginationPageField setStringValue:[numberFormatter stringFromNumber:[NSNumber numberWithUnsignedInteger:contentPage]]]; - [[paginationPageField formatter] setMaximum:[NSNumber numberWithUnsignedInteger:maxPage]]; -#ifndef SP_CODA - [paginationPageStepper setIntegerValue:contentPage]; - [paginationPageStepper setMaxValue:maxPage]; -#endif + [paginationViewController setPage:@(contentPage)]; + [paginationViewController setMaxPage:@(maxPage)]; } #pragma mark - @@ -1965,10 +1818,10 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper if (![tableContentView numberOfSelectedRows]) return; NSAlert *alert = [NSAlert alertWithMessageText:@"" - defaultButton:NSLocalizedString(@"Delete", @"delete button") - alternateButton:NSLocalizedString(@"Cancel", @"cancel button") - otherButton:nil - informativeTextWithFormat:@""]; + defaultButton:NSLocalizedString(@"Delete", @"delete button") + alternateButton:NSLocalizedString(@"Cancel", @"cancel button") + otherButton:nil + informativeTextWithFormat:@""]; [alert setAlertStyle:NSCriticalAlertStyle]; @@ -2025,7 +1878,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper /** * Perform the requested row deletion action. */ -- (void)removeRowSheetDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo +- (void)removeRowSheetDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { NSMutableIndexSet *selectedRows = [NSMutableIndexSet indexSet]; NSString *wherePart; @@ -2533,45 +2386,39 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper [spHistoryControllerInstance setModifyingState:YES]; #endif - NSString *targetFilterValue = [tableValues cellDataAtRow:[theArrowCell getClickedRow] column:dataColumnIndex]; + id targetFilterValue = [tableValues cellDataAtRow:[theArrowCell getClickedRow] column:dataColumnIndex]; //when navigating binary relations (eg. raw UUID) do so via a hex-encoded value for charset safety BOOL navigateAsHex = ([targetFilterValue isKindOfClass:[NSData class]] && [[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"binary"]); if(navigateAsHex) targetFilterValue = [mySQLConnection escapeData:(NSData *)targetFilterValue includingQuotes:NO]; + NSString *filterComparison = @"="; + if([targetFilterValue isNSNull]) filterComparison = @"IS NULL"; + else if(navigateAsHex) filterComparison = @"= (Hex String)"; + + // Store the filter details to use when loading the target table + NSDictionary *filterSettings = [SPRuleFilterController makeSerializedFilterForColumn:[refDictionary objectForKey:@"column"] + operator:filterComparison + values:@[targetFilterValue]]; + // If the link is within the current table, apply filter settings manually if ([[refDictionary objectForKey:@"table"] isEqualToString:selectedTable]) { SPMainQSync(^{ - [fieldField selectItemWithTitle:[refDictionary objectForKey:@"column"]]; - [self setCompareTypes:self]; - if ([targetFilterValue isNSNull]) { - [compareField selectItemWithTitle:@"IS NULL"]; - } - else { - if(navigateAsHex) [compareField selectItemWithTitle:@"= (Hex String)"]; - [argumentField setStringValue:targetFilterValue]; - } + [ruleFilterController restoreSerializedFilters:filterSettings]; + [self setRuleEditorVisible:YES animate:YES]; + activeFilter = SPTableContentFilterSourceRuleFilter; }); tableFilterRequired = YES; } else { - NSString *filterComparison = nil; - if([targetFilterValue isNSNull]) filterComparison = @"IS NULL"; - else if(navigateAsHex) filterComparison = @"= (Hex String)"; - - // Store the filter details to use when loading the target table - NSDictionary *filterSettings = @{ - @"filterField": [refDictionary objectForKey:@"column"], - @"filterValue": targetFilterValue, - @"filterComparison": SPBoxNil(filterComparison) - }; SPMainQSync(^{ [self setFiltersToRestore:filterSettings]; - + [self setActiveFilterToRestore:SPTableContentFilterSourceRuleFilter]; // Attempt to switch to the target table if (![tablesListInstance selectItemWithName:[refDictionary objectForKey:@"table"]]) { NSBeep(); [self setFiltersToRestore:nil]; + [self setActiveFilterToRestore:SPTableContentFilterSourceNone]; } }); } @@ -2592,188 +2439,6 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper } } -- (void)contentFiltersHaveBeenUpdated:(NSNotification *)notification -{ - [self setCompareTypes:nil]; -} - -/** - * Sets the compare types for the filter and the appropriate formatter for the textField - */ -- (IBAction)setCompareTypes:(id)sender -{ - - if(contentFilters == nil - || ![contentFilters objectForKey:@"number"] - || ![contentFilters objectForKey:@"string"] - || ![contentFilters objectForKey:@"date"]) { - NSLog(@"Error while setting filter types."); - NSBeep(); - return; - } - - // Retrieve the current field comparison setting for later restoration if possible - NSString *titleToRestore = [[compareField selectedItem] title]; - - // Reset the menu before building it back up - [compareField removeAllItems]; - - NSString *fieldTypeGrouping; - if([[tableDataInstance columnWithName:[fieldField titleOfSelectedItem]] objectForKey:@"typegrouping"]) - fieldTypeGrouping = [NSString stringWithString:[[tableDataInstance columnWithName:[fieldField titleOfSelectedItem]] objectForKey:@"typegrouping"]]; - else - return; - - if ( [fieldTypeGrouping isEqualToString:@"date"] ) { - compareType = @"date"; - - /* - if ([fieldType isEqualToString:@"timestamp"]) { - [argumentField setFormatter:[[NSDateFormatter alloc] - initWithDateFormat:@"%Y-%m-%d %H:%M:%S" allowNaturalLanguage:YES]]; - } - if ([fieldType isEqualToString:@"datetime"]) { - [argumentField setFormatter:[[NSDateFormatter alloc] initWithDateFormat:@"%Y-%m-%d %H:%M:%S" allowNaturalLanguage:YES]]; - } - if ([fieldType isEqualToString:@"date"]) { - [argumentField setFormatter:[[NSDateFormatter alloc] initWithDateFormat:@"%Y-%m-%d" allowNaturalLanguage:YES]]; - } - if ([fieldType isEqualToString:@"time"]) { - [argumentField setFormatter:[[NSDateFormatter alloc] initWithDateFormat:@"%H:%M:%S" allowNaturalLanguage:YES]]; - } - if ([fieldType isEqualToString:@"year"]) { - [argumentField setFormatter:[[NSDateFormatter alloc] initWithDateFormat:@"%Y" allowNaturalLanguage:YES]]; - } - */ - - // TODO: A bug in the framework previously meant enum fields had to be treated as string fields for the purposes - // of comparison - this can now be split out to support additional comparison fucntionality if desired. - } else if ([fieldTypeGrouping isEqualToString:@"string"] || [fieldTypeGrouping isEqualToString:@"binary"] - || [fieldTypeGrouping isEqualToString:@"textdata"] || [fieldTypeGrouping isEqualToString:@"blobdata"] - || [fieldTypeGrouping isEqualToString:@"enum"]) { - - compareType = @"string"; - // [argumentField setFormatter:nil]; - - } else if ([fieldTypeGrouping isEqualToString:@"bit"] || [fieldTypeGrouping isEqualToString:@"integer"] - || [fieldTypeGrouping isEqualToString:@"float"]) { - compareType = @"number"; - // [argumentField setFormatter:numberFormatter]; - - } else if ([fieldTypeGrouping isEqualToString:@"geometry"]) { - compareType = @"spatial"; - - } else { - compareType = @""; - NSBeep(); - NSLog(@"ERROR: unknown type for comparision: %@, in %@", [[tableDataInstance columnWithName:[fieldField titleOfSelectedItem]] objectForKey:@"type"], fieldTypeGrouping); - } - - // Add IS NULL and IS NOT NULL as they should always be available - // [compareField addItemWithTitle:@"IS NULL"]; - // [compareField addItemWithTitle:@"IS NOT NULL"]; - - // Remove user-defined filters first - if([numberOfDefaultFilters objectForKey:compareType]) { - NSUInteger cycles = [[contentFilters objectForKey:compareType] count] - [[numberOfDefaultFilters objectForKey:compareType] integerValue]; - while(cycles > 0) { - [[contentFilters objectForKey:compareType] removeLastObject]; - cycles--; - } - } - -#ifndef SP_CODA /* content filters */ - // Load global user-defined content filters - if([prefs objectForKey:SPContentFilters] - && [contentFilters objectForKey:compareType] - && [[prefs objectForKey:SPContentFilters] objectForKey:compareType]) - { - [[contentFilters objectForKey:compareType] addObjectsFromArray:[[prefs objectForKey:SPContentFilters] objectForKey:compareType]]; - } - - // Load doc-based user-defined content filters - if([[SPQueryController sharedQueryController] contentFilterForFileURL:[tableDocumentInstance fileURL]]) { - id filters = [[SPQueryController sharedQueryController] contentFilterForFileURL:[tableDocumentInstance fileURL]]; - if([filters objectForKey:compareType]) - [[contentFilters objectForKey:compareType] addObjectsFromArray:[filters objectForKey:compareType]]; - } -#endif - - // Rebuild operator popup menu - NSUInteger i = 0; - NSMenu *menu = [compareField menu]; - if([contentFilters objectForKey:compareType]) - for(id filter in [contentFilters objectForKey:compareType]) { - NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:([filter objectForKey:@"MenuLabel"])?[filter objectForKey:@"MenuLabel"]:@"not specified" action:NULL keyEquivalent:@""]; - // Create the tooltip - if([filter objectForKey:@"Tooltip"]) - [item setToolTip:[filter objectForKey:@"Tooltip"]]; - else { - NSMutableString *tip = [[NSMutableString alloc] init]; - if([filter objectForKey:@"Clause"] && [(NSString *)[filter objectForKey:@"Clause"] length]) { - [tip setString:[[filter objectForKey:@"Clause"] stringByReplacingOccurrencesOfRegex:@"(?<!\\\\)(\\$\\{.*?\\})" withString:@"[arg]"]]; - if([tip isMatchedByRegex:@"(?<!\\\\)\\$BINARY"]) { - [tip replaceOccurrencesOfRegex:@"(?<!\\\\)\\$BINARY" withString:@""]; - [tip appendString:NSLocalizedString(@"\n\nPress ⇧ for binary search (case-sensitive).", @"\n\npress shift for binary search tooltip message")]; - } - [tip flushCachedRegexData]; - [tip replaceOccurrencesOfRegex:@"(?<!\\\\)\\$CURRENT_FIELD" withString:[[fieldField titleOfSelectedItem] backtickQuotedString]]; - [tip flushCachedRegexData]; - [item setToolTip:tip]; - } else { - [item setToolTip:@""]; - } - [tip release]; - } - [item setTag:i]; - [menu addItem:item]; - [item release]; - i++; - } - -#ifndef SP_CODA - [menu addItem:[NSMenuItem separatorItem]]; - NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Edit Filters…", @"edit filter") action:NULL keyEquivalent:@""]; - [item setToolTip:NSLocalizedString(@"Edit user-defined Filters…", @"edit user-defined filter")]; - [item setTag:i]; - [menu addItem:item]; - [item release]; -#endif - - // Attempt to reselect the previously selected title, falling back to the first - // item on failure, as long as there is no filter selection to be restored. - if (!filterFieldToRestore) { - [compareField selectItemWithTitle:titleToRestore]; - if (![compareField selectedItem]) [compareField selectItemAtIndex:0]; - } - - // Update the argumentField enabled state - [self performSelectorOnMainThread:@selector(toggleFilterField:) withObject:self waitUntilDone:YES]; - - // set focus on argumentField - [argumentField performSelectorOnMainThread:@selector(selectText:) withObject:self waitUntilDone:YES]; - -} - -- (void)openContentFilterManager -{ - [compareField selectItemWithTag:lastSelectedContentFilterIndex]; - - // init query favorites controller -#ifndef SP_CODA - [prefs synchronize]; -#endif - if(contentFilterManager) [contentFilterManager release]; - contentFilterManager = [[SPContentFilterManager alloc] initWithDelegate:self forFilterType:compareType]; - - // Open query favorite manager - [NSApp beginSheet:[contentFilterManager window] - modalForWindow:[tableDocumentInstance parentWindow] - modalDelegate:contentFilterManager - didEndSelector:nil - contextInfo:nil]; -} - /** * Tries to write a new row to the table. * Returns YES if row is written to table, otherwise NO; also returns YES if no row @@ -2851,8 +2516,8 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper fieldValue = [NSString stringWithFormat:@"b'%@'", ((![desc length] || [desc isEqualToString:@"0"]) ? @"0" : desc)]; } else if ([fieldTypeGroup isEqualToString:@"date"] && [desc isEqualToString:@"NOW()"]) { fieldValue = @"NOW()"; - } else if ([fieldTypeGroup isEqualToString:@"string"] && [[rowObject description] isEqualToString:@"UUID()"]) { - fieldValue = @"UUID()"; + } else if ([fieldTypeGroup isEqualToString:@"string"] && [[rowObject description] isEqualToString:@"UUID()"]) { + fieldValue = @"UUID()"; } else { fieldValue = [mySQLConnection escapeAndQuoteString:desc]; } @@ -2986,12 +2651,20 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper currentlyEditingRow = -1; return YES; - + } // Report errors which have occurred - } else { - SPBeginAlertSheet(NSLocalizedString(@"Unable to write row", @"Unable to write row error"), NSLocalizedString(@"Edit row", @"Edit row button"), NSLocalizedString(@"Discard changes", @"discard changes button"), nil, [tableDocumentInstance parentWindow], self, @selector(addRowErrorSheetDidEnd:returnCode:contextInfo:), NULL, - [NSString stringWithFormat:NSLocalizedString(@"MySQL said:\n\n%@", @"message of panel when error while adding row to db"), [mySQLConnection lastErrorMessage]]); + SPBeginAlertSheet( + NSLocalizedString(@"Unable to write row", @"Unable to write row error"), + NSLocalizedString(@"Edit row", @"Edit row button"), + NSLocalizedString(@"Discard changes", @"discard changes button"), + nil, + [tableDocumentInstance parentWindow], + self, + @selector(addRowErrorSheetDidEnd:returnCode:contextInfo:), + NULL, + [NSString stringWithFormat:NSLocalizedString(@"MySQL said:\n\n%@", @"message of panel when error while adding row to db"), [mySQLConnection lastErrorMessage]] + ); return NO; } } @@ -3328,46 +3001,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper } -/** - * Close an open sheet. - */ -- (void)sheetDidEnd:(id)sheet returnCode:(NSInteger)returnCode contextInfo:(NSString *)contextInfo -{ -#ifndef SP_CODA - [sheet orderOut:self]; - - if([contextInfo isEqualToString:SPTableFilterSetDefaultOperator]) { - if(returnCode) { - if(filterTableDefaultOperator) [filterTableDefaultOperator release]; - NSString *newOperator = [filterTableSetDefaultOperatorValue stringValue]; - filterTableDefaultOperator = [[self escapeFilterTableDefaultOperator:newOperator] retain]; - [prefs setObject:newOperator forKey:SPFilterTableDefaultOperator]; - - if(![newOperator isMatchedByRegex:@"(?i)like\\s+['\"]%@%['\"]\\s*"]) { - if(![prefs objectForKey:SPFilterTableDefaultOperatorLastItems]) - [prefs setObject:[NSMutableArray array] forKey:SPFilterTableDefaultOperatorLastItems]; - - NSMutableArray *lastItems = [NSMutableArray array]; - [lastItems setArray:[prefs objectForKey:SPFilterTableDefaultOperatorLastItems]]; - - if([lastItems containsObject:newOperator]) - [lastItems removeObject:newOperator]; - if([lastItems count] > 0) - [lastItems insertObject:newOperator atIndex:0]; - else - [lastItems addObject:newOperator]; - // Remember only the last 15 items - if([lastItems count] > 15) - while([lastItems count] > 15) - [filterTableSetDefaultOperatorValue removeItemAtIndex:[lastItems count]-1]; - - [prefs setObject:lastItems forKey:SPFilterTableDefaultOperatorLastItems]; - } - [self updateFilterTableClause:nil]; - } - } -#endif -} + /** * Show Error sheet (can be called from inside of a endSheet selector) @@ -3564,160 +3198,11 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper #pragma mark Filter Table /** - * Clear the filter table - */ -- (IBAction)tableFilterClear:(id)sender -{ -#ifndef SP_CODA - - [filterTableView abortEditing]; - - if(filterTableData && [filterTableData count]) { - - // Clear filter data - for(NSNumber *col in [filterTableData allKeys]) - { - [[filterTableData objectForKey:col] setObject:[NSMutableArray arrayWithObjects:@"", @"", @"", @"", @"", @"", @"", @"", @"", @"", nil] forKey:SPTableContentFilterKey]; - } - - [filterTableView reloadData]; - [filterTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO]; - [filterTableWhereClause setString:@""]; - - // Reload table - [self filterTable:nil]; - } -#endif -} - -/** * Show filter table */ - (IBAction)showFilterTable:(id)sender { -#ifndef SP_CODA - [filterTableWindow makeKeyAndOrderFront:nil]; - [filterTableWhereClause setContinuousSpellCheckingEnabled:NO]; - [filterTableWhereClause setAutoindent:NO]; - [filterTableWhereClause setAutoindentIgnoresEnter:NO]; - [filterTableWhereClause setAutopair:[prefs boolForKey:SPCustomQueryAutoPairCharacters]]; - [filterTableWhereClause setAutohelp:NO]; - [filterTableWhereClause setAutouppercaseKeywords:[prefs boolForKey:SPCustomQueryAutoUppercaseKeywords]]; - [filterTableWhereClause setCompletionWasReinvokedAutomatically:NO]; - [filterTableWhereClause insertText:@""]; - [filterTableWhereClause didChangeText]; - - [[filterTableView window] makeFirstResponder:filterTableView]; -#endif -} - -/** - * Set filter table's Negate - */ -- (IBAction)toggleNegateClause:(id)sender -{ -#ifndef SP_CODA - filterTableNegate = !filterTableNegate; - - if (filterTableNegate) { - [filterTableQueryTitle setStringValue:NSLocalizedString(@"WHERE NOT query", @"Title of filter preview area when the query WHERE is negated")]; - } - else { - [filterTableQueryTitle setStringValue:NSLocalizedString(@"WHERE query", @"Title of filter preview area when the query WHERE is normal")]; - } - - // If live search is set perform filtering - if ([filterTableLiveSearchCheckbox state] == NSOnState) { - [self filterTable:filterTableFilterButton]; - } -#endif - -} - -/** - * Set filter table's Distinct - */ -- (IBAction)toggleDistinctSelect:(id)sender -{ -#ifndef SP_CODA - filterTableDistinct = !filterTableDistinct; - - [filterTableDistinctCheckbox setState:(filterTableDistinct) ? NSOnState : NSOffState]; - - // If live search is set perform filtering - if ([filterTableLiveSearchCheckbox state] == NSOnState) { - [self filterTable:filterTableFilterButton]; - } -#endif - -} - -/** - * Set filter table's default operator - */ -- (IBAction)setDefaultOperator:(id)sender -{ -#ifndef SP_CODA - - [filterTableWindow makeFirstResponder:filterTableView]; - - // Load history - if([prefs objectForKey:SPFilterTableDefaultOperatorLastItems]) { - NSMutableArray *lastItems = [NSMutableArray array]; - - [lastItems addObject:@"LIKE '%@%'"]; - - for(NSString* item in [prefs objectForKey:SPFilterTableDefaultOperatorLastItems]) - { - [lastItems addObject:item]; - } - - [filterTableSetDefaultOperatorValue removeAllItems]; - [filterTableSetDefaultOperatorValue addItemsWithObjectValues:lastItems]; - } - - [filterTableSetDefaultOperatorValue setStringValue:[prefs objectForKey:SPFilterTableDefaultOperator]]; - - [NSApp beginSheet:filterTableSetDefaultOperatorSheet - modalForWindow:filterTableWindow - modalDelegate:self - didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) - contextInfo:SPTableFilterSetDefaultOperator]; -#endif - -} - -/** - * Generate WHERE clause to look for last typed pattern in all fields - */ -- (IBAction)toggleLookAllFieldsMode:(id)sender -{ - [self updateFilterTableClause:sender]; - -#ifndef SP_CODA - // If live search is set perform filtering - if ([filterTableLiveSearchCheckbox state] == NSOnState) { - [self filterTable:filterTableFilterButton]; - } -#endif - -} - -/** - * Closes the current sheet and stops the modal session - */ -- (IBAction)closeSheet:(id)sender -{ - [NSApp endSheet:[sender window] returnCode:[sender tag]]; - [[sender window] orderOut:self]; -} - -/** - * Opens the content filter help page in the default browser. - */ -- (IBAction)showDefaultOperaterHelp:(id)sender -{ - [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:SPLOCALIZEDURL_CONTENTFILTERHELP]]; + [filterTableController showFilterTableWindow]; } #pragma mark - @@ -3850,7 +3335,8 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper */ - (CGFloat) tablesListWidth { - return [[[[tableDocumentInstance valueForKeyPath:@"contentViewSplitter"] subviews] objectAtIndex:0] frame].size.width; +#warning private ivar accessed from outside + return [[[[tableDocumentInstance valueForKey:@"contentViewSplitter"] subviews] objectAtIndex:0] frame].size.width; } /** @@ -3860,21 +3346,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper */ - (NSDictionary *) filterSettings { - NSDictionary *theDictionary; - - if (![fieldField isEnabled]) return nil; - - theDictionary = [NSDictionary dictionaryWithObjectsAndKeys: - [self tableFilterString], @"menuLabel", - [fieldField titleOfSelectedItem], @"filterField", - [[compareField selectedItem] title], @"filterComparison", - [NSNumber numberWithInteger:[[compareField selectedItem] tag]], @"filterComparisonTag", - [argumentField stringValue], @"filterValue", - [firstBetweenField stringValue], @"firstBetweenField", - [secondBetweenField stringValue], @"secondBetweenField", - nil]; - - return theDictionary; + return [ruleFilterController serializedFilter]; } /** @@ -3921,44 +3393,9 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper */ - (void) setFiltersToRestore:(NSDictionary *)filterSettings { - if (filterFieldToRestore) SPClear(filterFieldToRestore); - if (filterComparisonToRestore) SPClear(filterComparisonToRestore); - if (filterValueToRestore) SPClear(filterValueToRestore); - if (firstBetweenValueToRestore) SPClear(firstBetweenValueToRestore); - if (secondBetweenValueToRestore) SPClear(secondBetweenValueToRestore); - - if ([filterSettings count]) { - if ([filterSettings objectForKey:@"filterField"]) - filterFieldToRestore = [[NSString alloc] initWithString:[filterSettings objectForKey:@"filterField"]]; - if ([[filterSettings objectForKey:@"filterComparison"] unboxNull]) { - // Check if operator is BETWEEN, if so set up input fields - if([[filterSettings objectForKey:@"filterComparison"] isEqualToString:@"BETWEEN"]) { - [argumentField setHidden:YES]; - [betweenTextField setHidden:NO]; - [firstBetweenField setHidden:NO]; - [secondBetweenField setHidden:NO]; - [firstBetweenField setEnabled:YES]; - [secondBetweenField setEnabled:YES]; - } - - filterComparisonToRestore = [[NSString alloc] initWithString:[filterSettings objectForKey:@"filterComparison"]]; - } - if([filterComparisonToRestore isEqualToString:@"BETWEEN"]) { - if ([filterSettings objectForKey:@"firstBetweenField"]) - firstBetweenValueToRestore = [[NSString alloc] initWithString:[filterSettings objectForKey:@"firstBetweenField"]]; - if ([filterSettings objectForKey:@"secondBetweenField"]) - secondBetweenValueToRestore = [[NSString alloc] initWithString:[filterSettings objectForKey:@"secondBetweenField"]]; - } else { - id filterValue = [filterSettings objectForKey:@"filterValue"]; - if ([filterValue unboxNull]) { - if ([filterValue isKindOfClass:[NSData class]]) { - filterValueToRestore = [[NSString alloc] initWithData:(NSData *)filterValue encoding:[mySQLConnection stringEncoding]]; - } else { - filterValueToRestore = [[NSString alloc] initWithString:(NSString *)filterValue]; - } - } - } - } + [filterSettings retain]; + SPClear(filtersToRestore); + filtersToRestore = filterSettings; } /** @@ -3973,6 +3410,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper [self setSelectionToRestore:[self selectionDetailsAllowingIndexSelection:YES]]; [self setViewportToRestore:[self viewport]]; [self setFiltersToRestore:[self filterSettings]]; + [self setActiveFilterToRestore:activeFilter]; } /** @@ -3985,36 +3423,98 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper [self setSelectionToRestore:nil]; [self setViewportToRestore:NSZeroRect]; [self setFiltersToRestore:nil]; + [self setActiveFilterToRestore:SPTableContentFilterSourceNone]; } -- (void) setFilterTableData:(NSData*)arcData +- (NSData*) filterTableData { -#ifndef SP_CODA - if(!arcData) return; - NSDictionary *filterData = [NSUnarchiver unarchiveObjectWithData:arcData]; - [filterTableData removeAllObjects]; - [filterTableData addEntriesFromDictionary:filterData]; - [filterTableWindow makeKeyAndOrderFront:nil]; - // [filterTableView reloadData]; -#endif + return [filterTableController filterTableData]; } -- (NSData*) filterTableData +- (void)setFilterTableData:(NSData *)arcData; { -#ifndef SP_CODA - if(![filterTableWindow isVisible]) return nil; + [filterTableController setFilterTableData:arcData]; +} - [filterTableView deselectAll:nil]; +- (SPTableContentFilterSource)activeFilter +{ + return activeFilter; +} - return [NSArchiver archivedDataWithRootObject:filterTableData]; -#else - return nil; -#endif +- (void)setActiveFilterToRestore:(SPTableContentFilterSource)filter +{ + activeFilterToRestore = filter; } #pragma mark - #pragma mark Table drawing and editing +- (void)updateFilterRuleEditorSize:(CGFloat)requestedHeight animate:(BOOL)animate +{ + NSRect contentAreaRect = [contentAreaContainer frame]; + CGFloat availableHeight = contentAreaRect.size.height; + + NSRect ruleEditorRect = [[[ruleFilterController view] enclosingScrollView] frame]; + + //adjust for the UI elements below the rule editor, but only if the view height should not be 0 (ie. hidden) + CGFloat containerRequestedHeight = requestedHeight ? requestedHeight + ruleEditorRect.origin.y : 0; + + //the rule editor can ask for about one-third of the available space before we have it use it's scrollbar + CGFloat topContainerGivenHeight = MIN(containerRequestedHeight,(availableHeight / 3)); + + // abort if the size didn't really change + NSRect topContainerRect = [filterRuleEditorContainer frame]; + if(topContainerGivenHeight == topContainerRect.size.height) return; + + CGFloat newBottomContainerHeight = availableHeight - topContainerGivenHeight; + + NSRect bottomContainerRect = [tableContentContainer frame]; + bottomContainerRect.size.height = newBottomContainerHeight; + + topContainerRect.origin.y = newBottomContainerHeight; + topContainerRect.size.height = topContainerGivenHeight; + + // this one should be inferable from the IB layout IMHO, but the OS gets it wrong + ruleEditorRect.size.height = topContainerGivenHeight - ruleEditorRect.origin.y; + + if(animate) { + [NSAnimationContext beginGrouping]; + [[tableContentContainer animator] setFrame:bottomContainerRect]; + [[filterRuleEditorContainer animator] setFrame:topContainerRect]; + [[[[ruleFilterController view] enclosingScrollView] animator] setFrame:ruleEditorRect]; + [NSAnimationContext endGrouping]; + } + else { + [tableContentContainer setFrameSize:bottomContainerRect.size]; + [filterRuleEditorContainer setFrame:topContainerRect]; + [[[ruleFilterController view] enclosingScrollView] setFrame:ruleEditorRect]; + } + + //disable rubberband scrolling as long as there is nothing to scroll + if(scrollViewHasRubberbandScrolling) { + NSScrollView *filterControllerScroller = [[ruleFilterController view] enclosingScrollView]; + if (ruleEditorRect.size.height >= requestedHeight) { + [filterControllerScroller setVerticalScrollElasticity:NSScrollElasticityNone]; + } else { + [filterControllerScroller setVerticalScrollElasticity:NSScrollElasticityAutomatic]; + } + } +} + +- (void)filterRuleEditorPreferredSizeChanged:(NSNotification *)notification +{ + if(showFilterRuleEditor) { + [self updateFilterRuleEditorSize:[ruleFilterController preferredHeight] animate:YES]; + } +} + +- (void)contentViewSizeChanged:(NSNotification *)notification +{ + if(showFilterRuleEditor) { + [self updateFilterRuleEditorSize:[ruleFilterController preferredHeight] animate:NO]; + } +} + /** * Updates the number of rows in the selected table. * Attempts to use the fullResult count if available, also updating the @@ -4035,6 +3535,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper [tableDataInstance setStatusValue:@"y" forKey:@"RowsCountAccurate"]; #ifndef SP_CODA [[tableInfoInstance onMainThread] tableChanged:nil]; +#warning private ivar accessed from outside [[[tableDocumentInstance valueForKey:@"extendedTableInfoInstance"] onMainThread] loadTable:selectedTable]; #endif @@ -4135,7 +3636,8 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper [removeButton setEnabled:NO]; [duplicateButton setEnabled:NO]; [reloadButton setEnabled:NO]; - [filterButton setEnabled:NO]; + [ruleFilterController setEnabled:NO]; + [toggleRuleFilterButton setEnabled:NO]; tableRowsSelectable = NO; [paginationPreviousButton setEnabled:NO]; [paginationNextButton setEnabled:NO]; @@ -4170,7 +3672,8 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper } } - [filterButton setEnabled:[fieldField isEnabled]]; + [ruleFilterController setEnabled:(!![selectedTable length])]; + [toggleRuleFilterButton setEnabled:(!![selectedTable length])]; tableRowsSelectable = YES; } @@ -4190,22 +3693,27 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { #ifndef SP_CODA /* observe pref changes */ - // Display table veiew vertical gridlines preference changed - if ([keyPath isEqualToString:SPDisplayTableViewVerticalGridlines]) { - [tableContentView setGridStyleMask:([[change objectForKey:NSKeyValueChangeNewKey] boolValue]) ? NSTableViewSolidVerticalGridLineMask : NSTableViewGridNone]; - [filterTableView setGridStyleMask:([[change objectForKey:NSKeyValueChangeNewKey] boolValue]) ? NSTableViewSolidVerticalGridLineMask : NSTableViewGridNone]; - } - // Table font preference changed - else if ([keyPath isEqualToString:SPGlobalResultTableFont]) { - NSFont *tableFont = [NSUnarchiver unarchiveObjectWithData:[change objectForKey:NSKeyValueChangeNewKey]]; + // a parent class (or cocoa) can also use KVO, so we need to watch out to only catch those KVO messages we requested + if(context == TableContentKVOContext) { + // Display table veiew vertical gridlines preference changed + if ([keyPath isEqualToString:SPDisplayTableViewVerticalGridlines]) { + [tableContentView setGridStyleMask:([[change objectForKey:NSKeyValueChangeNewKey] boolValue]) ? NSTableViewSolidVerticalGridLineMask : NSTableViewGridNone]; + } + // Table font preference changed + else if ([keyPath isEqualToString:SPGlobalResultTableFont]) { + NSFont *tableFont = [NSUnarchiver unarchiveObjectWithData:[change objectForKey:NSKeyValueChangeNewKey]]; - [tableContentView setRowHeight:2.0f + NSSizeToCGSize([@"{ǞṶḹÜ∑zgyf" sizeWithAttributes:@{NSFontAttributeName : tableFont}]).height]; - [tableContentView setFont:tableFont]; - [tableContentView reloadData]; + [tableContentView setRowHeight:2.0f + NSSizeToCGSize([@"{ǞṶḹÜ∑zgyf" sizeWithAttributes:@{NSFontAttributeName : tableFont}]).height]; + [tableContentView setFont:tableFont]; + [tableContentView reloadData]; + } + // Display binary data as Hex + else if ([keyPath isEqualToString:SPDisplayBinaryDataAsHex] && [tableContentView numberOfRows] > 0) { + [tableContentView reloadData]; + } } - // Display binary data as Hex - else if ([keyPath isEqualToString:SPDisplayBinaryDataAsHex] && [tableContentView numberOfRows] > 0) { - [tableContentView reloadData]; + else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } #endif } @@ -4255,11 +3763,6 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper - (NSInteger)numberOfRowsInTableView:(SPCopyTable *)tableView { -#ifndef SP_CODA - if (tableView == filterTableView) { - return filterTableIsSwapped ? [filterTableData count] : [[[filterTableData objectForKey:@"0"] objectForKey:SPTableContentFilterKey] count]; - } -#endif if (tableView == tableContentView) { return tableRowsCount; } @@ -4270,20 +3773,6 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper - (id)tableView:(SPCopyTable *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex { NSUInteger columnIndex = [[tableColumn identifier] integerValue]; -#ifndef SP_CODA - if (tableView == filterTableView) { - if (filterTableIsSwapped) { - // First column shows the field names - if (columnIndex == 0) { - return [[[NSTableHeaderCell alloc] initTextCell:[[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:@"name"]] autorelease]; - } - - return NSArrayObjectAtIndex([[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:SPTableContentFilterKey], columnIndex - 1); - } - - return NSArrayObjectAtIndex([[filterTableData objectForKey:[tableColumn identifier]] objectForKey:SPTableContentFilterKey], rowIndex); - } -#endif if (tableView == tableContentView) { id value = nil; @@ -4354,20 +3843,6 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper - (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex { -#ifndef SP_CODA - if(tableView == filterTableView) { - if (filterTableIsSwapped) { - [[[filterTableData objectForKey:[NSNumber numberWithInteger:rowIndex]] objectForKey:SPTableContentFilterKey] replaceObjectAtIndex:([[tableColumn identifier] integerValue] - 1) withObject:(NSString *)object]; - } - else { - [[[filterTableData objectForKey:[tableColumn identifier]] objectForKey:SPTableContentFilterKey] replaceObjectAtIndex:rowIndex withObject:(NSString *)object]; - } - - [self updateFilterTableClause:nil]; - - return; - } -#endif if (tableView == tableContentView) { NSInteger columnIndex = [[tableColumn identifier] integerValue]; // If the current cell should have been edited in a sheet, do nothing - field closing will have already @@ -4459,209 +3934,13 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper #ifndef SP_CODA /** - * Escape passed operator for usage as filterTableDefaultOperator. - */ -- (NSString*)escapeFilterTableDefaultOperator:(NSString *)op -{ - if (!op) return @""; - - NSMutableString *newOp = [[[NSMutableString alloc] initWithCapacity:[op length]] autorelease]; - - [newOp setString:op]; - [newOp replaceOccurrencesOfRegex:@"%" withString:@"%%"]; - [newOp replaceOccurrencesOfRegex:@"(?<!`)@(?!=`)" withString:@"%@"]; - - return newOp; -} - -/** - * Update WHERE clause in filter table window. - * - * @param currentValue If currentValue == nil take the data from filterTableData, if currentValue == filterTableSearchAllFields - * generate a WHERE clause to search in all given fields, if currentValue == a string take this string as table cell data of the - * currently edited table cell - */ -- (void)updateFilterTableClause:(id)currentValue -{ - NSMutableString *clause = [NSMutableString string]; - NSInteger numberOfRows = [self numberOfRowsInTableView:filterTableView]; - NSInteger numberOfCols = [[filterTableView tableColumns] count]; - NSInteger numberOfValues = 0; - NSRange opRange, defopRange; - - BOOL lookInAllFields = NO; - - NSString *re1 = @"^\\s*(<[=>]?|>=?|!?=|≠|≤|≥)\\s*(.*?)\\s*$"; - NSString *re2 = @"^\\s*(.*)\\s+(.*?)\\s*$"; - - NSInteger editedRow = [filterTableView editedRow]; - - if (currentValue == filterTableSearchAllFields) { - numberOfRows = 1; - lookInAllFields = YES; - } - - [filterTableWhereClause setString:@""]; - - for (NSInteger i = 0; i < numberOfRows; i++) - { - numberOfValues = 0; - - for (NSInteger anIndex = 0; anIndex < numberOfCols; anIndex++) - { - NSString *filterCell = nil; - NSDictionary *filterCellData = [NSDictionary dictionaryWithDictionary:[filterTableData objectForKey:[NSString stringWithFormat:@"%ld", (long)anIndex]]]; - - // Take filterTableData - if (!currentValue) { - filterCell = NSArrayObjectAtIndex([filterCellData objectForKey:SPTableContentFilterKey], i); - } - // Take last edited value to create the OR clause - else if (lookInAllFields) { - if (lastEditedFilterTableValue && [lastEditedFilterTableValue length]) { - filterCell = lastEditedFilterTableValue; - } - else { - [filterTableWhereClause setString:@""]; - [filterTableWhereClause insertText:@""]; - [filterTableWhereClause scrollRangeToVisible:NSMakeRange(0, 0)]; - - // If live search is set perform filtering - if ([filterTableLiveSearchCheckbox state] == NSOnState) { - [self filterTable:filterTableFilterButton]; - } - } - } - // Take value from currently edited table cell - else if ([currentValue isKindOfClass:[NSString class]]) { - if (i == editedRow && anIndex == [[NSArrayObjectAtIndex([filterTableView tableColumns], [filterTableView editedColumn]) identifier] integerValue]) { - filterCell = (NSString*)currentValue; - } - else { - filterCell = NSArrayObjectAtIndex([filterCellData objectForKey:SPTableContentFilterKey], i); - } - } - - if ([filterCell length]) { - - // Recode special operators - filterCell = [filterCell stringByReplacingOccurrencesOfRegex:@"^\\s*≠" withString:@"!="]; - filterCell = [filterCell stringByReplacingOccurrencesOfRegex:@"^\\s*≤" withString:@"<="]; - filterCell = [filterCell stringByReplacingOccurrencesOfRegex:@"^\\s*≥" withString:@">="]; - - if (numberOfValues) { - [clause appendString:(lookInAllFields) ? @" OR " : @" AND "]; - } - - NSString *fieldName = [[filterCellData objectForKey:@"name"] backtickQuotedString]; - NSString *filterTableDefaultOperatorWithFieldName = [filterTableDefaultOperator stringByReplacingOccurrencesOfString:@"`@`" withString:fieldName]; - - opRange = [filterCell rangeOfString:@"`@`"]; - defopRange = [filterTableDefaultOperator rangeOfString:@"`@`"]; - - // if cell data begins with ' or " treat it as it is - // by checking if default operator by itself contains a ' or " - if so - // remove first and if given the last ' or " - if ([filterCell isMatchedByRegex:@"^\\s*['\"]"]) { - if ([filterTableDefaultOperator isMatchedByRegex:@"['\"]"]) { - NSArray *matches = [filterCell arrayOfCaptureComponentsMatchedByRegex:@"^\\s*(['\"])(.*)\\1\\s*$"]; - - if ([matches count] && [matches = NSArrayObjectAtIndex(matches, 0) count] == 3) { - [clause appendFormat:[NSString stringWithFormat:@"%%@ %@", filterTableDefaultOperatorWithFieldName], fieldName, NSArrayObjectAtIndex(matches, 2)]; - } - else { - matches = [filterCell arrayOfCaptureComponentsMatchedByRegex:@"^\\s*(['\"])(.*)\\s*$"]; - - if ([matches count] && [matches = NSArrayObjectAtIndex(matches, 0) count] == 3) { - [clause appendFormat:[NSString stringWithFormat:@"%%@ %@", filterTableDefaultOperatorWithFieldName], fieldName, NSArrayObjectAtIndex(matches, 2)]; - } - } - } - else { - [clause appendFormat:[NSString stringWithFormat:@"%%@ %@", filterTableDefaultOperatorWithFieldName], fieldName, filterCell]; - } - } - // If cell contains the field name placeholder - else if (opRange.length || defopRange.length) { - filterCell = [filterCell stringByReplacingOccurrencesOfString:@"`@`" withString:fieldName]; - - if (defopRange.length) { - [clause appendFormat:filterTableDefaultOperatorWithFieldName, [filterCell stringByReplacingOccurrencesOfString:@"`@`" withString:fieldName]]; - } - else { - [clause appendString:[filterCell stringByReplacingOccurrencesOfString:@"`@`" withString:fieldName]]; - } - } - // If cell is equal to NULL - else if ([filterCell isMatchedByRegex:@"(?i)^\\s*null\\s*$"]) { - [clause appendFormat:@"%@ IS NULL", fieldName]; - } - // If cell starts with an operator - else if ([filterCell isMatchedByRegex:re1]) { - NSArray *matches = [filterCell arrayOfCaptureComponentsMatchedByRegex:re1]; - - if ([matches count] && [matches = NSArrayObjectAtIndex(matches, 0) count] == 3) { - [clause appendFormat:@"%@ %@ %@", fieldName, NSArrayObjectAtIndex(matches, 1), NSArrayObjectAtIndex(matches, 2)]; - } - } - // If cell consists of at least two words treat the first as operator and the rest as argument - else if ([filterCell isMatchedByRegex:re2]) { - NSArray *matches = [filterCell arrayOfCaptureComponentsMatchedByRegex:re2]; - - if ([matches count] && [matches = NSArrayObjectAtIndex(matches,0) count] == 3) { - [clause appendFormat:@"%@ %@ %@", fieldName, [NSArrayObjectAtIndex(matches, 1) uppercaseString], NSArrayObjectAtIndex(matches, 2)]; - } - } - // Apply the default operator - else { - [clause appendFormat:[NSString stringWithFormat:@"%%@ %@", filterTableDefaultOperatorWithFieldName], fieldName, filterCell]; - } - - numberOfValues++; - } - } - - if (numberOfValues) { - [clause appendString:@"\nOR\n"]; - } - } - - // Remove last " OR " if any - [filterTableWhereClause setString:[clause length] > 3 ? [clause substringToIndex:([clause length] - 4)] : @""]; - - // Update syntax highlighting and uppercasing - [filterTableWhereClause insertText:@""]; - [filterTableWhereClause scrollRangeToVisible:NSMakeRange(0, 0)]; - - // If live search is set perform filtering - if ([filterTableLiveSearchCheckbox state] == NSOnState) { - [self filterTable:filterTableFilterButton]; - } -} - -/** * Makes the content filter field have focus by making it the first responder. */ - (void)makeContentFilterHaveFocus { - NSDictionary *filter = [[contentFilters objectForKey:compareType] objectAtIndex:[[compareField selectedItem] tag]]; - - if ([filter objectForKey:@"NumberOfArguments"]) { - - NSUInteger numOfArgs = [[filter objectForKey:@"NumberOfArguments"] integerValue]; - - switch (numOfArgs) - { - case 2: - [[firstBetweenField window] makeFirstResponder:firstBetweenField]; - break; - case 1: - [[argumentField window] makeFirstResponder:argumentField]; - break; - default: - [[compareField window] makeFirstResponder:compareField]; - } - } + [self setRuleEditorVisible:YES animate:YES]; + [toggleRuleFilterButton setState:NSOnState]; + [ruleFilterController focusFirstInputField]; } #endif @@ -4834,135 +4113,128 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper { if ([tableDocumentInstance isWorking]) return NO; -#ifndef SP_CODA - if (tableView == filterTableView) { - return (filterTableIsSwapped && [[tableColumn identifier] integerValue] == 0) ? NO : YES; - } - else -#endif - if (tableView == tableContentView) { - - // Nothing is editable while the field editor is running. - // This guards against a special case where accessibility services might - // check if a table field is editable while the sheet is running. - if (fieldEditor) return NO; - - // Ensure that row is editable since it could contain "(not loaded)" columns together with - // issue that the table has no primary key - NSString *wherePart = [NSString stringWithString:[self argumentForRow:[tableContentView selectedRow]]]; + if (tableView == tableContentView) { - if (![wherePart length]) return NO; + // Nothing is editable while the field editor is running. + // This guards against a special case where accessibility services might + // check if a table field is editable while the sheet is running. + if (fieldEditor) return NO; - // If the selected cell hasn't been loaded, load it. - if ([[tableValues cellDataAtRow:rowIndex column:[[tableColumn identifier] integerValue]] isSPNotLoaded]) { + // Ensure that row is editable since it could contain "(not loaded)" columns together with + // issue that the table has no primary key + NSString *wherePart = [NSString stringWithString:[self argumentForRow:[tableContentView selectedRow]]]; - // Only get the data for the selected column, not all of them - NSString *query = [NSString stringWithFormat:@"SELECT %@ FROM %@ WHERE %@", [[[tableColumn headerCell] stringValue] backtickQuotedString], [selectedTable backtickQuotedString], wherePart]; + if (![wherePart length]) return NO; - SPMySQLResult *tempResult = [mySQLConnection queryString:query]; + // If the selected cell hasn't been loaded, load it. + if ([[tableValues cellDataAtRow:rowIndex column:[[tableColumn identifier] integerValue]] isSPNotLoaded]) { - if (![tempResult numberOfRows]) { - SPOnewayAlertSheet( - NSLocalizedString(@"Error", @"error"), - [tableDocumentInstance parentWindow], - NSLocalizedString(@"Couldn't load the row. Reload the table to be sure that the row exists and use a primary key for your table.", @"message of panel when loading of row failed") - ); - return NO; - } + // Only get the data for the selected column, not all of them + NSString *query = [NSString stringWithFormat:@"SELECT %@ FROM %@ WHERE %@", [[[tableColumn headerCell] stringValue] backtickQuotedString], [selectedTable backtickQuotedString], wherePart]; - NSArray *tempRow = [tempResult getRowAsArray]; + SPMySQLResult *tempResult = [mySQLConnection queryString:query]; - [tableValues replaceObjectInRow:rowIndex column:[[tableContentView tableColumns] indexOfObject:tableColumn] withObject:[tempRow objectAtIndex:0]]; - [tableContentView reloadData]; + if (![tempResult numberOfRows]) { + SPOnewayAlertSheet( + NSLocalizedString(@"Error", @"error"), + [tableDocumentInstance parentWindow], + NSLocalizedString(@"Couldn't load the row. Reload the table to be sure that the row exists and use a primary key for your table.", @"message of panel when loading of row failed") + ); + return NO; } - // Retrieve the column definition - NSDictionary *columnDefinition = [cqColumnDefinition objectAtIndex:[[tableColumn identifier] integerValue]]; + NSArray *tempRow = [tempResult getRowAsArray]; - // Open the editing sheet if required - if ([tableContentView shouldUseFieldEditorForRow:rowIndex column:[[tableColumn identifier] integerValue] checkWithLock:NULL]) { + [tableValues replaceObjectInRow:rowIndex column:[[tableContentView tableColumns] indexOfObject:tableColumn] withObject:[tempRow objectAtIndex:0]]; + [tableContentView reloadData]; + } - BOOL isBlob = [tableDataInstance columnIsBlobOrText:[[tableColumn headerCell] stringValue]]; + // Retrieve the column definition + NSDictionary *columnDefinition = [cqColumnDefinition objectAtIndex:[[tableColumn identifier] integerValue]]; - // A table is per definition editable - BOOL isFieldEditable = YES; + // Open the editing sheet if required + if ([tableContentView shouldUseFieldEditorForRow:rowIndex column:[[tableColumn identifier] integerValue] checkWithLock:NULL]) { - // Check for Views if field is editable - if ([tablesListInstance tableType] == SPTableTypeView) { - NSArray *editStatus = [self fieldEditStatusForRow:rowIndex andColumn:[[tableColumn identifier] integerValue]]; - isFieldEditable = [[editStatus objectAtIndex:0] integerValue] == 1; - } + BOOL isBlob = [tableDataInstance columnIsBlobOrText:[[tableColumn headerCell] stringValue]]; - NSUInteger fieldLength = 0; - NSString *fieldEncoding = nil; - BOOL allowNULL = YES; + // A table is per definition editable + BOOL isFieldEditable = YES; - NSString *fieldType = [columnDefinition objectForKey:@"type"]; + // Check for Views if field is editable + if ([tablesListInstance tableType] == SPTableTypeView) { + NSArray *editStatus = [self fieldEditStatusForRow:rowIndex andColumn:[[tableColumn identifier] integerValue]]; + isFieldEditable = [[editStatus objectAtIndex:0] integerValue] == 1; + } - if ([columnDefinition objectForKey:@"char_length"]) { - fieldLength = [[columnDefinition objectForKey:@"char_length"] integerValue]; - } + NSUInteger fieldLength = 0; + NSString *fieldEncoding = nil; + BOOL allowNULL = YES; - if ([columnDefinition objectForKey:@"null"]) { - allowNULL = (![[columnDefinition objectForKey:@"null"] integerValue]); - } + NSString *fieldType = [columnDefinition objectForKey:@"type"]; - if ([columnDefinition objectForKey:@"charset_name"] && ![[columnDefinition objectForKey:@"charset_name"] isEqualToString:@"binary"]) { - fieldEncoding = [columnDefinition objectForKey:@"charset_name"]; - } + if ([columnDefinition objectForKey:@"char_length"]) { + fieldLength = [[columnDefinition objectForKey:@"char_length"] integerValue]; + } - fieldEditor = [[SPFieldEditorController alloc] init]; + if ([columnDefinition objectForKey:@"null"]) { + allowNULL = (![[columnDefinition objectForKey:@"null"] integerValue]); + } - [fieldEditor setEditedFieldInfo:[NSDictionary dictionaryWithObjectsAndKeys: - [[tableColumn headerCell] stringValue], @"colName", - [self usedQuery], @"usedQuery", - @"content", @"tableSource", - nil]]; + if ([columnDefinition objectForKey:@"charset_name"] && ![[columnDefinition objectForKey:@"charset_name"] isEqualToString:@"binary"]) { + fieldEncoding = [columnDefinition objectForKey:@"charset_name"]; + } - [fieldEditor setTextMaxLength:fieldLength]; - [fieldEditor setFieldType:fieldType == nil ? @"" : fieldType]; - [fieldEditor setFieldEncoding:fieldEncoding == nil ? @"" : fieldEncoding]; - [fieldEditor setAllowNULL:allowNULL]; + fieldEditor = [[SPFieldEditorController alloc] init]; - id cellValue = [tableValues cellDataAtRow:rowIndex column:[[tableColumn identifier] integerValue]]; + [fieldEditor setEditedFieldInfo:[NSDictionary dictionaryWithObjectsAndKeys: + [[tableColumn headerCell] stringValue], @"colName", + [self usedQuery], @"usedQuery", + @"content", @"tableSource", + nil]]; - if ([cellValue isNSNull]) { - cellValue = [NSString stringWithString:[prefs objectForKey:SPNullValue]]; - } + [fieldEditor setTextMaxLength:fieldLength]; + [fieldEditor setFieldType:fieldType == nil ? @"" : fieldType]; + [fieldEditor setFieldEncoding:fieldEncoding == nil ? @"" : fieldEncoding]; + [fieldEditor setAllowNULL:allowNULL]; - if ([self cellValueIsDisplayedAsHexForColumn:[[tableColumn identifier] integerValue]]) { - [fieldEditor setTextMaxLength:[[self tableView:tableContentView objectValueForTableColumn:tableColumn row:rowIndex] length]]; - isFieldEditable = NO; - } + id cellValue = [tableValues cellDataAtRow:rowIndex column:[[tableColumn identifier] integerValue]]; - NSInteger editedColumn = 0; + if ([cellValue isNSNull]) { + cellValue = [NSString stringWithString:[prefs objectForKey:SPNullValue]]; + } - for (NSTableColumn* col in [tableContentView tableColumns]) - { - if ([[col identifier] isEqualToString:[tableColumn identifier]]) break; + if ([self cellValueIsDisplayedAsHexForColumn:[[tableColumn identifier] integerValue]]) { + [fieldEditor setTextMaxLength:[[self tableView:tableContentView objectValueForTableColumn:tableColumn row:rowIndex] length]]; + isFieldEditable = NO; + } - editedColumn++; - } + NSInteger editedColumn = 0; - [fieldEditor editWithObject:cellValue - fieldName:[[tableColumn headerCell] stringValue] - usingEncoding:[mySQLConnection stringEncoding] - isObjectBlob:isBlob - isEditable:isFieldEditable - withWindow:[tableDocumentInstance parentWindow] - sender:self - contextInfo:[NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithInteger:rowIndex], @"rowIndex", - [NSNumber numberWithInteger:editedColumn], @"columnIndex", - [NSNumber numberWithBool:isFieldEditable], @"isFieldEditable", - nil]]; + for (NSTableColumn* col in [tableContentView tableColumns]) + { + if ([[col identifier] isEqualToString:[tableColumn identifier]]) break; - return NO; + editedColumn++; } - return YES; + [fieldEditor editWithObject:cellValue + fieldName:[[tableColumn headerCell] stringValue] + usingEncoding:[mySQLConnection stringEncoding] + isObjectBlob:isBlob + isEditable:isFieldEditable + withWindow:[tableDocumentInstance parentWindow] + sender:self + contextInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInteger:rowIndex], @"rowIndex", + [NSNumber numberWithInteger:editedColumn], @"columnIndex", + [NSNumber numberWithBool:isFieldEditable], @"isFieldEditable", + nil]]; + + return NO; } + return YES; + } + return YES; } @@ -5002,13 +4274,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper */ - (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)rowIndex { -#ifndef SP_CODA - if (tableView == filterTableView) { - return YES; - } - else -#endif - return tableView == tableContentView ? tableRowsSelectable : YES; + return tableView == tableContentView ? tableRowsSelectable : YES; } /** @@ -5063,76 +4329,62 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper */ - (void)tableView:(SPCopyTable *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex { -#ifndef SP_CODA - if (tableView == filterTableView) { - if (filterTableIsSwapped && [[tableColumn identifier] integerValue] == 0) { - [cell setDrawsBackground:YES]; - [cell setBackgroundColor:lightGrayColor]; - } - else { - [cell setDrawsBackground:NO]; - } + if (tableView == tableContentView) { - return; - } - else -#endif - if (tableView == tableContentView) { + if (![cell respondsToSelector:@selector(setTextColor:)]) return; - if (![cell respondsToSelector:@selector(setTextColor:)]) return; + BOOL cellIsNullOrUnloaded = NO; + BOOL cellIsLinkCell = [cell isMemberOfClass:[SPTextAndLinkCell class]]; - BOOL cellIsNullOrUnloaded = NO; - BOOL cellIsLinkCell = [cell isMemberOfClass:[SPTextAndLinkCell class]]; + NSUInteger columnIndex = [[tableColumn identifier] integerValue]; - NSUInteger columnIndex = [[tableColumn identifier] integerValue]; + // If user wants to edit 'cell' set text color to black and return to avoid + // writing in gray if value was NULL + if ([tableView editedColumn] != -1 + && [tableView editedRow] == rowIndex + && (NSUInteger)[[NSArrayObjectAtIndex([tableView tableColumns], [tableView editedColumn]) identifier] integerValue] == columnIndex) { + [cell setTextColor:blackColor]; + if (cellIsLinkCell) [cell setLinkActive:NO]; + return; + } + + // While the table is being loaded, additional validation is required - data + // locks must be used to avoid crashes, and indexes higher than the available + // rows or columns may be requested. Use gray to indicate loading in these cases. + if (isWorking) { + pthread_mutex_lock(&tableValuesLock); - // If user wants to edit 'cell' set text color to black and return to avoid - // writing in gray if value was NULL - if ([tableView editedColumn] != -1 - && [tableView editedRow] == rowIndex - && (NSUInteger)[[NSArrayObjectAtIndex([tableView tableColumns], [tableView editedColumn]) identifier] integerValue] == columnIndex) { - [cell setTextColor:blackColor]; - if (cellIsLinkCell) [cell setLinkActive:NO]; - return; + if (rowIndex < (NSInteger)tableRowsCount && columnIndex < [tableValues columnCount]) { + cellIsNullOrUnloaded = [tableValues cellIsNullOrUnloadedAtRow:rowIndex column:columnIndex]; } - // While the table is being loaded, additional validation is required - data - // locks must be used to avoid crashes, and indexes higher than the available - // rows or columns may be requested. Use gray to indicate loading in these cases. - if (isWorking) { - pthread_mutex_lock(&tableValuesLock); + pthread_mutex_unlock(&tableValuesLock); + } + else { + cellIsNullOrUnloaded = [tableValues cellIsNullOrUnloadedAtRow:rowIndex column:columnIndex]; + } - if (rowIndex < (NSInteger)tableRowsCount && columnIndex < [tableValues columnCount]) { - cellIsNullOrUnloaded = [tableValues cellIsNullOrUnloadedAtRow:rowIndex column:columnIndex]; - } + if (cellIsNullOrUnloaded) { + [cell setTextColor:rowIndex == [tableContentView selectedRow] ? whiteColor : lightGrayColor]; + } + else { + [cell setTextColor:blackColor]; - pthread_mutex_unlock(&tableValuesLock); - } - else { - cellIsNullOrUnloaded = [tableValues cellIsNullOrUnloadedAtRow:rowIndex column:columnIndex]; + if ([self cellValueIsDisplayedAsHexForColumn:[[tableColumn identifier] integerValue]]) { + [cell setTextColor:rowIndex == [tableContentView selectedRow] ? whiteColor : blueColor]; } + } - if (cellIsNullOrUnloaded) { - [cell setTextColor:rowIndex == [tableContentView selectedRow] ? whiteColor : lightGrayColor]; + // Disable link arrows for the currently editing row and for any NULL or unloaded cells + if (cellIsLinkCell) { + if (cellIsNullOrUnloaded || [tableView editedRow] == rowIndex) { + [cell setLinkActive:NO]; } else { - [cell setTextColor:blackColor]; - - if ([self cellValueIsDisplayedAsHexForColumn:[[tableColumn identifier] integerValue]]) { - [cell setTextColor:rowIndex == [tableContentView selectedRow] ? whiteColor : blueColor]; - } - } - - // Disable link arrows for the currently editing row and for any NULL or unloaded cells - if (cellIsLinkCell) { - if (cellIsNullOrUnloaded || [tableView editedRow] == rowIndex) { - [cell setLinkActive:NO]; - } - else { - [cell setLinkActive:YES]; - } + [cell setLinkActive:YES]; } } + } } #ifndef SP_CODA @@ -5145,10 +4397,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper */ - (NSString *)tableView:(NSTableView *)tableView toolTipForCell:(id)aCell rect:(NSRectPointer)rect tableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row mouseLocation:(NSPoint)mouseLocation { - if (tableView == filterTableView) { - return nil; - } - else if (tableView == tableContentView) { + if (tableView == tableContentView) { if ([[aCell stringValue] length] < 2 || [tableDocumentInstance isWorking]) return nil; @@ -5211,12 +4460,11 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper // Show the cell string value as tooltip (including line breaks and tabs) // by using the cell's font [SPTooltip showWithObject:[aCell stringValue] - atLocation:pos - ofType:@"text" - displayOptions:[NSDictionary dictionaryWithObjectsAndKeys: - [[aCell font] familyName], @"fontname", - [NSString stringWithFormat:@"%f",[[aCell font] pointSize]], @"fontsize", - nil]]; + atLocation:pos + ofType:@"text" + displayOptions:[NSDictionary dictionaryWithObjectsAndKeys:[[aCell font] familyName], @"fontname", + [NSString stringWithFormat:@"%f", [[aCell font] pointSize]], @"fontsize", + nil]]; return nil; } @@ -5225,64 +4473,6 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper } #endif -#ifndef SP_CODA /* SplitView delegate methods */ - -#pragma mark - -#pragma mark SplitView delegate methods - -- (BOOL)splitView:(NSSplitView *)sender canCollapseSubview:(NSView *)subview -{ - return NO; -} - -/** - * Set a minimum size for the filter text area. - */ -- (CGFloat)splitView:(NSSplitView *)sender constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset -{ - return proposedMax - 180; -} - -/** - * Set a minimum size for the field list and action area. - */ -- (CGFloat)splitView:(NSSplitView *)sender constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset -{ - return proposedMin + 225; -} - -/** - * Improve default resizing and resize only the filter text area by default. - */ -- (void)splitView:(NSSplitView *)sender resizeSubviewsWithOldSize:(NSSize)oldSize -{ - NSSize newSize = [sender frame].size; - NSView *leftView = [[sender subviews] objectAtIndex:0]; - NSView *rightView = [[sender subviews] objectAtIndex:1]; - float dividerThickness = [sender dividerThickness]; - NSRect leftFrame = [leftView frame]; - NSRect rightFrame = [rightView frame]; - - // Resize height of both views - leftFrame.size.height = newSize.height; - rightFrame.size.height = newSize.height; - - // Only resize the right view's width - unless the constraint has been reached - if (rightFrame.size.width > 180 || newSize.width > oldSize.width) { - rightFrame.size.width = newSize.width - leftFrame.size.width - dividerThickness; - } - else { - leftFrame.size.width = newSize.width - rightFrame.size.width - dividerThickness; - } - - rightFrame.origin.x = leftFrame.size.width + dividerThickness; - - [leftView setFrame:leftFrame]; - [rightView setFrame:rightFrame]; -} - -#endif - #pragma mark - #pragma mark Control delegate methods @@ -5314,24 +4504,6 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper return YES; } -- (void)controlTextDidChange:(NSNotification *)notification -{ -#ifndef SP_CODA - if ([notification object] == filterTableView) { - - NSString *string = [[[[notification userInfo] objectForKey:@"NSFieldEditor"] textStorage] string]; - - if (string && [string length]) { - if (lastEditedFilterTableValue) [lastEditedFilterTableValue release]; - - lastEditedFilterTableValue = [[NSString stringWithString:string] retain]; - } - - [self updateFilterTableClause:string]; - } -#endif -} - /** * If the user selected a table cell which is a blob field and tried to edit it * cancel the inline edit, display the field editor sheet instead for editing @@ -5454,6 +4626,13 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper { [[NSNotificationCenter defaultCenter] removeObserver:self]; + if(_mainNibLoaded) { + //TODO this should be changed to the variant with …context: after 10.6 support is removed! + [prefs removeObserver:self forKeyPath:SPGlobalResultTableFont]; + [prefs removeObserver:self forKeyPath:SPDisplayBinaryDataAsHex]; + [prefs removeObserver:self forKeyPath:SPDisplayTableViewVerticalGridlines]; + } + // Cancel previous performSelector: requests on ourselves and the table view // to prevent crashes for deferred actions [NSObject cancelPreviousPerformRequestsWithTarget:self]; @@ -5467,17 +4646,11 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper SPClear(dataColumns); SPClear(oldRow); #ifndef SP_CODA - for (id retainedObject in nibObjectsToRelease) [retainedObject release]; - SPClear(nibObjectsToRelease); SPClear(paginationPopover); + SPClear(paginationViewController); - SPClear(filterTableData); - if (lastEditedFilterTableValue) SPClear(lastEditedFilterTableValue); - if (filterTableDefaultOperator) SPClear(filterTableDefaultOperator); #endif if (selectedTable) SPClear(selectedTable); - if (contentFilters) SPClear(contentFilters); - if (numberOfDefaultFilters) SPClear(numberOfDefaultFilters); if (keys) SPClear(keys); if (sortCol) SPClear(sortCol); SPClear(usedQuery); @@ -5485,12 +4658,54 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper if (selectionToRestore) SPClear(selectionToRestore); if (cqColumnDefinition) SPClear(cqColumnDefinition); - if (filterFieldToRestore) filterFieldToRestore = nil; - if (filterComparisonToRestore) filterComparisonToRestore = nil; - if (filterValueToRestore) filterValueToRestore = nil; - if (firstBetweenValueToRestore) firstBetweenValueToRestore = nil; - if (secondBetweenValueToRestore) secondBetweenValueToRestore = nil; + SPClear(filtersToRestore); + + [super dealloc]; +} + +@end + +#pragma mark - +@implementation ContentPaginationViewController + +@synthesize page = page; +@synthesize maxPage = maxPage; +@synthesize target = target; +@synthesize action = action; + +- (instancetype)init +{ + if((self = [super initWithNibName:@"ContentPaginationView" bundle:nil])) { + [self setPage:@1]; + [self setMaxPage:@1]; + } + return self; +} + +- (IBAction)paginationGoAction:(id)sender +{ + if(target && action) [target performSelector:action withObject:self]; +} + +- (void)makeInputFirstResponder +{ + [[paginationPageField window] makeFirstResponder:paginationPageField]; +} + +- (BOOL)isFirstResponderInside +{ + NSResponder *firstResponder = [[paginationPageField window] firstResponder]; + return ( + [firstResponder isKindOfClass:[NSView class]] && + [(NSView *)firstResponder isDescendantOf:[self view]] + ); +} + +- (void)dealloc +{ + [self setPage:nil]; + [self setMaxPage:nil]; [super dealloc]; } diff --git a/Source/SPTableContentFilterController.h b/Source/SPTableContentFilterController.h deleted file mode 100644 index 542f4bad..00000000 --- a/Source/SPTableContentFilterController.h +++ /dev/null @@ -1,21 +0,0 @@ -// This class is a dummy. -// It is only present because DBView.xib already references it, but the -// code itself is still in another branch. This stub is used to avoid a warning -// from the Nib loader, saying 'this class was not found and replaced with a NSObject'. - -#import <Foundation/Foundation.h> - -@class SPSplitView; -@class SPTableData; -@class SPDatabaseDocument; -@class SPTablesList; - -@interface SPTableContentFilterController : NSObject { - IBOutlet SPSplitView *contentSplitView; - IBOutlet NSRuleEditor *filterRuleEditor; - IBOutlet SPTableData *tableDataInstance; - IBOutlet SPDatabaseDocument *tableDocumentInstance; - IBOutlet SPTablesList *tablesListInstance; -} - -@end diff --git a/Source/SPTableContentFilterController.m b/Source/SPTableContentFilterController.m deleted file mode 100644 index 39c0f722..00000000 --- a/Source/SPTableContentFilterController.m +++ /dev/null @@ -1,5 +0,0 @@ -#import "SPTableContentFilterController.h" - -@implementation SPTableContentFilterController - -@end diff --git a/Source/SPTableFilterParser.m b/Source/SPTableFilterParser.m index 93b976f8..ac408baf 100644 --- a/Source/SPTableFilterParser.m +++ b/Source/SPTableFilterParser.m @@ -109,8 +109,7 @@ [clause flushCachedRegexData]; // Escape % sign for format insertion ie if number of arguments is greater than 0 - if(numberOfArguments > 0) - [clause replaceOccurrencesOfRegex:@"%" withString:@"%%"]; + if(numberOfArguments > 0) [clause replaceOccurrencesOfRegex:@"%" withString:@"%%"]; [clause flushCachedRegexData]; // Replace placeholder ${} by %@ diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index 409f85b4..16a3957d 100644 --- a/sequel-pro.xcodeproj/project.pbxproj +++ b/sequel-pro.xcodeproj/project.pbxproj @@ -185,7 +185,7 @@ 503CDBB21ACDC204004F8A2F /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 503CDBB11ACDC204004F8A2F /* Quartz.framework */; }; 505F568F1BCEE485007467DD /* SPFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 507FF1111BBCC57600104523 /* SPFunctions.m */; }; 505F56901BCEE491007467DD /* SPOSInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 50EAB5B71A8FBB08008F627A /* SPOSInfo.m */; }; - 506CE9311A311C6C0039F736 /* SPTableContentFilterController.m in Sources */ = {isa = PBXBuildFile; fileRef = 506CE9301A311C6C0039F736 /* SPTableContentFilterController.m */; }; + 506CE9311A311C6C0039F736 /* SPRuleFilterController.m in Sources */ = {isa = PBXBuildFile; fileRef = 506CE9301A311C6C0039F736 /* SPRuleFilterController.m */; }; 507FF1121BBCC57600104523 /* SPFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 507FF1111BBCC57600104523 /* SPFunctions.m */; }; 507FF1621BBF0D5000104523 /* SPTableCopyTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 112730551180788A000737FD /* SPTableCopyTest.m */; }; 507FF2421BC33BBC00104523 /* SPOSInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 50EAB5B71A8FBB08008F627A /* SPOSInfo.m */; }; @@ -391,6 +391,8 @@ 58FEF57E0F3B4E9700518E8E /* SPTableData.m in Sources */ = {isa = PBXBuildFile; fileRef = 58FEF57D0F3B4E9700518E8E /* SPTableData.m */; }; 73F70A961E4E547500636550 /* SPJSONFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 73F70A951E4E547500636550 /* SPJSONFormatter.m */; }; 8D15AC340486D014006FF6A4 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A7FEA54F5311CA2CBB /* Cocoa.framework */; }; + 9BE765EBBDFD2F121C13D274 /* SPFillView.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BE768F3989033CEDDC2027E /* SPFillView.m */; }; + 9BE76F2886901784E4FD2321 /* SPFilterTableController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BE76A3D5C9830E2F7738770 /* SPFilterTableController.m */; }; B51D6B9E114C310C0074704E /* toolbar-switch-to-table-triggers.png in Resources */ = {isa = PBXBuildFile; fileRef = B51D6B9D114C310C0074704E /* toolbar-switch-to-table-triggers.png */; }; B52460D70F8EF92300171639 /* SPArrayAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = B52460D40F8EF92300171639 /* SPArrayAdditions.m */; }; B52460D80F8EF92300171639 /* SPTextViewAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = B52460D60F8EF92300171639 /* SPTextViewAdditions.m */; }; @@ -411,6 +413,11 @@ B5E2C5FA0F2353B5007446E0 /* table-property.png in Resources */ = {isa = PBXBuildFile; fileRef = B5E2C5F90F2353B5007446E0 /* table-property.png */; }; B5E92F190F75B2D100012500 /* ExportDialog.xib in Resources */ = {isa = PBXBuildFile; fileRef = B5E92F170F75B2D100012500 /* ExportDialog.xib */; }; B5E92F1C0F75B2E800012500 /* SPExportController.m in Sources */ = {isa = PBXBuildFile; fileRef = B5E92F1B0F75B2E800012500 /* SPExportController.m */; }; + BA6B043D20A4FB0B00B012E1 /* button_filter_active@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BA6B043920A4FB0A00B012E1 /* button_filter_active@2x.png */; }; + BA6B043E20A4FB0B00B012E1 /* button_filter@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BA6B043C20A4FB0B00B012E1 /* button_filter@2x.png */; }; + BA6B044120A4FEDC00B012E1 /* button_filter_active.png in Resources */ = {isa = PBXBuildFile; fileRef = BA6B043F20A4FEDB00B012E1 /* button_filter_active.png */; }; + BA6B044220A4FEDC00B012E1 /* button_filter.png in Resources */ = {isa = PBXBuildFile; fileRef = BA6B044020A4FEDC00B012E1 /* button_filter.png */; }; + BAC6BAF920A0D22400247837 /* FilterTableWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = BAC6BAF720A0D22400247837 /* FilterTableWindow.xib */; }; BC01BCCF104024BE006BDEE7 /* SPEncodingPopupAccessory.m in Sources */ = {isa = PBXBuildFile; fileRef = BC01BCCE104024BE006BDEE7 /* SPEncodingPopupAccessory.m */; }; BC05F1C5101241DF008A97F8 /* YRKSpinningProgressIndicator.m in Sources */ = {isa = PBXBuildFile; fileRef = BC05F1C4101241DF008A97F8 /* YRKSpinningProgressIndicator.m */; }; BC09D7DE12A786FB0030DB64 /* cancel-clicked-highlighted.png in Resources */ = {isa = PBXBuildFile; fileRef = BC09D7D812A786FB0030DB64 /* cancel-clicked-highlighted.png */; }; @@ -879,8 +886,8 @@ 503B02C91AE82C5E0060CAB1 /* SPTableFilterParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableFilterParser.m; sourceTree = "<group>"; }; 503B02CE1AE95C2C0060CAB1 /* SPTableFilterParserTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableFilterParserTest.m; sourceTree = "<group>"; }; 503CDBB11ACDC204004F8A2F /* Quartz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quartz.framework; path = System/Library/Frameworks/Quartz.framework; sourceTree = SDKROOT; }; - 506CE92F1A311C6C0039F736 /* SPTableContentFilterController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTableContentFilterController.h; sourceTree = "<group>"; }; - 506CE9301A311C6C0039F736 /* SPTableContentFilterController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableContentFilterController.m; sourceTree = "<group>"; }; + 506CE92F1A311C6C0039F736 /* SPRuleFilterController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPRuleFilterController.h; sourceTree = "<group>"; }; + 506CE9301A311C6C0039F736 /* SPRuleFilterController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPRuleFilterController.m; sourceTree = "<group>"; }; 507FF1101BBCC4C400104523 /* SPFunctions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPFunctions.h; sourceTree = "<group>"; }; 507FF1111BBCC57600104523 /* SPFunctions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPFunctions.m; sourceTree = "<group>"; }; 508022941BF7BA470052A9B2 /* English */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = English; path = English.lproj/SPQLPluginExportSettingsTemplate.html; sourceTree = "<group>"; }; @@ -1127,6 +1134,10 @@ 73F70A941E4E547500636550 /* SPJSONFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPJSONFormatter.h; sourceTree = "<group>"; }; 73F70A951E4E547500636550 /* SPJSONFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPJSONFormatter.m; sourceTree = "<group>"; }; 8D15AC370486D014006FF6A4 /* Sequel Pro.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Sequel Pro.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 9BE768F3989033CEDDC2027E /* SPFillView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPFillView.m; sourceTree = "<group>"; }; + 9BE76A3D5C9830E2F7738770 /* SPFilterTableController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPFilterTableController.m; sourceTree = "<group>"; }; + 9BE76CC0CCE3A4A74E3E8D5E /* SPFillView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPFillView.h; sourceTree = "<group>"; }; + 9BE76F9BF9BDA2921CDD05AF /* SPFilterTableController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPFilterTableController.h; sourceTree = "<group>"; }; B51D6B9D114C310C0074704E /* toolbar-switch-to-table-triggers.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "toolbar-switch-to-table-triggers.png"; sourceTree = "<group>"; }; B52460D30F8EF92300171639 /* SPArrayAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPArrayAdditions.h; sourceTree = "<group>"; }; B52460D40F8EF92300171639 /* SPArrayAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPArrayAdditions.m; sourceTree = "<group>"; }; @@ -1153,6 +1164,11 @@ B5E92F1A0F75B2E800012500 /* SPExportController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPExportController.h; sourceTree = "<group>"; }; B5E92F1B0F75B2E800012500 /* SPExportController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPExportController.m; sourceTree = "<group>"; }; B5EAC0FC0EC87FF900CC579C /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + BA6B043920A4FB0A00B012E1 /* button_filter_active@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "button_filter_active@2x.png"; sourceTree = "<group>"; }; + BA6B043C20A4FB0B00B012E1 /* button_filter@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "button_filter@2x.png"; sourceTree = "<group>"; }; + BA6B043F20A4FEDB00B012E1 /* button_filter_active.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = button_filter_active.png; sourceTree = "<group>"; }; + BA6B044020A4FEDC00B012E1 /* button_filter.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = button_filter.png; sourceTree = "<group>"; }; + BAC6BAF820A0D22400247837 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/FilterTableWindow.xib; sourceTree = "<group>"; }; BC01BCCD104024BE006BDEE7 /* SPEncodingPopupAccessory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPEncodingPopupAccessory.h; sourceTree = "<group>"; }; BC01BCCE104024BE006BDEE7 /* SPEncodingPopupAccessory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPEncodingPopupAccessory.m; sourceTree = "<group>"; }; BC05F1C3101241DF008A97F8 /* YRKSpinningProgressIndicator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YRKSpinningProgressIndicator.h; sourceTree = "<group>"; }; @@ -1552,11 +1568,13 @@ 17A7773311C52D8E001E27B4 /* SPIndexesController.m */, 50A9F8AF19EAD4B90053E571 /* SPGotoDatabaseController.h */, 50A9F8B019EAD4B90053E571 /* SPGotoDatabaseController.m */, - 506CE92F1A311C6C0039F736 /* SPTableContentFilterController.h */, - 506CE9301A311C6C0039F736 /* SPTableContentFilterController.m */, + 506CE92F1A311C6C0039F736 /* SPRuleFilterController.h */, + 506CE9301A311C6C0039F736 /* SPRuleFilterController.m */, 1713C73D140D88D400CFD461 /* Query Controller */, 17381853151FB29C0078FFE2 /* User Manager */, 17846B9D170C95D800414499 /* Process List */, + 9BE76A3D5C9830E2F7738770 /* SPFilterTableController.m */, + 9BE76F9BF9BDA2921CDD05AF /* SPFilterTableController.h */, ); name = "Subview Controllers"; sourceTree = "<group>"; @@ -1971,6 +1989,8 @@ 17DC8827126B32F300E9AAEC /* Table Views */, 17DF51241163C68600E3F396 /* Outline Views */, 17DC8828126B332F00E9AAEC /* Accessory Views */, + 9BE768F3989033CEDDC2027E /* SPFillView.m */, + 9BE76CC0CCE3A4A74E3E8D5E /* SPFillView.h */, ); name = Views; sourceTree = "<group>"; @@ -2014,18 +2034,6 @@ 17E6418B0EF01FF7001BC333 /* Images */ = { isa = PBXGroup; children = ( - 17BFC80D10D3FF5200A3B112 /* Icons */, - 17B548661E82B02100175D5A /* button_bar_handle@2x.png */, - 17B548671E82B02100175D5A /* button_duplicate@2x.png */, - 17B548681E82B02100175D5A /* button_edit_mode_selected@2x.png */, - 17B548691E82B02100175D5A /* button_edit_mode@2x.png */, - 17B5486A1E82B02100175D5A /* button_edit@2x.png */, - 17B5486B1E82B02100175D5A /* button_left@2x.png */, - 17B5486C1E82B02100175D5A /* button_pagination@2x.png */, - 17B5486D1E82B02100175D5A /* button_remove@2x.png */, - 17B5486E1E82B02100175D5A /* button_right@2x.png */, - 17B5486F1E82B02100175D5A /* button_select_all@2x.png */, - 17B548701E82B02100175D5A /* button_select_none@2x.png */, C9AD7C8116761B3300234EEE /* button_action.png */, C9AD7C8216761B3300234EEE /* button_action@2x.png */, 582E93F5168298C6003459FD /* button_add_folder.png */, @@ -2033,20 +2041,31 @@ 582E93FF168299E3003459FD /* button_add.png */, 3876E14F1CC0B26000D85154 /* button_add@2x.png */, 582E940116829B0A003459FD /* button_bar_handle.png */, + 17B548661E82B02100175D5A /* button_bar_handle@2x.png */, C9C9943F1678A439001F5DA8 /* button_bar_spacer.png */, 582E940D1682A2AD003459FD /* button_bar_spacer@2x.png */, 582E94151682A463003459FD /* button_clear.png */, 17AD35F41E79888D000F213E /* button_clear@2x.png */, 582E94171682A4B4003459FD /* button_duplicate.png */, - 582E942316835EA9003459FD /* button_edit.png */, - 582E941E16835DD4003459FD /* button_edit_mode.png */, + 17B548671E82B02100175D5A /* button_duplicate@2x.png */, 582E941D16835DD4003459FD /* button_edit_mode_selected.png */, + 17B548681E82B02100175D5A /* button_edit_mode_selected@2x.png */, + 582E941E16835DD4003459FD /* button_edit_mode.png */, + 17B548691E82B02100175D5A /* button_edit_mode@2x.png */, + 582E942316835EA9003459FD /* button_edit.png */, + 17B5486A1E82B02100175D5A /* button_edit@2x.png */, + BA6B043F20A4FEDB00B012E1 /* button_filter_active.png */, + BA6B043920A4FB0A00B012E1 /* button_filter_active@2x.png */, + BA6B044020A4FEDC00B012E1 /* button_filter.png */, + BA6B043C20A4FB0B00B012E1 /* button_filter@2x.png */, C9AD7C851676204300234EEE /* button_info_pane_hide.png */, C9AD7C861676204300234EEE /* button_info_pane_hide@2x.png */, C9AD7C871676204300234EEE /* button_info_pane_show.png */, C9AD7C881676204300234EEE /* button_info_pane_show@2x.png */, 58C3506410B9A56C00D37E14 /* button_left.png */, + 17B5486B1E82B02100175D5A /* button_left@2x.png */, 58C3506A10B9AA8B00D37E14 /* button_pagination.png */, + 17B5486C1E82B02100175D5A /* button_pagination@2x.png */, C9C9943B1678A117001F5DA8 /* button_pane_hide.png */, C9C9943C1678A117001F5DA8 /* button_pane_hide@2x.png */, C9C994431678A5CD001F5DA8 /* button_pane_show.png */, @@ -2054,42 +2073,47 @@ C9AD7C7D167619B400234EEE /* button_refresh.png */, C9AD7C7E167619B400234EEE /* button_refresh@2x.png */, 582E942516835FAE003459FD /* button_remove.png */, + 17B5486D1E82B02100175D5A /* button_remove@2x.png */, 58C3506610B9A57300D37E14 /* button_right.png */, + 17B5486E1E82B02100175D5A /* button_right@2x.png */, 582E94291683628C003459FD /* button_select_all.png */, + 17B5486F1E82B02100175D5A /* button_select_all@2x.png */, 582E942A1683628C003459FD /* button_select_none.png */, - BC09D7DD12A786FB0030DB64 /* cancel.png */, - BC09D7D912A786FB0030DB64 /* cancel-clicked.png */, + 17B548701E82B02100175D5A /* button_select_none@2x.png */, BC09D7D812A786FB0030DB64 /* cancel-clicked-highlighted.png */, + BC09D7D912A786FB0030DB64 /* cancel-clicked.png */, BC09D7DA12A786FB0030DB64 /* cancel-highlighted.png */, - BC09D7DC12A786FB0030DB64 /* cancel-hovered.png */, BC09D7DB12A786FB0030DB64 /* cancel-hovered-highlighted.png */, + BC09D7DC12A786FB0030DB64 /* cancel-hovered.png */, + BC09D7DD12A786FB0030DB64 /* cancel.png */, 582E942D1683658A003459FD /* clearconsole.png */, - B577483A0F7A8B57003B34F9 /* database.png */, 177E792B0FCB54EC00E9E122 /* database-small.png */, C9AD7C771676138000234EEE /* database-small@2x.png */, + B577483A0F7A8B57003B34F9 /* database.png */, 177E792C0FCB54EC00E9E122 /* dummy-small.png */, 582E9449168374C1003459FD /* field-small-square.png */, 384582C30FB95FF800DDACB6 /* func-small.png */, 17E6419D0EF02036001BC333 /* grabber-horizontal.png */, 17E6419E0EF02036001BC333 /* grabber-vertical.png */, 582E944B16837986003459FD /* hideconsole.png */, + 17BFC80D10D3FF5200A3B112 /* Icons */, 5843DA68161FA35600EAA6D1 /* key-icon-alternate.png */, 5843DA69161FA35600EAA6D1 /* key-icon-alternate@2x.png */, 5843DA6A161FA35600EAA6D1 /* key-icon.png */, 5843DA6B161FA35600EAA6D1 /* key-icon@2x.png */, 58E205FB1234FE4F00A97059 /* KeyTemplate.pdf */, - 58D2E22D101222870063EF1D /* link-arrow.png */, - 582E939C168296F3003459FD /* link-arrow@2x.png */, 58D2E22B101222870063EF1D /* link-arrow-clicked.png */, 582E9399168296F3003459FD /* link-arrow-clicked@2x.png */, - 581068B51015411B0068C6E2 /* link-arrow-highlighted.png */, - 582E939B168296F3003459FD /* link-arrow-highlighted@2x.png */, 58D2E22C101222870063EF1D /* link-arrow-highlighted-clicked.png */, 582E939A168296F3003459FD /* link-arrow-highlighted-clicked@2x.png */, + 581068B51015411B0068C6E2 /* link-arrow-highlighted.png */, + 582E939B168296F3003459FD /* link-arrow-highlighted@2x.png */, + 58D2E22D101222870063EF1D /* link-arrow.png */, + 582E939C168296F3003459FD /* link-arrow@2x.png */, 582E944F16837AA9003459FD /* network-small.png */, 384582C60FB9603600DDACB6 /* proc-small.png */, - 58F48AA2161D03C6008536A1 /* quick-connect-icon.pdf */, 58F48B2D161D08C0008536A1 /* quick-connect-icon-white.pdf */, + 58F48AA2161D03C6008536A1 /* quick-connect-icon.pdf */, 50F530511ABCF66B002F2C1A /* reset.pdf */, 582E946F16837DB2003459FD /* showconsole.png */, 588B2CC50FE5641E00EC5FC0 /* ssh-connected.png */, @@ -2102,12 +2126,12 @@ 582E9481168380D6003459FD /* sync_arrows_05.png */, 582E9482168380D6003459FD /* sync_arrows_06.png */, B5E2C5F90F2353B5007446E0 /* table-property.png */, - C9C9944B1678BCFA001F5DA8 /* table-small.png */, - C9C9944C1678BCFA001F5DA8 /* table-small@2x.png */, C9C994471678B3E6001F5DA8 /* table-small-square.png */, C9C994481678B3E6001F5DA8 /* table-small-square@2x.png */, - 582E948E168383F0003459FD /* table-view-small.png */, + C9C9944B1678BCFA001F5DA8 /* table-small.png */, + C9C9944C1678BCFA001F5DA8 /* table-small@2x.png */, 582E948D168383F0003459FD /* table-view-small-square.png */, + 582E948E168383F0003459FD /* table-view-small.png */, 58A137CC123ED5E6000B1B75 /* titlebarlock.png */, 582E94A716839AD5003459FD /* toolbar-preferences-autoupdate.png */, 582E94A916839AEF003459FD /* toolbar-preferences-general.png */, @@ -2158,6 +2182,7 @@ BCCBD7FA104408B000D4C50A /* SaveSPFAccessory.xib */, B58DA7390FF8BB9E00FDDACD /* SSHQuestionDialog.xib */, 4D90B7A0101E0D1500D116A1 /* UserManagerView.xib */, + BAC6BAF720A0D22400247837 /* FilterTableWindow.xib */, ); path = Interfaces; sourceTree = "<group>"; @@ -2829,6 +2854,7 @@ 17E641F20EF02036001BC333 /* toolbar-switch-to-structure.png in Resources */, 17E641F30EF02036001BC333 /* toolbar-switch-to-table-info.png in Resources */, 17E6420A0EF020CB001BC333 /* DBView.xib in Resources */, + BA6B043D20A4FB0B00B012E1 /* button_filter_active@2x.png in Resources */, 17E641FA0EF02088001BC333 /* Growl Registration Ticket.growlRegDict in Resources */, 17E641FC0EF02088001BC333 /* sequel-pro.scriptSuite in Resources */, 3876E1501CC0B26000D85154 /* button_add@2x.png in Resources */, @@ -2847,6 +2873,7 @@ BC2C8E220FA8C2DB008468C7 /* SPMySQLHelpTemplate.html in Resources */, 384582C40FB95FF800DDACB6 /* func-small.png in Resources */, 384582C70FB9603600DDACB6 /* proc-small.png in Resources */, + BAC6BAF920A0D22400247837 /* FilterTableWindow.xib in Resources */, 177E792E0FCB54EC00E9E122 /* database-small.png in Resources */, 177E792F0FCB54EC00E9E122 /* dummy-small.png in Resources */, B58DA73C0FF8BBA500FDDACD /* PrintAccessory.xib in Resources */, @@ -2860,6 +2887,7 @@ BCA6F631100FA7D700E80253 /* FieldEditorSheet.xib in Resources */, 58D2E22E101222870063EF1D /* link-arrow-clicked.png in Resources */, 58D2E22F101222870063EF1D /* link-arrow-highlighted-clicked.png in Resources */, + BA6B044120A4FEDC00B012E1 /* button_filter_active.png in Resources */, 58D2E230101222870063EF1D /* link-arrow.png in Resources */, 17B548781E82B02100175D5A /* button_remove@2x.png in Resources */, 581068B61015411B0068C6E2 /* link-arrow-highlighted.png in Resources */, @@ -2913,7 +2941,9 @@ 5843DA6E161FA35600EAA6D1 /* key-icon.png in Resources */, 5843DA6F161FA35600EAA6D1 /* key-icon@2x.png in Resources */, C9F92710162D38D70051CB2E /* toolbar-switch-to-table-info@2x.png in Resources */, + BA6B044220A4FEDC00B012E1 /* button_filter.png in Resources */, C9F92712162D39E60051CB2E /* toolbar-switch-to-browse.png in Resources */, + BA6B043E20A4FB0B00B012E1 /* button_filter@2x.png in Resources */, C9F92714162D39FE0051CB2E /* toolbar-switch-to-browse@2x.png in Resources */, 17B5487B1E82B02100175D5A /* button_select_none@2x.png in Resources */, C9AD7C781676138000234EEE /* database-small@2x.png in Resources */, @@ -3233,7 +3263,7 @@ 1785EB6A127DD79300F468C8 /* SPEditorPreferencePane.m in Sources */, 17FDB04C1280778B00DBBBC2 /* SPFontPreviewTextField.m in Sources */, 17D3C22212859E070047709F /* SPFavoriteNode.m in Sources */, - 506CE9311A311C6C0039F736 /* SPTableContentFilterController.m in Sources */, + 506CE9311A311C6C0039F736 /* SPRuleFilterController.m in Sources */, 17D3C66E128AD4710047709F /* SPFavoritesController.m in Sources */, 17D3C671128AD8160047709F /* SPSingleton.m in Sources */, 17D3C6D3128B1C900047709F /* SPFavoritesOutlineView.m in Sources */, @@ -3267,6 +3297,8 @@ 50E217B318174246009D3580 /* SPColorSelectorView.m in Sources */, 50E217B618174280009D3580 /* SPFavoriteColorSupport.m in Sources */, 1A564F74237E2E4958CA593A /* SPPillAttachmentCell.m in Sources */, + 9BE76F2886901784E4FD2321 /* SPFilterTableController.m in Sources */, + 9BE765EBBDFD2F121C13D274 /* SPFillView.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3542,6 +3574,14 @@ name = ExportDialog.xib; sourceTree = "<group>"; }; + BAC6BAF720A0D22400247837 /* FilterTableWindow.xib */ = { + isa = PBXVariantGroup; + children = ( + BAC6BAF820A0D22400247837 /* English */, + ); + name = FilterTableWindow.xib; + sourceTree = "<group>"; + }; BC30C00F111C98BD002701C9 /* DataMigrationDialog.xib */ = { isa = PBXVariantGroup; children = ( |