Tree with reordering and core data storage + undo

This commit is contained in:
relikd
2018-08-02 14:02:00 +02:00
parent 1de26a7a88
commit 6db2607105
8 changed files with 263 additions and 106 deletions

View File

@@ -34,7 +34,6 @@
54ACC28821061B3C0020715F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/Main.xib; sourceTree = "<group>"; }; 54ACC28821061B3C0020715F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/Main.xib; sourceTree = "<group>"; };
54ACC28A21061B3C0020715F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 54ACC28A21061B3C0020715F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
54ACC28B21061B3C0020715F /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; }; 54ACC28B21061B3C0020715F /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
54ACC28D21061B3C0020715F /* baRSS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = baRSS.entitlements; sourceTree = "<group>"; };
54ACC29321061E270020715F /* NewsController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NewsController.h; sourceTree = "<group>"; }; 54ACC29321061E270020715F /* NewsController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NewsController.h; sourceTree = "<group>"; };
54ACC29421061E270020715F /* NewsController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NewsController.m; sourceTree = "<group>"; }; 54ACC29421061E270020715F /* NewsController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NewsController.m; sourceTree = "<group>"; };
54ACC29621061FBA0020715F /* Preferences.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Preferences.h; sourceTree = "<group>"; }; 54ACC29621061FBA0020715F /* Preferences.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Preferences.h; sourceTree = "<group>"; };
@@ -103,7 +102,6 @@
54ACC28721061B3C0020715F /* Main.xib */, 54ACC28721061B3C0020715F /* Main.xib */,
54ACC28A21061B3C0020715F /* Info.plist */, 54ACC28A21061B3C0020715F /* Info.plist */,
54ACC28B21061B3C0020715F /* main.m */, 54ACC28B21061B3C0020715F /* main.m */,
54ACC28D21061B3C0020715F /* baRSS.entitlements */,
54ACC28221061B3B0020715F /* DBv1.xcdatamodeld */, 54ACC28221061B3B0020715F /* DBv1.xcdatamodeld */,
); );
path = baRSS; path = baRSS;
@@ -146,7 +144,7 @@
enabled = 0; enabled = 0;
}; };
com.apple.Sandbox = { com.apple.Sandbox = {
enabled = 1; enabled = 0;
}; };
}; };
}; };
@@ -325,21 +323,34 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CLANG_UNDEFINED_BEHAVIOR_SANITIZER_INTEGER = YES;
CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES;
CLANG_WARN_ASSIGN_ENUM = YES;
CLANG_WARN_CXX0X_EXTENSIONS = YES; CLANG_WARN_CXX0X_EXTENSIONS = YES;
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES; CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES;
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES; CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CODE_SIGN_ENTITLEMENTS = baRSS/baRSS.entitlements; CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = UY657LKNHJ; DEVELOPMENT_TEAM = "";
EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE = NO; EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)", "$(PROJECT_DIR)",
); );
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES; GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES;
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
GCC_WARN_PEDANTIC = YES;
GCC_WARN_SHADOW = YES;
GCC_WARN_SIGN_COMPARE = YES; GCC_WARN_SIGN_COMPARE = YES;
GCC_WARN_STRICT_SELECTOR_MATCH = YES; GCC_WARN_STRICT_SELECTOR_MATCH = YES;
GCC_WARN_UNKNOWN_PRAGMAS = YES; GCC_WARN_UNKNOWN_PRAGMAS = YES;
@@ -352,8 +363,8 @@
MACOSX_DEPLOYMENT_TARGET = 10.12; MACOSX_DEPLOYMENT_TARGET = 10.12;
PRODUCT_BUNDLE_IDENTIFIER = de.relikd.baRSS; PRODUCT_BUNDLE_IDENTIFIER = de.relikd.baRSS;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 3.0;
}; };
name = Debug; name = Debug;
}; };
@@ -361,21 +372,34 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CLANG_UNDEFINED_BEHAVIOR_SANITIZER_INTEGER = YES;
CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES;
CLANG_WARN_ASSIGN_ENUM = YES;
CLANG_WARN_CXX0X_EXTENSIONS = YES; CLANG_WARN_CXX0X_EXTENSIONS = YES;
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES; CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES;
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES; CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CODE_SIGN_ENTITLEMENTS = baRSS/baRSS.entitlements; CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = UY657LKNHJ; DEVELOPMENT_TEAM = "";
EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE = NO; EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)", "$(PROJECT_DIR)",
); );
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES; GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES;
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
GCC_WARN_PEDANTIC = YES;
GCC_WARN_SHADOW = YES;
GCC_WARN_SIGN_COMPARE = YES; GCC_WARN_SIGN_COMPARE = YES;
GCC_WARN_STRICT_SELECTOR_MATCH = YES; GCC_WARN_STRICT_SELECTOR_MATCH = YES;
GCC_WARN_UNKNOWN_PRAGMAS = YES; GCC_WARN_UNKNOWN_PRAGMAS = YES;
@@ -388,7 +412,7 @@
MACOSX_DEPLOYMENT_TARGET = 10.12; MACOSX_DEPLOYMENT_TARGET = 10.12;
PRODUCT_BUNDLE_IDENTIFIER = de.relikd.baRSS; PRODUCT_BUNDLE_IDENTIFIER = de.relikd.baRSS;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0; PROVISIONING_PROFILE_SPECIFIER = "";
}; };
name = Release; name = Release;
}; };

View File

@@ -60,21 +60,13 @@
_persistentContainer = [[NSPersistentContainer alloc] initWithName:@"DBv1"]; _persistentContainer = [[NSPersistentContainer alloc] initWithName:@"DBv1"];
[_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) { [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
if (error != nil) { if (error != nil) {
// Replace this implementation with code to handle the error appropriately. NSLog(@"Couldn't read NSPersistentContainer: %@, %@", error, error.userInfo);
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
NSLog(@"Unresolved error %@, %@", error, error.userInfo);
abort(); abort();
} }
}]; }];
NSUndoManager *um = [[NSUndoManager alloc] init];
um.levelsOfUndo = 30;
_persistentContainer.viewContext.undoManager = um;
} }
} }

View File

@@ -19,16 +19,19 @@
</connections> </connections>
</customObject> </customObject>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/> <customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<customObject id="he0-Eb-PDA" customClass="NewsController"> <treeController mode="entity" entityName="FeedConfig" fetchPredicateFormat="parent == nil" automaticallyPreparesContent="YES" childrenKeyPath="children" leafKeyPath="type" id="1oZ-Uo-mIu" customClass="NewsController">
<connections> <connections>
<outlet property="openUnreadItem" destination="Qqw-Xj-oA5" id="8df-is-Qop"/> <binding destination="Voe-Tx-rLC" name="managedObjectContext" keyPath="self.persistentContainer.viewContext" id="Q1f-VN-9qq"/>
<outlet property="pauseItem" destination="D7r-Vb-9eO" id="o7e-jD-3Yj"/> <outlet property="openUnreadItem" destination="Qqw-Xj-oA5" id="JN3-Ej-EPc"/>
<outlet property="updateAllItem" destination="wgp-fa-8Wj" id="edP-bQ-bIM"/> <outlet property="outlineView" destination="hKk-G1-1po" id="Z4y-uD-Zse"/>
<outlet property="pauseItem" destination="D7r-Vb-9eO" id="8Cu-bX-0uT"/>
<outlet property="updateAllItem" destination="wgp-fa-8Wj" id="dI0-8A-qaY"/>
</connections> </connections>
</customObject> </treeController>
<customObject id="bgB-po-1IK" customClass="Preferences"> <customObject id="bgB-po-1IK" customClass="Preferences">
<connections> <connections>
<outlet property="appDelegate" destination="Voe-Tx-rLC" id="1aD-dC-e6r"/> <outlet property="feedsOutline" destination="hKk-G1-1po" id="jcw-Fq-DyK"/>
<outlet property="newsController" destination="1oZ-Uo-mIu" id="hAk-g4-Oii"/>
<outlet property="toolbar" destination="wLj-fD-HpE" id="bMA-dd-1H1"/> <outlet property="toolbar" destination="wLj-fD-HpE" id="bMA-dd-1H1"/>
<outlet property="viewFeeds" destination="EwH-fT-yW8" id="pmQ-as-O2V"/> <outlet property="viewFeeds" destination="EwH-fT-yW8" id="pmQ-as-O2V"/>
<outlet property="viewGeneral" destination="8H4-sI-Ub8" id="lWU-Il-4in"/> <outlet property="viewGeneral" destination="8H4-sI-Ub8" id="lWU-Il-4in"/>
@@ -40,26 +43,26 @@
<menuItem title="Pause Updates" id="D7r-Vb-9eO"> <menuItem title="Pause Updates" id="D7r-Vb-9eO">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<connections> <connections>
<action selector="pauseUpdates:" target="he0-Eb-PDA" id="9Nm-b4-h4h"/> <action selector="pauseUpdates:" target="1oZ-Uo-mIu" id="WaC-Gv-Jlh"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Update All Feeds" id="wgp-fa-8Wj"> <menuItem title="Update All Feeds" id="wgp-fa-8Wj">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<connections> <connections>
<action selector="updateAllFeeds:" target="he0-Eb-PDA" id="z7p-Ax-hgc"/> <action selector="updateAllFeeds:" target="1oZ-Uo-mIu" id="5yX-Gc-9Qg"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Open All Unread" id="Qqw-Xj-oA5"> <menuItem title="Open All Unread" id="Qqw-Xj-oA5">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<connections> <connections>
<action selector="openAllUnread:" target="he0-Eb-PDA" id="bgo-Tt-0sv"/> <action selector="openAllUnread:" target="1oZ-Uo-mIu" id="dpt-U6-GPp"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem isSeparatorItem="YES" id="ld2-b0-07a"/> <menuItem isSeparatorItem="YES" id="ld2-b0-07a"/>
<menuItem isSeparatorItem="YES" id="1VS-wM-kTc"/> <menuItem isSeparatorItem="YES" id="1VS-wM-kTc"/>
<menuItem title="Preferences" keyEquivalent="," id="VFY-eR-2EA"> <menuItem title="Preferences" keyEquivalent="," id="VFY-eR-2EA">
<connections> <connections>
<action selector="showWindow:" target="bgB-po-1IK" id="eBk-Wq-1eA"/> <action selector="makeKeyAndOrderFront:" target="ai3-RW-O8g" id="qaE-j2-fux"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem title="Quit" keyEquivalent="q" id="Nb6-yK-a1A"> <menuItem title="Quit" keyEquivalent="q" id="Nb6-yK-a1A">
@@ -318,14 +321,14 @@
<rect key="frame" x="1" y="0.0" width="318" height="306"/> <rect key="frame" x="1" y="0.0" width="318" height="306"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" alternatingRowBackgroundColors="YES" multipleSelection="NO" autosaveColumns="NO" rowSizeStyle="automatic" headerView="sii-NE-JkJ" viewBased="YES" indentationPerLevel="16" outlineTableColumn="Lb1-9n-wlc" id="hKk-G1-1po"> <outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="firstColumnOnly" alternatingRowBackgroundColors="YES" autosaveColumns="NO" headerView="sii-NE-JkJ" indentationPerLevel="16" outlineTableColumn="Lb1-9n-wlc" id="hKk-G1-1po">
<rect key="frame" x="0.0" y="0.0" width="318" height="283"/> <rect key="frame" x="0.0" y="0.0" width="318" height="283"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<size key="intercellSpacing" width="3" height="2"/> <size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/> <color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns> <tableColumns>
<tableColumn identifier="" editable="NO" width="252.5" minWidth="40" maxWidth="1000" id="Lb1-9n-wlc"> <tableColumn identifier="" width="252.5" minWidth="40" maxWidth="1000" id="Lb1-9n-wlc">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Name"> <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Name">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
@@ -337,29 +340,17 @@
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES"/> <tableColumnResizingMask key="resizingMask" resizeWithTable="YES"/>
<prototypeCellViews>
<tableCellView id="6yl-oX-eNn">
<rect key="frame" x="1" y="1" width="253" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Z97-we-rgn">
<rect key="frame" x="0.0" y="0.0" width="253" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="FGY-QQ-joq">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections> <connections>
<outlet property="textField" destination="Z97-we-rgn" id="Xfe-pC-8kn"/> <binding destination="1oZ-Uo-mIu" name="value" keyPath="arrangedObjects.name" id="Lgo-HL-51z">
<dictionary key="options">
<bool key="NSConditionallySetsEditable" value="YES"/>
<string key="NSNullPlaceholder">TUUP</string>
</dictionary>
</binding>
</connections> </connections>
</tableCellView>
</prototypeCellViews>
</tableColumn> </tableColumn>
<tableColumn identifier="" editable="NO" width="59.5" minWidth="40" maxWidth="1000" id="8st-OH-BXG"> <tableColumn identifier="" width="59.5" minWidth="40" maxWidth="1000" id="8st-OH-BXG">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Update"> <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Refresh">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
@@ -369,11 +360,13 @@
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
<connections>
<binding destination="1oZ-Uo-mIu" name="value" keyPath="arrangedObjects.refresh" id="R51-fy-Ufu"/>
</connections>
</tableColumn> </tableColumn>
</tableColumns> </tableColumns>
<connections> <connections>
<outlet property="dataSource" destination="he0-Eb-PDA" id="bid-ep-fYr"/> <outlet property="dataSource" destination="1oZ-Uo-mIu" id="sch-o5-yEm"/>
<outlet property="delegate" destination="he0-Eb-PDA" id="FV8-3T-QDV"/>
</connections> </connections>
</outlineView> </outlineView>
</subviews> </subviews>
@@ -399,7 +392,8 @@
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
</buttonCell> </buttonCell>
<connections> <connections>
<action selector="addFeed:" target="he0-Eb-PDA" id="dC1-tw-xxy"/> <action selector="add:" target="1oZ-Uo-mIu" id="9rc-sz-RXa"/>
<binding destination="1oZ-Uo-mIu" name="enabled" keyPath="canInsert" id="1xq-Nj-Acq"/>
</connections> </connections>
</button> </button>
<button toolTip="Delete item" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kwZ-TS-nM7"> <button toolTip="Delete item" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kwZ-TS-nM7">
@@ -410,7 +404,8 @@
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
</buttonCell> </buttonCell>
<connections> <connections>
<action selector="removeFeed:" target="he0-Eb-PDA" id="C4r-3D-ARB"/> <action selector="remove:" target="1oZ-Uo-mIu" id="mph-g8-8J0"/>
<binding destination="1oZ-Uo-mIu" name="enabled" keyPath="canRemove" id="9oY-fm-uq0"/>
</connections> </connections>
</button> </button>
<button toolTip="Add new line separator" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="BRb-Je-jM4"> <button toolTip="Add new line separator" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="BRb-Je-jM4">
@@ -421,7 +416,7 @@
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
</buttonCell> </buttonCell>
<connections> <connections>
<action selector="addSeparator:" target="he0-Eb-PDA" id="68L-xT-tM9"/> <action selector="addSeparator:" target="1oZ-Uo-mIu" id="bs4-Jk-EWD"/>
</connections> </connections>
</button> </button>
<button toolTip="Add new grouping folder" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kQF-LC-quC"> <button toolTip="Add new grouping folder" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kQF-LC-quC">
@@ -432,7 +427,7 @@
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
</buttonCell> </buttonCell>
<connections> <connections>
<action selector="addGroup:" target="he0-Eb-PDA" id="82S-0f-6d9"/> <action selector="addGroup:" target="1oZ-Uo-mIu" id="8MO-eQ-XcJ"/>
</connections> </connections>
</button> </button>
<button hidden="YES" toolTip="Import or Export data" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="XaQ-S9-guo"> <button hidden="YES" toolTip="Import or Export data" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="XaQ-S9-guo">

View File

@@ -10,8 +10,19 @@
<attribute name="published" optional="YES" attributeType="Transformable" customClassName="NSArray" syncable="YES"/> <attribute name="published" optional="YES" attributeType="Transformable" customClassName="NSArray" syncable="YES"/>
<attribute name="subtitle" optional="YES" attributeType="String" syncable="YES"/> <attribute name="subtitle" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="title" optional="YES" attributeType="String" syncable="YES"/> <attribute name="title" optional="YES" attributeType="String" syncable="YES"/>
<relationship name="config" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="FeedConfig" inverseName="feed" inverseEntity="FeedConfig" syncable="YES"/>
<relationship name="items" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="FeedItem" inverseName="feed" inverseEntity="FeedItem" syncable="YES"/> <relationship name="items" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="FeedItem" inverseName="feed" inverseEntity="FeedItem" syncable="YES"/>
</entity> </entity>
<entity name="FeedConfig" representedClassName="FeedConfig" syncable="YES" codeGenerationType="class">
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="refresh" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="sortIndex" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES" syncable="YES"/>
<attribute name="type" optional="YES" attributeType="Integer 16" defaultValueString="1" usesScalarValueType="YES" syncable="YES"/>
<attribute name="url" optional="YES" attributeType="String" syncable="YES"/>
<relationship name="children" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="FeedConfig" inverseName="parent" inverseEntity="FeedConfig" syncable="YES"/>
<relationship name="feed" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="Feed" inverseName="config" inverseEntity="Feed" syncable="YES"/>
<relationship name="parent" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="FeedConfig" inverseName="children" inverseEntity="FeedConfig" syncable="YES"/>
</entity>
<entity name="FeedItem" representedClassName="FeedItem" syncable="YES" codeGenerationType="class"> <entity name="FeedItem" representedClassName="FeedItem" syncable="YES" codeGenerationType="class">
<attribute name="author" optional="YES" attributeType="String" syncable="YES"/> <attribute name="author" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="link" optional="YES" attributeType="String" syncable="YES"/> <attribute name="link" optional="YES" attributeType="String" syncable="YES"/>
@@ -27,8 +38,9 @@
<relationship name="feedItem" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="FeedItem" inverseName="tags" inverseEntity="FeedItem" syncable="YES"/> <relationship name="feedItem" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="FeedItem" inverseName="tags" inverseEntity="FeedItem" syncable="YES"/>
</entity> </entity>
<elements> <elements>
<element name="FeedItem" positionX="-36" positionY="-36" width="128" height="165"/> <element name="Feed" positionX="-209" positionY="-3" width="128" height="210"/>
<element name="FeedTag" positionX="0" positionY="54" width="128" height="75"/> <element name="FeedConfig" positionX="-20" positionY="-126" width="128" height="165"/>
<element name="Feed" positionX="27" positionY="9" width="128" height="195"/> <element name="FeedItem" positionX="-20" positionY="81" width="128" height="165"/>
<element name="FeedTag" positionX="187" positionY="171" width="128" height="75"/>
</elements> </elements>
</model> </model>

View File

@@ -22,5 +22,10 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
@interface NewsController : NSViewController <NSOutlineViewDataSource, NSOutlineViewDelegate> @interface NewsController : NSTreeController <NSOutlineViewDataSource, NSOutlineViewDelegate>
- (IBAction)addFeed:(NSButton *)sender;
- (IBAction)addGroup:(NSButton *)sender;
- (IBAction)addSeparator:(NSButton *)sender;
- (NSString*)copyDescriptionOfSelectedItems;
@end @end

View File

@@ -26,22 +26,28 @@
#import "DBv1+CoreDataModel.h" #import "DBv1+CoreDataModel.h"
@interface NewsController () @interface NewsController ()
@property (weak) IBOutlet NSOutlineView *outlineView;
@property (weak) IBOutlet NSMenuItem *pauseItem; @property (weak) IBOutlet NSMenuItem *pauseItem;
@property (weak) IBOutlet NSMenuItem *updateAllItem; @property (weak) IBOutlet NSMenuItem *updateAllItem;
@property (weak) IBOutlet NSMenuItem *openUnreadItem; @property (weak) IBOutlet NSMenuItem *openUnreadItem;
@property (retain) NSManagedObjectContext *managedContext;
@property (strong) NSArray<NSTreeNode*> *currentlyDraggedNodes;
@end @end
@implementation NewsController @implementation NewsController
// Declare a string constant for the drag type - to be used when writing and retrieving pasteboard data...
static NSString *dragNodeType = @"baRSS-feed-type";
- (void)awakeFromNib { - (void)awakeFromNib {
[super awakeFromNib]; [super awakeFromNib];
self.managedContext = [((AppDelegate*)[NSApp delegate]) persistentContainer].viewContext; // Set the outline view to accept the custom drag type AbstractTreeNodeType...
[self.outlineView registerForDraggedTypes:[NSArray arrayWithObject:dragNodeType]];
[self setSortDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"sortIndex" ascending:YES]]];
} }
- (IBAction)pauseUpdates:(NSMenuItem *)sender { - (IBAction)pauseUpdates:(NSMenuItem *)sender {
NSLog(@"pause"); NSLog(@"pause");
NSLog(@"%@", self.managedContext);
} }
- (IBAction)updateAllFeeds:(NSMenuItem *)sender { - (IBAction)updateAllFeeds:(NSMenuItem *)sender {
NSLog(@"update all"); NSLog(@"update all");
@@ -49,7 +55,7 @@
NSLog(@"obj = %@", obj); NSLog(@"obj = %@", obj);
// TODO: check status code // TODO: check status code
/* /*
Feed *a = [[Feed alloc] initWithEntity:Feed.entity insertIntoManagedObjectContext:self.managedContext]; Feed *a = [[Feed alloc] initWithEntity:Feed.entity insertIntoManagedObjectContext:self.managedObjectContext];
a.title = obj[@"feed"][@"title"]; a.title = obj[@"feed"][@"title"];
a.subtitle = obj[@"feed"][@"subtitle"]; a.subtitle = obj[@"feed"][@"subtitle"];
a.author = obj[@"feed"][@"author"]; a.author = obj[@"feed"][@"author"];
@@ -60,7 +66,7 @@
a.date = obj[@"header"][@"date"]; a.date = obj[@"header"][@"date"];
a.modified = obj[@"header"][@"modified"]; a.modified = obj[@"header"][@"modified"];
for (NSDictionary *entry in obj[@"entries"]) { for (NSDictionary *entry in obj[@"entries"]) {
FeedItem *b = [[FeedItem alloc] initWithEntity:FeedItem.entity insertIntoManagedObjectContext:self.managedContext]; FeedItem *b = [[FeedItem alloc] initWithEntity:FeedItem.entity insertIntoManagedObjectContext:self.managedObjectContext];
b.title = entry[@"title"]; b.title = entry[@"title"];
b.subtitle = entry[@"subtitle"]; b.subtitle = entry[@"subtitle"];
b.author = entry[@"author"]; b.author = entry[@"author"];
@@ -68,45 +74,157 @@
b.published = entry[@"published"]; b.published = entry[@"published"];
b.summary = entry[@"summary"]; b.summary = entry[@"summary"];
for (NSString *tag in entry[@"tags"]) { for (NSString *tag in entry[@"tags"]) {
FeedTag *c = [[FeedTag alloc] initWithEntity:FeedTag.entity insertIntoManagedObjectContext:self.managedContext]; FeedTag *c = [[FeedTag alloc] initWithEntity:FeedTag.entity insertIntoManagedObjectContext:self.managedObjectContext];
c.name = tag; c.name = tag;
[b addTagsObject:c]; [b addTagsObject:c];
} }
[a addItemsObject:b]; [a addItemsObject:b];
}*/ }*/
} }
- (IBAction)openAllUnread:(NSMenuItem *)sender { - (IBAction)openAllUnread:(NSMenuItem *)sender {
NSLog(@"all unread"); NSLog(@"all unread");
} }
- (IBAction)addFeed:(NSButton *)sender { - (IBAction)addFeed:(NSButton *)sender {
NSLog(@"add feed"); NSLog(@"add feed");
NSLog(@"%@", self.managedContext);
}
- (IBAction)removeFeed:(NSButton *)sender {
NSLog(@"del feed");
} }
- (IBAction)addGroup:(NSButton *)sender { - (IBAction)addGroup:(NSButton *)sender {
FeedConfig *g = [[FeedConfig alloc] initWithEntity:FeedConfig.entity insertIntoManagedObjectContext:self.managedObjectContext];
g.name = @"Group";
g.type = 0;
NSLog(@"add group"); NSLog(@"add group");
} }
- (IBAction)addSeparator:(NSButton *)sender { - (IBAction)addSeparator:(NSButton *)sender {
NSLog(@"add separator"); NSLog(@"add separator");
// [self.managedObjectContext.undoManager beginUndoGrouping];
// [self.managedObjectContext.undoManager endUndoGrouping];
} }
- (NSString*)copyDescriptionOfSelectedItems {
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item { NSMutableString *str = [[NSMutableString alloc] init];
return 1; for (FeedConfig *item in self.selectedObjects) {
[self traverseChildren:item appendString:str indentation:0];
}
[[NSPasteboard generalPasteboard] clearContents];
[[NSPasteboard generalPasteboard] setString:str forType:NSPasteboardTypeString];
NSLog(@"%@", str);
return str;
} }
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item { - (void)traverseChildren:(FeedConfig*)obj appendString:(NSMutableString*)str indentation:(int)indent {
return NO; for (int i = indent; i > 0; i--) {
[str appendString:@" "];
}
switch (obj.type) {
case 0: [str appendFormat:@"%@:\n", obj.name]; break; // Group
case 2: [str appendString:@"-------------\n"]; break; // Separator
default: [str appendFormat:@"%@ (%@) - %@\n", obj.name, obj.url, obj.refresh];
}
for (FeedConfig *child in obj.children) {
[self traverseChildren:child appendString:str indentation:indent + 1];
}
} }
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item { - (FeedConfig*)insertNewItemAtCurrentSelection {
return @"du"; FeedConfig *selected = [[[self arrangedObjects] descendantNodeAtIndexPath:[self selectionIndexPath]] representedObject];
FeedConfig *newItem = [[FeedConfig alloc] initWithEntity:FeedConfig.entity insertIntoManagedObjectContext:self.managedObjectContext];
if (selected.type == 0) // a group
newItem.sortIndex = (int32_t)selected.children.count;
else
newItem.sortIndex = selected.sortIndex + 1;
return newItem;
} }
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item { - (void)incrementIndicesBy:(int)val forSubsequentNodes:(NSIndexPath*)path {
return @"hi"; NSIndexPath *parentPath = [path indexPathByRemovingLastIndex];
NSTreeNode *root = [self arrangedObjects];
if (parentPath.length > 0)
root = [root descendantNodeAtIndexPath:parentPath];
for (NSUInteger i = [path indexAtPosition:path.length - 1]; i < root.childNodes.count; i++) {
((FeedConfig*)[root.childNodes[i] representedObject]).sortIndex += val;
}
}
#pragma mark - Dragging Support, Data Source Delegate
- (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard {
[self.managedObjectContext.undoManager beginUndoGrouping];
[pboard declareTypes:[NSArray arrayWithObject:dragNodeType] owner:self];
[pboard setString:@"dragging" forType:dragNodeType];
self.currentlyDraggedNodes = items;
return YES;
}
- (void)outlineView:(NSOutlineView *)outlineView draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation {
self.currentlyDraggedNodes = nil;
[self.managedObjectContext.undoManager endUndoGrouping];
if ([self.managedObjectContext hasChanges]) {
NSError *err;
[self.managedObjectContext save:&err];
if (err) NSLog(@"Error: %@", err);
}
}
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index {
NSArray<NSTreeNode *> *dstChildren = [item childNodes];
if (!item || !dstChildren)
dstChildren = [self arrangedObjects].childNodes;
bool isFolderDrag = (index == -1);
NSUInteger insertIndex = (isFolderDrag ? dstChildren.count : (NSUInteger)index);
// index where the items will be moved to, but not final since items above can vanish
NSIndexPath *dest = [item indexPath];
if (!dest) dest = [NSIndexPath indexPathWithIndex:insertIndex];
else dest = [dest indexPathByAddingIndex:insertIndex];
// decrement index for every item that is dragged from the same location (above the destination)
NSUInteger updateIndex = insertIndex;
for (NSTreeNode *node in self.currentlyDraggedNodes) {
NSIndexPath *nodesPath = [node indexPath];
if ([[nodesPath indexPathByRemovingLastIndex] isEqualTo:[dest indexPathByRemovingLastIndex]] &&
insertIndex > [nodesPath indexAtPosition:nodesPath.length - 1])
{
--updateIndex;
}
}
// decrement sort indices at source
for (NSTreeNode *node in self.currentlyDraggedNodes)
[self incrementIndicesBy:-1 forSubsequentNodes:[node indexPath]];
// increment sort indices at destination
if (!isFolderDrag)
[self incrementIndicesBy:(int)self.currentlyDraggedNodes.count forSubsequentNodes:dest];
// move items
[self moveNodes:self.currentlyDraggedNodes toIndexPath:dest];
// set sort indices for dragged items
for (NSUInteger i = 0; i < self.currentlyDraggedNodes.count; i++) {
FeedConfig *fc = [self.currentlyDraggedNodes[i] representedObject];
fc.sortIndex = (int32_t)(updateIndex + i);
}
return YES;
}
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index {
FeedConfig *fc = [(NSTreeNode*)item representedObject];
if (index == -1 && fc.type != 0) { // if drag is on specific item and that item isnt a group
return NSDragOperationNone;
}
NSTreeNode *parent = item;
while (parent != nil) {
for (NSTreeNode *node in self.currentlyDraggedNodes) {
if (parent == node)
return NSDragOperationNone; // cannot move items into a child of its own
}
parent = [parent parentNode];
}
return NSDragOperationGeneric;
} }
@end @end

View File

@@ -21,13 +21,15 @@
// SOFTWARE. // SOFTWARE.
#import "Preferences.h" #import "Preferences.h"
#import "AppDelegate.h" #import "NewsController.h"
@interface Preferences () @interface Preferences ()
@property (weak) IBOutlet NSToolbar *toolbar; @property (weak) IBOutlet NSToolbar *toolbar;
@property (weak) IBOutlet NSView *viewGeneral; @property (weak) IBOutlet NSView *viewGeneral;
@property (weak) IBOutlet NSView *viewFeeds; @property (weak) IBOutlet NSView *viewFeeds;
@property (weak) IBOutlet AppDelegate *appDelegate;
@property (weak) IBOutlet NewsController *newsController;
@property (weak) IBOutlet NSOutlineView *feedsOutline;
@end @end
@implementation Preferences @implementation Preferences
@@ -53,12 +55,33 @@
- (void)keyDown:(NSEvent *)event { - (void)keyDown:(NSEvent *)event {
if (event.modifierFlags & NSEventModifierFlagCommand) { if (event.modifierFlags & NSEventModifierFlagCommand) {
if ([event.characters isEqualToString:@"w"]) { bool holdShift = event.modifierFlags & NSEventModifierFlagShift;
[self close]; @try {
} else if ([event.characters isEqualToString:@"q"]) { unichar key = [event.characters characterAtIndex:0];
[self.appDelegate quitClicked:self]; switch (key) {
case 'w': [self close]; break;
case 'q': [NSApplication.sharedApplication terminate:self]; break;
}
if (self.window.contentView == self.viewFeeds) { // these only apply for NSOutlineView
switch (key) {
case 'z':
if (holdShift) [self.newsController.managedObjectContext.undoManager redo];
else [self.newsController.managedObjectContext.undoManager undo];
[self.newsController rearrangeObjects]; // update the ordering
break;
case 'n': [self.newsController addFeed:nil]; break;
case 'o': break; // open .opml file
case 's': break; // save data or backup .opml file
case 'c': // copy row entry
[self.newsController copyDescriptionOfSelectedItems];
break;
case 'a': [self.feedsOutline selectAll:nil]; break;
// TODO: delete
}
}
} @catch (NSException *exception) {
NSLog(@"%@", event);
} }
// TODO: new, delete, ...
} }
} }

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>