├── .gitignore ├── .swiftlint.yml ├── LICENSE ├── README.md ├── pock_headline_news.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── pock_headline_news ├── Info.plist ├── Sources │ ├── Extension │ │ ├── NSColor.swift │ │ ├── NSTextField.swift │ │ └── NSViewController.swift │ ├── HeadLineNewsView.swift │ ├── HeadLineNewsWidget.swift │ ├── Model │ │ └── Item.swift │ ├── Preferences │ │ ├── HeadlineNewsWidgetPreferencePane │ │ │ ├── HeadlineNewsWidgetPreferencePane.swift │ │ │ └── HeadlineNewsWidgetPreferencePane.xib │ │ ├── Preferences.swift │ │ ├── TextFieldAlert.swift │ │ ├── TextFieldAlert.xib │ │ └── TextFieldTableViewCell.swift │ └── RSSParser.swift └── pock_headline_news-Bridging-Header.h └── resources └── demo.gif /.gitignore: -------------------------------------------------------------------------------- 1 | xcuserdata/ 2 | *.xcworkspace 3 | 4 | Pods/* 5 | *.lock 6 | .DS_Store -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | #- block_based_kvo 3 | #- class_delegate_protocol 4 | #- closing_brace 5 | #- closure_parameter_position 6 | #- colon 7 | #- comma 8 | #- comment_spacing 9 | #- compiler_protocol_init 10 | #- computed_accessors_order 11 | #- control_statement 12 | #- custom_rules 13 | #- cyclomatic_complexity 14 | #- deployment_target 15 | #- discouraged_direct_init 16 | #- duplicate_enum_cases 17 | #- duplicate_imports 18 | #- dynamic_inline 19 | #- empty_enum_arguments 20 | #- empty_parameters 21 | #- empty_parentheses_with_trailing_closure 22 | #- file_length 23 | #- for_where 24 | - force_cast 25 | #- force_try 26 | #- function_body_length 27 | #- function_parameter_count 28 | #- generic_type_name 29 | #- identifier_name 30 | #- implicit_getter 31 | #- inclusive_language 32 | #- inert_defer 33 | #- is_disjoint 34 | #- large_tuple 35 | #- leading_whitespace 36 | #- legacy_cggeometry_functions 37 | #- legacy_constant 38 | #- legacy_constructor 39 | #- legacy_hashing 40 | #- legacy_nsgeometry_functions 41 | #- line_length 42 | #- mark 43 | #- multiple_closures_with_trailing_closure 44 | #- nesting 45 | #- no_fallthrough_only 46 | #- no_space_in_method_call 47 | #- notification_center_detachment 48 | #- nsobject_prefer_isequal 49 | #- opening_brace 50 | #- operator_whitespace 51 | #- orphaned_doc_comment 52 | #- private_over_fileprivate 53 | #- private_unit_test 54 | #- protocol_property_accessors_order 55 | #- reduce_boolean 56 | #- redundant_discardable_let 57 | #- redundant_objc_attribute 58 | #- redundant_optional_initialization 59 | #- redundant_set_access_control 60 | #- redundant_string_enum_value 61 | #- redundant_void_return 62 | #- return_arrow_whitespace 63 | #- shorthand_operator 64 | #- statement_position 65 | #- superfluous_disable_command 66 | #- switch_case_alignment 67 | #- syntactic_sugar 68 | #- todo 69 | #- trailing_comma 70 | #- trailing_newline 71 | #- trailing_semicolon 72 | #- trailing_whitespace 73 | #- type_body_length 74 | #- type_name 75 | #- unneeded_break_in_switch 76 | #- unused_capture_list 77 | #- unused_closure_parameter 78 | #- unused_control_flow_label 79 | #- unused_enumerated 80 | #- unused_optional_binding 81 | #- unused_setter_value 82 | #- valid_ibinspectable 83 | #- vertical_parameter_alignment 84 | #- vertical_whitespace 85 | #- void_return 86 | #- weak_delegate 87 | #- xctfail_message 88 | 89 | 90 | opt_in_rules: 91 | - anyobject_protocol 92 | - array_init 93 | - attributes 94 | - balanced_xctest_lifecycle 95 | - capture_variable 96 | - closure_body_length 97 | - closure_end_indentation 98 | - closure_spacing 99 | - collection_alignment 100 | - conditional_returns_on_newline 101 | - contains_over_filter_count 102 | - contains_over_filter_is_empty 103 | - contains_over_first_not_nil 104 | - contains_over_range_nil_comparison 105 | - convenience_type 106 | - discarded_notification_center_observer 107 | - discouraged_assert 108 | #- discouraged_object_literal 109 | - discouraged_optional_boolean 110 | - discouraged_optional_collection 111 | - empty_collection_literal 112 | - empty_count 113 | - empty_string 114 | - empty_xctest_method 115 | - enum_case_associated_values_count 116 | - expiring_todo 117 | #- explicit_acl 118 | #- explicit_enum_raw_value 119 | - explicit_init 120 | #- explicit_self 121 | #- explicit_top_level_acl 122 | #- explicit_type_interface 123 | #- extension_access_modifier 124 | - fallthrough 125 | - fatal_error_message 126 | #- file_header 127 | - file_name 128 | - file_name_no_space 129 | - file_types_order 130 | - first_where 131 | - flatmap_over_map_reduce 132 | - force_unwrapping 133 | - function_default_parameter_at_end 134 | - ibinspectable_in_extension 135 | - identical_operands 136 | - implicit_return 137 | #- implicitly_unwrapped_optional 138 | #- indentation_width 139 | - joined_default_parameter 140 | - last_where 141 | - legacy_multiple 142 | #- legacy_objc_type 143 | - legacy_random 144 | - let_var_whitespace 145 | - literal_expression_end_indentation 146 | - lower_acl_than_parent 147 | - missing_docs 148 | - modifier_order 149 | #- multiline_arguments 150 | #- multiline_arguments_brackets 151 | #- multiline_function_chains 152 | #- multiline_literal_brackets 153 | #- multiline_parameters 154 | #- multiline_parameters_brackets 155 | - nimble_operator 156 | #- no_extension_access_modifier 157 | #- no_grouping_extension 158 | - nslocalizedstring_key 159 | - nslocalizedstring_require_bundle 160 | #- number_separator 161 | #- object_literal 162 | - operator_usage_whitespace 163 | - optional_enum_case_matching 164 | - overridden_super_call 165 | - override_in_extension 166 | - pattern_matching_keywords 167 | - prefer_nimble 168 | - prefer_self_type_over_type_of_self 169 | - prefer_zero_over_explicit_init 170 | #- prefixed_toplevel_constant 171 | - private_action 172 | - private_outlet 173 | - private_subject 174 | #- prohibited_interface_builder 175 | - prohibited_super_call 176 | - quick_discouraged_call 177 | - quick_discouraged_focused_test 178 | - quick_discouraged_pending_test 179 | - raw_value_for_camel_cased_codable_enum 180 | - reduce_into 181 | - redundant_nil_coalescing 182 | - redundant_type_annotation 183 | #- required_deinit 184 | - required_enum_case 185 | - single_test_class 186 | - sorted_first_last 187 | #- sorted_imports 188 | - static_operator 189 | - strict_fileprivate 190 | #- strong_iboutlet 191 | - switch_case_on_newline 192 | - test_case_accessibility 193 | - toggle_bool 194 | - trailing_closure 195 | - type_contents_order 196 | - unavailable_function 197 | - unneeded_parentheses_in_closure_argument 198 | - unowned_variable_capture 199 | - untyped_error_in_catch 200 | - unused_declaration 201 | - unused_import 202 | - vertical_parameter_alignment_on_call 203 | #- vertical_whitespace_between_cases 204 | #- vertical_whitespace_closing_braces 205 | #- vertical_whitespace_opening_braces 206 | - xct_specific_matcher 207 | - yoda_condition 208 | 209 | included: 210 | - {pock-timer-widget} 211 | 212 | excluded: 213 | - Pods 214 | - Carthage 215 | - SourcePackages 216 | - Generated 217 | 218 | line_length: 219 | warning: 300 220 | error: 500 221 | 222 | identifier_name: 223 | min_length: 224 | warning: 1 225 | excluded: 226 | - id 227 | - URL 228 | - GlobalAPIKey 229 | 230 | file_name: 231 | excluded: 232 | - main.swift 233 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 p-x9 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Headline News Widget for Pock 2 | This is a headline news widget plugin for [Pock](https://github.com/pock/pock) 3 | You can display the articles fetched by rss. 4 | 5 | 6 | 7 | 8 | [![Github issues](https://img.shields.io/github/issues/p-x9/pock-headline-news-widget)](https://github.com/p-x9/pock-headline-news-widget/issues) 9 | [![Github forks](https://img.shields.io/github/forks/p-x9/pock-headline-news-widget)](https://github.com/p-x9/pock-headline-news-widget/network/members) 10 | [![Github stars](https://img.shields.io/github/stars/p-x9/pock-headline-news-widget)](https://github.com/p-x9/pock-headline-news-widget/stargazers) 11 | [![Github top language](https://img.shields.io/github/languages/top/p-x9/pock-headline-news-widget)](https://github.com/p-x9/pock-headline-news-widget/) 12 | 13 | ## Demo 14 | In the demo video, the following rss url is used. 15 | https://news.yahoo.co.jp/rss/topics/top-picks.xml 16 | 17 | ![Demo](https://user-images.githubusercontent.com/50244599/134106222-3d05f442-9d75-43ce-b417-01b529ac330c.gif) 18 | 19 | 20 | ## Usage 21 | It's very simple to use. 22 | - tap: start animating. If you tap during the animation, the full article will open in your browser. 23 | - long press: stop 24 | 25 | You can customize following properties 26 | - text animating speed 27 | - text font 28 | - text font color 29 | - background color 30 | 31 | 32 | ## Installation 33 | 1. download '.pock.zip' file from [here](https://github.com/p-x9/pock-headline-news-widget/releases) and unzip. 34 | 2. double click it and reload Pock. 35 | 3. open 'Customize Touch Bar' panel and add this widget. 36 | 4. open WidgetManager panel and set your rss url. 37 | -------------------------------------------------------------------------------- /pock_headline_news.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4200788F26F8B43F007AE2DF /* TextFieldTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4200788E26F8B43F007AE2DF /* TextFieldTableViewCell.swift */; }; 11 | 4200789626F8EB4F007AE2DF /* TextFieldAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4200789426F8EB4F007AE2DF /* TextFieldAlert.swift */; }; 12 | 4200789726F8EB4F007AE2DF /* TextFieldAlert.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4200789526F8EB4F007AE2DF /* TextFieldAlert.xib */; }; 13 | 422F66CF26F2049400AEFAE0 /* HeadLineNewsWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422F66CE26F2049400AEFAE0 /* HeadLineNewsWidget.swift */; }; 14 | 422F66E926F31F2900AEFAE0 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422F66E826F31F2900AEFAE0 /* Item.swift */; }; 15 | 422F66EB26F31F6800AEFAE0 /* RSSParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422F66EA26F31F6800AEFAE0 /* RSSParser.swift */; }; 16 | 422F66ED26F3A08D00AEFAE0 /* NSColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422F66EC26F3A08D00AEFAE0 /* NSColor.swift */; }; 17 | 422F66FA26F6191A00AEFAE0 /* HeadlineNewsWidgetPreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422F66F826F6191A00AEFAE0 /* HeadlineNewsWidgetPreferencePane.swift */; }; 18 | 422F66FB26F6191A00AEFAE0 /* HeadlineNewsWidgetPreferencePane.xib in Resources */ = {isa = PBXBuildFile; fileRef = 422F66F926F6191A00AEFAE0 /* HeadlineNewsWidgetPreferencePane.xib */; }; 19 | 422F66FD26F6393D00AEFAE0 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422F66FC26F6393D00AEFAE0 /* Preferences.swift */; }; 20 | 422F670126F6620700AEFAE0 /* HeadLineNewsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422F670026F6620700AEFAE0 /* HeadLineNewsView.swift */; }; 21 | 422F670426F73D9C00AEFAE0 /* NSViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422F670326F73D9C00AEFAE0 /* NSViewController.swift */; }; 22 | 4246EF612712EB0F003541E9 /* PockKit in Frameworks */ = {isa = PBXBuildFile; productRef = 4246EF602712EB0F003541E9 /* PockKit */; }; 23 | 4246EF642712EB40003541E9 /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = 4246EF632712EB40003541E9 /* Defaults */; }; 24 | 4246EF672712EB70003541E9 /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 4246EF662712EB70003541E9 /* SnapKit */; }; 25 | 42476943274D5BF700D4761C /* NSTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42476942274D5BF700D4761C /* NSTextField.swift */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXFileReference section */ 29 | 4200788E26F8B43F007AE2DF /* TextFieldTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldTableViewCell.swift; sourceTree = ""; }; 30 | 4200789426F8EB4F007AE2DF /* TextFieldAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldAlert.swift; sourceTree = ""; }; 31 | 4200789526F8EB4F007AE2DF /* TextFieldAlert.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TextFieldAlert.xib; sourceTree = ""; }; 32 | 422F66C426F2020200AEFAE0 /* pock_headline_news.pock */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = pock_headline_news.pock; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | 422F66C726F2020200AEFAE0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 34 | 422F66CD26F2049400AEFAE0 /* pock_headline_news-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "pock_headline_news-Bridging-Header.h"; sourceTree = ""; }; 35 | 422F66CE26F2049400AEFAE0 /* HeadLineNewsWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadLineNewsWidget.swift; sourceTree = ""; }; 36 | 422F66E826F31F2900AEFAE0 /* Item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Item.swift; sourceTree = ""; }; 37 | 422F66EA26F31F6800AEFAE0 /* RSSParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RSSParser.swift; sourceTree = ""; }; 38 | 422F66EC26F3A08D00AEFAE0 /* NSColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSColor.swift; sourceTree = ""; }; 39 | 422F66F826F6191A00AEFAE0 /* HeadlineNewsWidgetPreferencePane.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineNewsWidgetPreferencePane.swift; sourceTree = ""; }; 40 | 422F66F926F6191A00AEFAE0 /* HeadlineNewsWidgetPreferencePane.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = HeadlineNewsWidgetPreferencePane.xib; sourceTree = ""; }; 41 | 422F66FC26F6393D00AEFAE0 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; 42 | 422F670026F6620700AEFAE0 /* HeadLineNewsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadLineNewsView.swift; sourceTree = ""; }; 43 | 422F670226F6F4D400AEFAE0 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 44 | 422F670326F73D9C00AEFAE0 /* NSViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSViewController.swift; sourceTree = ""; }; 45 | 42476942274D5BF700D4761C /* NSTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSTextField.swift; sourceTree = ""; }; 46 | /* End PBXFileReference section */ 47 | 48 | /* Begin PBXFrameworksBuildPhase section */ 49 | 422F66C126F2020200AEFAE0 /* Frameworks */ = { 50 | isa = PBXFrameworksBuildPhase; 51 | buildActionMask = 2147483647; 52 | files = ( 53 | 4246EF672712EB70003541E9 /* SnapKit in Frameworks */, 54 | 4246EF642712EB40003541E9 /* Defaults in Frameworks */, 55 | 4246EF612712EB0F003541E9 /* PockKit in Frameworks */, 56 | ); 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | /* End PBXFrameworksBuildPhase section */ 60 | 61 | /* Begin PBXGroup section */ 62 | 422F66BB26F2020200AEFAE0 = { 63 | isa = PBXGroup; 64 | children = ( 65 | 422F670226F6F4D400AEFAE0 /* README.md */, 66 | 422F66C626F2020200AEFAE0 /* pock_headline_news */, 67 | 422F66C526F2020200AEFAE0 /* Products */, 68 | ); 69 | sourceTree = ""; 70 | }; 71 | 422F66C526F2020200AEFAE0 /* Products */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 422F66C426F2020200AEFAE0 /* pock_headline_news.pock */, 75 | ); 76 | name = Products; 77 | sourceTree = ""; 78 | }; 79 | 422F66C626F2020200AEFAE0 /* pock_headline_news */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 422F66C726F2020200AEFAE0 /* Info.plist */, 83 | 422F66CD26F2049400AEFAE0 /* pock_headline_news-Bridging-Header.h */, 84 | 422F66EF26F6185200AEFAE0 /* Sources */, 85 | ); 86 | path = pock_headline_news; 87 | sourceTree = ""; 88 | }; 89 | 422F66EF26F6185200AEFAE0 /* Sources */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 422F66CE26F2049400AEFAE0 /* HeadLineNewsWidget.swift */, 93 | 422F670026F6620700AEFAE0 /* HeadLineNewsView.swift */, 94 | 422F66EA26F31F6800AEFAE0 /* RSSParser.swift */, 95 | 422F66F026F6186900AEFAE0 /* Model */, 96 | 422F66F126F6187700AEFAE0 /* Extension */, 97 | 422F66F226F6188700AEFAE0 /* Preferences */, 98 | ); 99 | path = Sources; 100 | sourceTree = ""; 101 | }; 102 | 422F66F026F6186900AEFAE0 /* Model */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 422F66E826F31F2900AEFAE0 /* Item.swift */, 106 | ); 107 | path = Model; 108 | sourceTree = ""; 109 | }; 110 | 422F66F126F6187700AEFAE0 /* Extension */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | 422F66EC26F3A08D00AEFAE0 /* NSColor.swift */, 114 | 422F670326F73D9C00AEFAE0 /* NSViewController.swift */, 115 | 42476942274D5BF700D4761C /* NSTextField.swift */, 116 | ); 117 | path = Extension; 118 | sourceTree = ""; 119 | }; 120 | 422F66F226F6188700AEFAE0 /* Preferences */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 422F66FC26F6393D00AEFAE0 /* Preferences.swift */, 124 | 4200788E26F8B43F007AE2DF /* TextFieldTableViewCell.swift */, 125 | 4200789426F8EB4F007AE2DF /* TextFieldAlert.swift */, 126 | 4200789526F8EB4F007AE2DF /* TextFieldAlert.xib */, 127 | 422F66F726F618FE00AEFAE0 /* HeadlineNewsWidgetPreferencePane */, 128 | ); 129 | path = Preferences; 130 | sourceTree = ""; 131 | }; 132 | 422F66F726F618FE00AEFAE0 /* HeadlineNewsWidgetPreferencePane */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | 422F66F826F6191A00AEFAE0 /* HeadlineNewsWidgetPreferencePane.swift */, 136 | 422F66F926F6191A00AEFAE0 /* HeadlineNewsWidgetPreferencePane.xib */, 137 | ); 138 | path = HeadlineNewsWidgetPreferencePane; 139 | sourceTree = ""; 140 | }; 141 | /* End PBXGroup section */ 142 | 143 | /* Begin PBXNativeTarget section */ 144 | 422F66C326F2020200AEFAE0 /* pock_headline_news */ = { 145 | isa = PBXNativeTarget; 146 | buildConfigurationList = 422F66CA26F2020200AEFAE0 /* Build configuration list for PBXNativeTarget "pock_headline_news" */; 147 | buildPhases = ( 148 | 422F66C026F2020200AEFAE0 /* Sources */, 149 | 422F66C126F2020200AEFAE0 /* Frameworks */, 150 | 422F66C226F2020200AEFAE0 /* Resources */, 151 | 422F66EE26F3A27400AEFAE0 /* Run SwiftLint */, 152 | 422F66D226F240E300AEFAE0 /* Debug Script */, 153 | ); 154 | buildRules = ( 155 | ); 156 | dependencies = ( 157 | ); 158 | name = pock_headline_news; 159 | packageProductDependencies = ( 160 | 4246EF602712EB0F003541E9 /* PockKit */, 161 | 4246EF632712EB40003541E9 /* Defaults */, 162 | 4246EF662712EB70003541E9 /* SnapKit */, 163 | ); 164 | productName = pock_headline_news; 165 | productReference = 422F66C426F2020200AEFAE0 /* pock_headline_news.pock */; 166 | productType = "com.apple.product-type.bundle"; 167 | }; 168 | /* End PBXNativeTarget section */ 169 | 170 | /* Begin PBXProject section */ 171 | 422F66BC26F2020200AEFAE0 /* Project object */ = { 172 | isa = PBXProject; 173 | attributes = { 174 | LastUpgradeCheck = 1250; 175 | TargetAttributes = { 176 | 422F66C326F2020200AEFAE0 = { 177 | CreatedOnToolsVersion = 12.5.1; 178 | LastSwiftMigration = 1250; 179 | }; 180 | }; 181 | }; 182 | buildConfigurationList = 422F66BF26F2020200AEFAE0 /* Build configuration list for PBXProject "pock_headline_news" */; 183 | compatibilityVersion = "Xcode 9.3"; 184 | developmentRegion = en; 185 | hasScannedForEncodings = 0; 186 | knownRegions = ( 187 | en, 188 | Base, 189 | ); 190 | mainGroup = 422F66BB26F2020200AEFAE0; 191 | packageReferences = ( 192 | 4246EF5F2712EB0F003541E9 /* XCRemoteSwiftPackageReference "pockkit" */, 193 | 4246EF622712EB40003541E9 /* XCRemoteSwiftPackageReference "Defaults" */, 194 | 4246EF652712EB6F003541E9 /* XCRemoteSwiftPackageReference "SnapKit" */, 195 | ); 196 | productRefGroup = 422F66C526F2020200AEFAE0 /* Products */; 197 | projectDirPath = ""; 198 | projectRoot = ""; 199 | targets = ( 200 | 422F66C326F2020200AEFAE0 /* pock_headline_news */, 201 | ); 202 | }; 203 | /* End PBXProject section */ 204 | 205 | /* Begin PBXResourcesBuildPhase section */ 206 | 422F66C226F2020200AEFAE0 /* Resources */ = { 207 | isa = PBXResourcesBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | 422F66FB26F6191A00AEFAE0 /* HeadlineNewsWidgetPreferencePane.xib in Resources */, 211 | 4200789726F8EB4F007AE2DF /* TextFieldAlert.xib in Resources */, 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | }; 215 | /* End PBXResourcesBuildPhase section */ 216 | 217 | /* Begin PBXShellScriptBuildPhase section */ 218 | 422F66D226F240E300AEFAE0 /* Debug Script */ = { 219 | isa = PBXShellScriptBuildPhase; 220 | buildActionMask = 2147483647; 221 | files = ( 222 | ); 223 | inputFileListPaths = ( 224 | ); 225 | inputPaths = ( 226 | ); 227 | name = "Debug Script"; 228 | outputFileListPaths = ( 229 | ); 230 | outputPaths = ( 231 | ); 232 | runOnlyForDeploymentPostprocessing = 0; 233 | shellPath = /bin/sh; 234 | shellScript = "if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n echo \"Copying ${PRODUCT_NAME}.pock to destination folder\"\n cp -r $TARGET_BUILD_DIR/$PRODUCT_NAME.pock ~/Library/Application\\ Support/Pock/Widgets/\nfi\n\n"; 235 | }; 236 | 422F66EE26F3A27400AEFAE0 /* Run SwiftLint */ = { 237 | isa = PBXShellScriptBuildPhase; 238 | buildActionMask = 2147483647; 239 | files = ( 240 | ); 241 | inputFileListPaths = ( 242 | ); 243 | inputPaths = ( 244 | ); 245 | name = "Run SwiftLint"; 246 | outputFileListPaths = ( 247 | ); 248 | outputPaths = ( 249 | ); 250 | runOnlyForDeploymentPostprocessing = 0; 251 | shellPath = /bin/sh; 252 | shellScript = "if which swiftlint >/dev/null; then\n swiftlint --fix\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; 253 | }; 254 | /* End PBXShellScriptBuildPhase section */ 255 | 256 | /* Begin PBXSourcesBuildPhase section */ 257 | 422F66C026F2020200AEFAE0 /* Sources */ = { 258 | isa = PBXSourcesBuildPhase; 259 | buildActionMask = 2147483647; 260 | files = ( 261 | 42476943274D5BF700D4761C /* NSTextField.swift in Sources */, 262 | 422F66ED26F3A08D00AEFAE0 /* NSColor.swift in Sources */, 263 | 4200789626F8EB4F007AE2DF /* TextFieldAlert.swift in Sources */, 264 | 422F670426F73D9C00AEFAE0 /* NSViewController.swift in Sources */, 265 | 422F670126F6620700AEFAE0 /* HeadLineNewsView.swift in Sources */, 266 | 422F66EB26F31F6800AEFAE0 /* RSSParser.swift in Sources */, 267 | 422F66FD26F6393D00AEFAE0 /* Preferences.swift in Sources */, 268 | 422F66FA26F6191A00AEFAE0 /* HeadlineNewsWidgetPreferencePane.swift in Sources */, 269 | 4200788F26F8B43F007AE2DF /* TextFieldTableViewCell.swift in Sources */, 270 | 422F66E926F31F2900AEFAE0 /* Item.swift in Sources */, 271 | 422F66CF26F2049400AEFAE0 /* HeadLineNewsWidget.swift in Sources */, 272 | ); 273 | runOnlyForDeploymentPostprocessing = 0; 274 | }; 275 | /* End PBXSourcesBuildPhase section */ 276 | 277 | /* Begin XCBuildConfiguration section */ 278 | 422F66C826F2020200AEFAE0 /* Debug */ = { 279 | isa = XCBuildConfiguration; 280 | buildSettings = { 281 | ALWAYS_SEARCH_USER_PATHS = NO; 282 | CLANG_ANALYZER_NONNULL = YES; 283 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 284 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 285 | CLANG_CXX_LIBRARY = "libc++"; 286 | CLANG_ENABLE_MODULES = YES; 287 | CLANG_ENABLE_OBJC_ARC = YES; 288 | CLANG_ENABLE_OBJC_WEAK = YES; 289 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 290 | CLANG_WARN_BOOL_CONVERSION = YES; 291 | CLANG_WARN_COMMA = YES; 292 | CLANG_WARN_CONSTANT_CONVERSION = YES; 293 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 294 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 295 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 296 | CLANG_WARN_EMPTY_BODY = YES; 297 | CLANG_WARN_ENUM_CONVERSION = YES; 298 | CLANG_WARN_INFINITE_RECURSION = YES; 299 | CLANG_WARN_INT_CONVERSION = YES; 300 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 301 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 302 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 303 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 304 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 305 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 306 | CLANG_WARN_STRICT_PROTOTYPES = YES; 307 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 308 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 309 | CLANG_WARN_UNREACHABLE_CODE = YES; 310 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 311 | COPY_PHASE_STRIP = NO; 312 | DEBUG_INFORMATION_FORMAT = dwarf; 313 | ENABLE_STRICT_OBJC_MSGSEND = YES; 314 | ENABLE_TESTABILITY = YES; 315 | GCC_C_LANGUAGE_STANDARD = gnu11; 316 | GCC_DYNAMIC_NO_PIC = NO; 317 | GCC_NO_COMMON_BLOCKS = YES; 318 | GCC_OPTIMIZATION_LEVEL = 0; 319 | GCC_PREPROCESSOR_DEFINITIONS = ( 320 | "DEBUG=1", 321 | "$(inherited)", 322 | ); 323 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 324 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 325 | GCC_WARN_UNDECLARED_SELECTOR = YES; 326 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 327 | GCC_WARN_UNUSED_FUNCTION = YES; 328 | GCC_WARN_UNUSED_VARIABLE = YES; 329 | MACOSX_DEPLOYMENT_TARGET = 11.3; 330 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 331 | MTL_FAST_MATH = YES; 332 | ONLY_ACTIVE_ARCH = YES; 333 | SDKROOT = macosx; 334 | }; 335 | name = Debug; 336 | }; 337 | 422F66C926F2020200AEFAE0 /* Release */ = { 338 | isa = XCBuildConfiguration; 339 | buildSettings = { 340 | ALWAYS_SEARCH_USER_PATHS = NO; 341 | CLANG_ANALYZER_NONNULL = YES; 342 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 343 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 344 | CLANG_CXX_LIBRARY = "libc++"; 345 | CLANG_ENABLE_MODULES = YES; 346 | CLANG_ENABLE_OBJC_ARC = YES; 347 | CLANG_ENABLE_OBJC_WEAK = YES; 348 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 349 | CLANG_WARN_BOOL_CONVERSION = YES; 350 | CLANG_WARN_COMMA = YES; 351 | CLANG_WARN_CONSTANT_CONVERSION = YES; 352 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 353 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 354 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 355 | CLANG_WARN_EMPTY_BODY = YES; 356 | CLANG_WARN_ENUM_CONVERSION = YES; 357 | CLANG_WARN_INFINITE_RECURSION = YES; 358 | CLANG_WARN_INT_CONVERSION = YES; 359 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 360 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 361 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 362 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 363 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 364 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 365 | CLANG_WARN_STRICT_PROTOTYPES = YES; 366 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 367 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 368 | CLANG_WARN_UNREACHABLE_CODE = YES; 369 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 370 | COPY_PHASE_STRIP = NO; 371 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 372 | ENABLE_NS_ASSERTIONS = NO; 373 | ENABLE_STRICT_OBJC_MSGSEND = YES; 374 | GCC_C_LANGUAGE_STANDARD = gnu11; 375 | GCC_NO_COMMON_BLOCKS = YES; 376 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 377 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 378 | GCC_WARN_UNDECLARED_SELECTOR = YES; 379 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 380 | GCC_WARN_UNUSED_FUNCTION = YES; 381 | GCC_WARN_UNUSED_VARIABLE = YES; 382 | MACOSX_DEPLOYMENT_TARGET = 11.3; 383 | MTL_ENABLE_DEBUG_INFO = NO; 384 | MTL_FAST_MATH = YES; 385 | SDKROOT = macosx; 386 | }; 387 | name = Release; 388 | }; 389 | 422F66CB26F2020200AEFAE0 /* Debug */ = { 390 | isa = XCBuildConfiguration; 391 | buildSettings = { 392 | CLANG_ENABLE_MODULES = YES; 393 | CODE_SIGN_STYLE = Automatic; 394 | COMBINE_HIDPI_IMAGES = YES; 395 | DEVELOPMENT_TEAM = WLCQDVKTS9; 396 | INFOPLIST_FILE = pock_headline_news/Info.plist; 397 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; 398 | LD_RUNPATH_SEARCH_PATHS = ( 399 | "$(inherited)", 400 | "@executable_path/../Frameworks", 401 | "@loader_path/../Frameworks", 402 | ); 403 | PRODUCT_BUNDLE_IDENTIFIER = "com.p-x9.pock-headline-news"; 404 | PRODUCT_NAME = "$(TARGET_NAME)"; 405 | SKIP_INSTALL = NO; 406 | SWIFT_OBJC_BRIDGING_HEADER = "pock_headline_news/pock_headline_news-Bridging-Header.h"; 407 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 408 | SWIFT_VERSION = 5.0; 409 | WRAPPER_EXTENSION = pock; 410 | }; 411 | name = Debug; 412 | }; 413 | 422F66CC26F2020200AEFAE0 /* Release */ = { 414 | isa = XCBuildConfiguration; 415 | buildSettings = { 416 | CLANG_ENABLE_MODULES = YES; 417 | CODE_SIGN_STYLE = Automatic; 418 | COMBINE_HIDPI_IMAGES = YES; 419 | DEVELOPMENT_TEAM = WLCQDVKTS9; 420 | INFOPLIST_FILE = pock_headline_news/Info.plist; 421 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; 422 | LD_RUNPATH_SEARCH_PATHS = ( 423 | "$(inherited)", 424 | "@executable_path/../Frameworks", 425 | "@loader_path/../Frameworks", 426 | ); 427 | PRODUCT_BUNDLE_IDENTIFIER = "com.p-x9.pock-headline-news"; 428 | PRODUCT_NAME = "$(TARGET_NAME)"; 429 | SKIP_INSTALL = NO; 430 | SWIFT_OBJC_BRIDGING_HEADER = "pock_headline_news/pock_headline_news-Bridging-Header.h"; 431 | SWIFT_VERSION = 5.0; 432 | WRAPPER_EXTENSION = pock; 433 | }; 434 | name = Release; 435 | }; 436 | /* End XCBuildConfiguration section */ 437 | 438 | /* Begin XCConfigurationList section */ 439 | 422F66BF26F2020200AEFAE0 /* Build configuration list for PBXProject "pock_headline_news" */ = { 440 | isa = XCConfigurationList; 441 | buildConfigurations = ( 442 | 422F66C826F2020200AEFAE0 /* Debug */, 443 | 422F66C926F2020200AEFAE0 /* Release */, 444 | ); 445 | defaultConfigurationIsVisible = 0; 446 | defaultConfigurationName = Release; 447 | }; 448 | 422F66CA26F2020200AEFAE0 /* Build configuration list for PBXNativeTarget "pock_headline_news" */ = { 449 | isa = XCConfigurationList; 450 | buildConfigurations = ( 451 | 422F66CB26F2020200AEFAE0 /* Debug */, 452 | 422F66CC26F2020200AEFAE0 /* Release */, 453 | ); 454 | defaultConfigurationIsVisible = 0; 455 | defaultConfigurationName = Release; 456 | }; 457 | /* End XCConfigurationList section */ 458 | 459 | /* Begin XCRemoteSwiftPackageReference section */ 460 | 4246EF5F2712EB0F003541E9 /* XCRemoteSwiftPackageReference "pockkit" */ = { 461 | isa = XCRemoteSwiftPackageReference; 462 | repositoryURL = "https://github.com/pock/pockkit.git"; 463 | requirement = { 464 | kind = upToNextMajorVersion; 465 | minimumVersion = 0.3.1; 466 | }; 467 | }; 468 | 4246EF622712EB40003541E9 /* XCRemoteSwiftPackageReference "Defaults" */ = { 469 | isa = XCRemoteSwiftPackageReference; 470 | repositoryURL = "https://github.com/sindresorhus/Defaults.git"; 471 | requirement = { 472 | kind = upToNextMajorVersion; 473 | minimumVersion = 5.0.0; 474 | }; 475 | }; 476 | 4246EF652712EB6F003541E9 /* XCRemoteSwiftPackageReference "SnapKit" */ = { 477 | isa = XCRemoteSwiftPackageReference; 478 | repositoryURL = "https://github.com/SnapKit/SnapKit.git"; 479 | requirement = { 480 | kind = upToNextMajorVersion; 481 | minimumVersion = 5.0.1; 482 | }; 483 | }; 484 | /* End XCRemoteSwiftPackageReference section */ 485 | 486 | /* Begin XCSwiftPackageProductDependency section */ 487 | 4246EF602712EB0F003541E9 /* PockKit */ = { 488 | isa = XCSwiftPackageProductDependency; 489 | package = 4246EF5F2712EB0F003541E9 /* XCRemoteSwiftPackageReference "pockkit" */; 490 | productName = PockKit; 491 | }; 492 | 4246EF632712EB40003541E9 /* Defaults */ = { 493 | isa = XCSwiftPackageProductDependency; 494 | package = 4246EF622712EB40003541E9 /* XCRemoteSwiftPackageReference "Defaults" */; 495 | productName = Defaults; 496 | }; 497 | 4246EF662712EB70003541E9 /* SnapKit */ = { 498 | isa = XCSwiftPackageProductDependency; 499 | package = 4246EF652712EB6F003541E9 /* XCRemoteSwiftPackageReference "SnapKit" */; 500 | productName = SnapKit; 501 | }; 502 | /* End XCSwiftPackageProductDependency section */ 503 | }; 504 | rootObject = 422F66BC26F2020200AEFAE0 /* Project object */; 505 | } 506 | -------------------------------------------------------------------------------- /pock_headline_news.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /pock_headline_news.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /pock_headline_news/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | CFBundleDevelopmentRegion 11 | $(DEVELOPMENT_LANGUAGE) 12 | CFBundleExecutable 13 | $(EXECUTABLE_NAME) 14 | CFBundleIdentifier 15 | $(PRODUCT_BUNDLE_IDENTIFIER) 16 | CFBundleInfoDictionaryVersion 17 | 6.0 18 | CFBundleName 19 | HeadlIneNews 20 | CFBundlePackageType 21 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 22 | CFBundleShortVersionString 23 | 1.0 24 | CFBundleVersion 25 | 1 26 | NSPrincipalClass 27 | pock_headline_news.HeadLineNewsWidget 28 | PKWidgetAuthor 29 | p_x9 30 | PKWidgetPreferenceClass 31 | pock_headline_news.HeadlineNewsWidgetPreferencePane 32 | 33 | 34 | -------------------------------------------------------------------------------- /pock_headline_news/Sources/Extension/NSColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSColor.swift 3 | // pock_headline_news 4 | // 5 | // Created by p-x9 on 2021/09/17. 6 | // 7 | // 8 | 9 | import Cocoa 10 | 11 | extension NSColor { 12 | static let touchBarBackgroundColor = NSColor(red: 54 / 256, green: 54 / 256, blue: 54 / 256, alpha: 1.0) 13 | static var random: NSColor { 14 | let r = CGFloat.random(in: 0 ... 255) / 255.0 15 | let g = CGFloat.random(in: 0 ... 255) / 255.0 16 | let b = CGFloat.random(in: 0 ... 255) / 255.0 17 | return NSColor(red: r, green: g, blue: b, alpha: 1.0) 18 | } 19 | 20 | } 21 | 22 | extension NSColor { 23 | convenience init(rgb: Int) { 24 | let r = CGFloat((rgb & 0xFF0000) >> 16) / 255.0 25 | let g = CGFloat((rgb & 0x00FF00) >> 8) / 255.0 26 | let b = CGFloat( rgb & 0x0000FF ) / 255.0 27 | self.init(red: r, green: g, blue: b, alpha: 1.0) 28 | } 29 | 30 | convenience init(rgba: Int) { 31 | let r = CGFloat((rgba & 0xFF000000) >> 24) / 255.0 32 | let g = CGFloat((rgba & 0x00FF0000) >> 16) / 255.0 33 | let b = CGFloat((rgba & 0x0000FF00) >> 8) / 255.0 34 | let a = CGFloat( rgba & 0x000000FF ) / 255.0 35 | self.init(red: r, green: g, blue: b, alpha: a) 36 | } 37 | 38 | convenience init?(rgb code: String) { 39 | var color: UInt64 = 0 40 | let code = code.replacingOccurrences(of: "#", with: "") 41 | if Scanner(string: code).scanHexInt64(&color) { 42 | self.init(rgb: Int(color)) 43 | return 44 | } 45 | return nil 46 | } 47 | 48 | convenience init?(rgba code: String) { 49 | var color: UInt64 = 0 50 | let code = code.replacingOccurrences(of: "#", with: "") 51 | if Scanner(string: code).scanHexInt64(&color) { 52 | self.init(rgba: code) 53 | return 54 | } 55 | return nil 56 | } 57 | } 58 | 59 | extension NSColor { 60 | private enum Component: Int { 61 | case red, green, blue, alpha 62 | } 63 | 64 | var redComponent: CGFloat { 65 | getComponent(.red) 66 | } 67 | 68 | var blueComponent: CGFloat { 69 | getComponent(.blue) 70 | } 71 | 72 | var greenComponent: CGFloat { 73 | getComponent(.green) 74 | } 75 | 76 | var alphaComponent: CGFloat { 77 | getComponent(.alpha) 78 | } 79 | 80 | private var rgbaArray: [CGFloat] { 81 | var red: CGFloat = -1 82 | var blue: CGFloat = -1 83 | var green: CGFloat = -1 84 | var alpha: CGFloat = 1 85 | getRed(&red, green: &green, blue: &blue, alpha: &alpha) 86 | return [red, green, blue, alpha] 87 | } 88 | 89 | private func getComponent(_ component: Component) -> CGFloat { 90 | self.rgbaArray[component.rawValue] 91 | } 92 | } 93 | 94 | extension NSColor { 95 | var rgbString: String { 96 | let rgb = Array(self.rgbaArray[0...2]) 97 | return rgb.reduce(into: "") {res, value in 98 | let intval = Int(round(value * 255)) 99 | res += (NSString(format: "%02X", intval) as String) 100 | } 101 | } 102 | 103 | var rgbaString: String { 104 | let rgba = self.rgbaArray 105 | return rgba.reduce(into: "") {res, value in 106 | let intval = Int(round(value * 255)) 107 | res += (NSString(format: "%02X", intval) as String) 108 | } 109 | } 110 | 111 | var rgbInt: Int { 112 | let rgba = self.rgbaArray 113 | let r = Int(rgba[0] * 255) << 16 114 | let g = Int(rgba[1] * 255) << 8 115 | let b = Int(rgba[2] * 255) 116 | 117 | return [r, g, b].reduce(0, +) 118 | } 119 | 120 | var rgbaInt: Int { 121 | let rgba = self.rgbaArray 122 | let r = Int(rgba[0] * 255) << 24 123 | let g = Int(rgba[1] * 255) << 16 124 | let b = Int(rgba[2] * 255) << 8 125 | let a = Int(rgba[3] * 255) 126 | 127 | return [r, g, b, a].reduce(0, +) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /pock_headline_news/Sources/Extension/NSTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSTextField.swift 3 | // pock_headline_news 4 | // 5 | // Created by p-x9 on 2021/11/24. 6 | // 7 | // 8 | 9 | import Cocoa 10 | 11 | extension NSTextField { 12 | override open func performKeyEquivalent(with event: NSEvent) -> Bool { 13 | let flags = event.modifierFlags.intersection(.deviceIndependentFlagsMask) 14 | let characters = event.charactersIgnoringModifiers?.lowercased() 15 | switch flags { 16 | case [.command]: 17 | let selector: Selector 18 | switch characters { 19 | case "x": 20 | selector = #selector(NSText.cut(_:)) 21 | case "c": 22 | selector = #selector(NSText.copy(_:)) 23 | case "v": 24 | selector = #selector(NSText.paste(_:)) 25 | case "a": 26 | selector = #selector(NSText.selectAll(_:)) 27 | case "z": 28 | selector = Selector(("undo:")) 29 | default: 30 | return super.performKeyEquivalent(with: event) 31 | } 32 | return NSApp.sendAction(selector, to: nil, from: self) 33 | case [.shift, .command] where characters == "z": 34 | return NSApp.sendAction(Selector(("redo:")), to: nil, from: self) 35 | default: 36 | return super.performKeyEquivalent(with: event) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /pock_headline_news/Sources/Extension/NSViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSViewController.swift 3 | // pock_headline_news 4 | // 5 | // Created by p-x9 on 2021/09/19. 6 | // 7 | // 8 | 9 | import Cocoa 10 | 11 | extension NSViewController { 12 | func presentAlert(title: String, message: String, style: NSAlert.Style = .informational, completion: ((Bool) -> Void)?) { 13 | let alert = NSAlert() 14 | alert.messageText = title 15 | alert.informativeText = message 16 | alert.alertStyle = style 17 | 18 | alert.addButton(withTitle: "OK") 19 | alert.buttons[0].keyEquivalent = "\r" // enter key 20 | 21 | if completion != nil { 22 | alert.addButton(withTitle: "Cancel") 23 | alert.buttons[1].keyEquivalent = "\u{1b}" // esc key 24 | } 25 | 26 | let ret = alert.runModal() 27 | 28 | completion?(ret == .alertFirstButtonReturn) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pock_headline_news/Sources/HeadLineNewsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HeadLineNewsView.swift 3 | // pock_headline_news 4 | // 5 | // Created by p-x9 on 2021/09/19. 6 | // 7 | // 8 | 9 | import Cocoa 10 | import SnapKit 11 | 12 | protocol HeadLineNewsViewDelegate: AnyObject { 13 | func nextItems(for view: HeadLineNewsView) -> [Item] 14 | func headLineNewsView(_ view: HeadLineNewsView, animationEndedWith items: [Item]) 15 | } 16 | 17 | class HeadLineNewsView: NSView { 18 | 19 | private let newsLabel: NSTextField = { 20 | let label = NSTextField() 21 | label.isEditable = false 22 | label.isSelectable = false 23 | label.textColor = .labelColor 24 | label.drawsBackground = false 25 | label.isBezeled = false 26 | label.alignment = .natural 27 | label.font = .systemFont(ofSize: 20) 28 | label.lineBreakMode = .byClipping 29 | label.cell?.isScrollable = true 30 | label.cell?.wraps = false 31 | label.wantsLayer = true 32 | 33 | return label 34 | }() 35 | 36 | var isHighlighted = false { 37 | didSet { 38 | if isHighlighted { 39 | self.layer?.backgroundColor = self.backgroundColor.highlight(withLevel: 0.25)?.cgColor 40 | } else { 41 | self.layer?.backgroundColor = self.backgroundColor.cgColor 42 | } 43 | } 44 | } 45 | 46 | weak var delegate: HeadLineNewsViewDelegate? 47 | private(set) var isRunning = false 48 | private var items = [Item]() 49 | 50 | private var currentIndex = -1 51 | var currentItem: Item? { 52 | if self.items.indices.contains(self.currentIndex) { 53 | return self.items[self.currentIndex] 54 | } 55 | return nil 56 | } 57 | 58 | var speed: Float = 1 { 59 | didSet { 60 | guard let layer = self.newsLabel.layer else { 61 | return 62 | } 63 | 64 | layer.timeOffset = layer.convertTime(CACurrentMediaTime(), from: nil) 65 | layer.beginTime = CACurrentMediaTime() 66 | layer.speed = self.speed 67 | } 68 | } 69 | 70 | var textColor: NSColor = .white { 71 | didSet { 72 | self.newsLabel.textColor = self.textColor 73 | } 74 | } 75 | 76 | var font: NSFont = .systemFont(ofSize: 20) { 77 | didSet { 78 | self.newsLabel.font = self.font 79 | } 80 | } 81 | 82 | var backgroundColor: NSColor = .black { 83 | didSet { 84 | self.layer?.backgroundColor = self.backgroundColor.cgColor 85 | } 86 | } 87 | 88 | override init(frame frameRect: NSRect) { 89 | super.init(frame: frameRect) 90 | 91 | self.setupView() 92 | } 93 | 94 | required init?(coder: NSCoder) { 95 | super.init(coder: coder) 96 | 97 | self.setupView() 98 | } 99 | 100 | override func touchesBegan(with event: NSEvent) { 101 | super.touchesBegan(with: event) 102 | 103 | self.isHighlighted = true 104 | } 105 | override func touchesEnded(with event: NSEvent) { 106 | super.touchesEnded(with: event) 107 | 108 | self.isHighlighted = false 109 | } 110 | 111 | override func draw(_ dirtyRect: NSRect) { 112 | super.draw(dirtyRect) 113 | 114 | // Drawing code here. 115 | } 116 | 117 | func setupView() { 118 | self.wantsLayer = true 119 | self.layer?.backgroundColor = self.backgroundColor.cgColor 120 | self.layer?.cornerRadius = 5 121 | 122 | self.addSubview(self.newsLabel) 123 | self.newsLabel.snp.makeConstraints {make in 124 | make.centerY.equalToSuperview() 125 | } 126 | } 127 | 128 | func animation() { 129 | self.isRunning = true 130 | 131 | self.currentIndex += 1 132 | 133 | if self.currentIndex < self.items.count { 134 | let item = self.items[self.currentIndex] 135 | let news = "【\(item.title)】 \(item.description)" 136 | self.newsLabel.stringValue = news 137 | self.newsLabel.sizeToFit() 138 | self.setAnimation() 139 | } else { 140 | self.delegate?.headLineNewsView(self, animationEndedWith: self.items) 141 | if let items = self.delegate?.nextItems(for: self), 142 | !items.isEmpty { 143 | self.items = items 144 | } 145 | self.currentIndex = -1 146 | self.animation() 147 | } 148 | } 149 | 150 | func setAnimation() { 151 | let animation = CABasicAnimation(keyPath: #keyPath(CALayer.position)) 152 | animation.repeatCount = 0 153 | animation.duration = CFTimeInterval(self.newsLabel.frame.width / 200) 154 | animation.fromValue = NSValue(point: NSPoint(x: self.frame.width, y: 0)) 155 | animation.toValue = NSValue(point: NSPoint(x: -self.newsLabel.frame.width, y: 0)) 156 | animation.isAdditive = true 157 | animation.isRemovedOnCompletion = false 158 | animation.fillMode = .both 159 | animation.delegate = self 160 | self.newsLabel.layer?.speed = self.speed 161 | self.newsLabel.layer?.add(animation, forKey: "position") 162 | } 163 | 164 | func startAnimating(with items: [Item]) { 165 | if items.isEmpty { 166 | return 167 | } 168 | self.items = items 169 | DispatchQueue.main.async { 170 | self.animation() 171 | } 172 | } 173 | 174 | func stopAnimating() { 175 | self.currentIndex = -1 176 | self.isRunning = false 177 | self.newsLabel.stringValue = "" 178 | self.newsLabel.layer?.removeAllAnimations() 179 | } 180 | 181 | } 182 | 183 | extension HeadLineNewsView: CAAnimationDelegate { 184 | func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { 185 | if self.isRunning { 186 | self.animation() 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /pock_headline_news/Sources/HeadLineNewsWidget.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HeadLineNewsWidget.swift 3 | // pock_headline_news 4 | // 5 | // Created by p-x9 on 2021/09/15. 6 | // 7 | // 8 | 9 | import PockKit 10 | import Cocoa 11 | import SnapKit 12 | 13 | class HeadLineNewsWidget: NSObject, PKWidget { 14 | static var identifier: String = "\(HeadLineNewsWidget.self)" 15 | 16 | var customizationLabel: String = "HeadLineNews" 17 | 18 | var view: NSView! 19 | let headLineNewsView = HeadLineNewsView(frame: NSRect(x: 0, y: 0, width: 200, height: 30)) 20 | 21 | var rssParser: RSSParser 22 | var items: [Item] = [] 23 | 24 | var speed: Float { Preferences[.textSpeed] } 25 | var textColor: NSColor { NSColor(rgba: Preferences[.textColor]) } 26 | var font: NSFont { 27 | NSFont(name: Preferences[.fontName], size: Preferences[.fontSize]) ?? .systemFont(ofSize: 20) 28 | } 29 | var backgroundColor: NSColor { NSColor(rgba: Preferences[.backgroundColor]) } 30 | var isRunning: Bool { 31 | self.headLineNewsView.isRunning 32 | } 33 | 34 | var rssURLs: [URL] { 35 | Preferences[.rssURLs].compactMap { URL(string: $0) } 36 | } 37 | 38 | override required init() { 39 | self.rssParser = RSSParser() 40 | 41 | super.init() 42 | 43 | self.view = self.headLineNewsView 44 | self.headLineNewsView.delegate = self 45 | self.updateUISettings() 46 | 47 | self.setupTapGesture() 48 | self.setupLongPressGesture() 49 | 50 | NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(updateUISettings), 51 | name: .shouldReloadUISettings, object: nil) 52 | NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(updateRssUrl), 53 | name: .shouldChangeRssUrl, object: nil) 54 | } 55 | 56 | func setupTapGesture() { 57 | let tapGesture = NSClickGestureRecognizer() 58 | tapGesture.target = self 59 | tapGesture.action = #selector(tap) 60 | tapGesture.allowedTouchTypes = .direct 61 | self.view.addGestureRecognizer(tapGesture) 62 | } 63 | 64 | func setupLongPressGesture() { 65 | let longPressGesture = NSPressGestureRecognizer() 66 | longPressGesture.target = self 67 | longPressGesture.action = #selector(longPress) 68 | longPressGesture.allowedTouchTypes = .direct 69 | self.view.addGestureRecognizer(longPressGesture) 70 | } 71 | 72 | func openLink() { 73 | guard let item = self.headLineNewsView.currentItem, 74 | let url = URL(string: item.link) else { 75 | return 76 | } 77 | NSWorkspace.shared.open(url) 78 | } 79 | 80 | @objc 81 | func tap(_ sender: NSGestureRecognizer?) { 82 | if !self.isRunning { 83 | self.items = self.rssParser.parse(urls: self.rssURLs) 84 | self.headLineNewsView.startAnimating(with: items) 85 | } else { 86 | self.openLink() 87 | } 88 | } 89 | 90 | @objc 91 | func longPress(_ sender: NSGestureRecognizer?) { 92 | self.headLineNewsView.stopAnimating() 93 | } 94 | 95 | @objc 96 | func updateUISettings() { 97 | self.headLineNewsView.textColor = self.textColor 98 | self.headLineNewsView.speed = self.speed 99 | self.headLineNewsView.font = self.font 100 | self.headLineNewsView.backgroundColor = self.backgroundColor 101 | } 102 | 103 | @objc 104 | func updateRssUrl() { 105 | self.headLineNewsView.stopAnimating() 106 | self.items = self.rssParser.parse(urls: self.rssURLs) 107 | if self.isRunning { 108 | Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) {_ in 109 | self.headLineNewsView.startAnimating(with: self.items) 110 | } 111 | } 112 | } 113 | 114 | } 115 | 116 | extension HeadLineNewsWidget: PKScreenEdgeMouseDelegate { 117 | private func shouldHighlight(for location: NSPoint, in view: NSView) -> Bool { 118 | headLineNewsView.convert(headLineNewsView.bounds, to: view).contains(location) 119 | } 120 | 121 | func screenEdgeController(_ controller: PKScreenEdgeController, mouseEnteredAtLocation location: NSPoint, in view: NSView) { 122 | self.headLineNewsView.isHighlighted = shouldHighlight(for: location, in: view) 123 | } 124 | 125 | func screenEdgeController(_ controller: PKScreenEdgeController, mouseMovedAtLocation location: NSPoint, in view: NSView) { 126 | self.headLineNewsView.isHighlighted = shouldHighlight(for: location, in: view) 127 | } 128 | 129 | func screenEdgeController(_ controller: PKScreenEdgeController, mouseClickAtLocation location: NSPoint, in view: NSView) { 130 | if !headLineNewsView.isHighlighted { 131 | return 132 | } 133 | if !self.isRunning { 134 | self.items = self.rssParser.parse(urls: self.rssURLs) 135 | self.headLineNewsView.startAnimating(with: items) 136 | } else { 137 | self.openLink() 138 | } 139 | } 140 | 141 | func screenEdgeController(_ controller: PKScreenEdgeController, mouseExitedAtLocation location: NSPoint, in view: NSView) { 142 | self.headLineNewsView.isHighlighted = false 143 | } 144 | } 145 | 146 | extension HeadLineNewsWidget: HeadLineNewsViewDelegate { 147 | func nextItems(for view: HeadLineNewsView) -> [Item] { 148 | self.items = self.rssParser.parse(urls: self.rssURLs) 149 | return self.items 150 | } 151 | 152 | func headLineNewsView(_ view: HeadLineNewsView, animationEndedWith items: [Item]) { 153 | 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /pock_headline_news/Sources/Model/Item.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Item.swift 3 | // pock_headline_news 4 | // 5 | // Created by p-x9 on 2021/09/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | struct Item { 12 | var title: String 13 | var pubDate: String 14 | var link: String 15 | var description: String 16 | 17 | func getPubDate() -> Date? { 18 | let dateFormatter = DateFormatter() 19 | dateFormatter.locale = Locale(identifier: "en_US_POSIX") 20 | dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss ZZZZ" 21 | 22 | return dateFormatter.date(from: self.pubDate) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pock_headline_news/Sources/Preferences/HeadlineNewsWidgetPreferencePane/HeadlineNewsWidgetPreferencePane.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HeadlineNewsWidgetPreferencePane.swift 3 | // pock_headline_news 4 | // 5 | // Created by p-x9 on 2021/09/18. 6 | // 7 | // 8 | 9 | import AppKit 10 | import PockKit 11 | 12 | class HeadlineNewsWidgetPreferencePane: NSViewController, PKWidgetPreference { 13 | static var nibName: NSNib.Name = "\(HeadlineNewsWidgetPreferencePane.self)" 14 | 15 | var rssURLs: [String] = Preferences[.rssURLs] 16 | 17 | @IBOutlet private var rssTableView: NSTableView! 18 | 19 | @IBOutlet private weak var speedSlider: NSSlider! { 20 | didSet { 21 | self.speedSlider.minValue = 0.0 22 | self.speedSlider.maxValue = 2.0 23 | self.speedSlider.floatValue = Preferences[.textSpeed] 24 | } 25 | } 26 | @IBOutlet private var speedTextField: NSTextField! { 27 | didSet { 28 | self.speedTextField.stringValue = String(format: "%.1f", self.speedSlider.floatValue) 29 | } 30 | } 31 | @IBOutlet private var textColorWell: NSColorWell! { 32 | didSet { 33 | self.textColorWell.color = NSColor(rgba: Preferences[.textColor]) 34 | } 35 | } 36 | @IBOutlet private var fontTextField: NSTextField! { 37 | didSet { 38 | self.fontTextField.stringValue = "\(Preferences[.fontName]) \(Int(Preferences[.fontSize]))" 39 | } 40 | } 41 | @IBOutlet private var fontSelectButton: NSButton! 42 | @IBOutlet private var backgroundColorWell: NSColorWell! { 43 | didSet { 44 | self.backgroundColorWell.color = NSColor(rgba: Preferences[.backgroundColor]) 45 | } 46 | } 47 | 48 | override func viewDidLoad() { 49 | super.viewDidLoad() 50 | 51 | self.rssTableView.delegate = self 52 | self.rssTableView.dataSource = self 53 | self.rssTableView.target = self 54 | self.rssTableView.doubleAction = #selector(handleTableViewDoubleClicked) 55 | } 56 | 57 | override func viewDidDisappear() { 58 | let panel = NSFontManager.shared.fontPanel(true) 59 | panel?.close() 60 | } 61 | 62 | @IBAction private func speedSliderValueChanged(_ sender: Any) { 63 | self.speedTextField.stringValue = String(format: "%.1f", self.speedSlider.floatValue) 64 | self.update(speed: self.speedSlider.floatValue) 65 | } 66 | 67 | @IBAction private func speedTextFieldChanged(_ sender: Any) { 68 | let range = self.speedSlider.minValue ... self.speedSlider.maxValue 69 | if let value = Float(self.speedTextField.stringValue), 70 | range.contains(Double(value)) { 71 | self.speedSlider.floatValue = value 72 | self.update(speed: self.speedSlider.floatValue) 73 | } else { 74 | self.speedTextField.stringValue = String(format: "%.1f", self.speedSlider.floatValue) 75 | } 76 | } 77 | 78 | @IBAction private func colorChanged(_ sender: Any) { 79 | guard let colorWell = sender as? NSColorWell else { 80 | return 81 | } 82 | switch colorWell { 83 | case self.textColorWell: 84 | Preferences[.textColor] = colorWell.color.rgbaInt 85 | case self.backgroundColorWell: 86 | Preferences[.backgroundColor] = colorWell.color.rgbaInt 87 | default: 88 | break 89 | } 90 | NSWorkspace.shared.notificationCenter.post(name: .shouldReloadUISettings, object: nil) 91 | } 92 | 93 | @IBAction private func handleFontSelectButton(_ sender: Any) { 94 | let fontManager = NSFontManager.shared 95 | fontManager.target = self 96 | let panel = fontManager.fontPanel(true) 97 | panel?.orderFront(self) 98 | panel?.isEnabled = true 99 | } 100 | 101 | @IBAction private func clickedRSSSegmentedControl(_ sender: Any) { 102 | guard let segmentedControl = sender as? NSSegmentedControl else { 103 | return 104 | } 105 | 106 | switch segmentedControl.selectedSegment { 107 | case 0:// Add 108 | self.addRssUrl() 109 | case 1:// Minus 110 | let selectedIndex = self.rssTableView.selectedRow 111 | self.removeRssUrl(index: selectedIndex) 112 | default: 113 | break 114 | } 115 | } 116 | 117 | func addRssUrl() { 118 | let textAlert = TextFieldAlert(nibName: "\(TextFieldAlert.self)", 119 | bundle: Bundle(identifier: "com.p-x9.pock-headline-news")) 120 | 121 | textAlert.titleMessage = "Add new RSS URL" 122 | textAlert.mainMessage = "input new rss URL \n Only 'https://' supported" 123 | textAlert.cancelButtonHandler = {[weak self] in 124 | self?.dismiss(textAlert) 125 | } 126 | textAlert.okButtonHandler = {[weak self] in 127 | let url = textAlert.text 128 | if let self = self, 129 | self.checkFormat(of: url, withAlert: true) { 130 | self.dismiss(textAlert) 131 | self.add(rssUrl: url) 132 | } 133 | } 134 | self.presentAsSheet(textAlert) 135 | } 136 | 137 | func checkFormat(of url: String, withAlert: Bool = false) -> Bool { 138 | if let url = URL(string: url), 139 | url.absoluteString.hasPrefix("https://") { 140 | return true 141 | } 142 | if withAlert { 143 | self.presentAlert(title: "Invalid URL", 144 | message: "Please check rss URL.\n(Only 'https://' supported)", 145 | style: .critical, 146 | completion: nil) 147 | } 148 | return false 149 | } 150 | 151 | func add(rssUrl: String) { 152 | self.rssURLs.append(rssUrl) 153 | self.rssTableView.reloadData() 154 | self.update(rssURLs: self.rssURLs) 155 | } 156 | 157 | func removeRssUrl(index: Int) { 158 | if self.rssURLs.indices.contains(index) { 159 | self.rssURLs.remove(at: index) 160 | Preferences[.rssURLs] = self.rssURLs 161 | self.rssTableView.removeRows(at: [index], withAnimation: .effectFade) 162 | NSWorkspace.shared.notificationCenter.post(name: .shouldChangeRssUrl, object: nil) 163 | } 164 | } 165 | 166 | func update(speed: Float) { 167 | Preferences[.textSpeed] = speed 168 | NSWorkspace.shared.notificationCenter.post(name: .shouldReloadUISettings, object: nil) 169 | } 170 | 171 | func update(font: NSFont) { 172 | Preferences[.fontSize] = font.pointSize 173 | Preferences[.fontName] = font.fontName 174 | NSWorkspace.shared.notificationCenter.post(name: .shouldReloadUISettings, object: nil) 175 | } 176 | 177 | func update(rssURLs: [String]) { 178 | Preferences[.rssURLs] = self.rssURLs 179 | NSWorkspace.shared.notificationCenter.post(name: .shouldChangeRssUrl, object: nil) 180 | } 181 | 182 | @objc 183 | func handleTableViewDoubleClicked() { 184 | self.rssTableView.deselectRow(self.rssTableView.clickedRow) 185 | guard let textFieldCell = self.rssTableView.view(atColumn: self.rssTableView.clickedColumn, 186 | row: self.rssTableView.clickedRow, 187 | makeIfNecessary: false) as? TextFieldTableViewCell else { 188 | return 189 | } 190 | textFieldCell.isEditable = true 191 | } 192 | 193 | func reset() { 194 | Preferences.reset() 195 | NSWorkspace.shared.notificationCenter.post(name: .shouldChangeRssUrl, object: nil) 196 | NSWorkspace.shared.notificationCenter.post(name: .shouldReloadUISettings, object: nil) 197 | } 198 | } 199 | 200 | extension HeadlineNewsWidgetPreferencePane: NSFontChanging { 201 | func changeFont(_ sender: NSFontManager?) { 202 | guard let fontManager = sender else { 203 | return 204 | } 205 | let newFont = fontManager.convert(.systemFont(ofSize: 20)) // dummy font 206 | self.update(font: newFont) 207 | self.fontTextField.stringValue = "\(newFont.fontName) \(Int(newFont.pointSize))" 208 | } 209 | } 210 | 211 | extension HeadlineNewsWidgetPreferencePane: NSTableViewDelegate, NSTableViewDataSource { 212 | func numberOfRows(in tableView: NSTableView) -> Int { 213 | self.rssURLs.count 214 | } 215 | func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { 216 | let identifier = NSUserInterfaceItemIdentifier(rawValue: "\(TextFieldTableViewCell.self)") 217 | let cell = tableView.makeView(withIdentifier: identifier, owner: self) as! TextFieldTableViewCell 218 | cell.text = self.rssURLs[row] 219 | cell.valueChangedHandler = {[weak self] text in 220 | guard let self = self else { 221 | return 222 | } 223 | if self.checkFormat(of: text, withAlert: true) { 224 | self.rssURLs[row] = text 225 | } else { 226 | cell.text = self.rssURLs[row] 227 | } 228 | 229 | } 230 | cell.textDidEndEditingHandler = {_ in 231 | cell.isEditable = false 232 | } 233 | return cell 234 | } 235 | 236 | } 237 | -------------------------------------------------------------------------------- /pock_headline_news/Sources/Preferences/HeadlineNewsWidgetPreferencePane/HeadlineNewsWidgetPreferencePane.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 74 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | -------------------------------------------------------------------------------- /pock_headline_news/Sources/Preferences/Preferences.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Preferences.swift 3 | // pock_headline_news 4 | // 5 | // Created by p-x9 on 2021/09/19. 6 | // 7 | // 8 | 9 | import Foundation 10 | import Defaults 11 | 12 | struct Preferences { 13 | typealias Keys = Defaults.Keys 14 | typealias Key = Defaults.Key 15 | 16 | static func reset() { 17 | Keys.rssURLs.reset() 18 | Keys.textSpeed.reset() 19 | Keys.textColor.reset() 20 | Keys.fontName.reset() 21 | Keys.fontSize.reset() 22 | Keys.backgroundColor.reset() 23 | } 24 | 25 | static subscript(key: Key) -> Value { 26 | get { 27 | Defaults[key] 28 | } 29 | set { 30 | Defaults[key] = newValue 31 | } 32 | } 33 | 34 | } 35 | 36 | extension Preferences.Keys { 37 | static let rssURLs = Preferences.Key<[String]>("rssURLs", default: ["https://news.yahoo.co.jp/rss/topics/top-picks.xml"]) 38 | static let textSpeed = Preferences.Key("textSpeed", default: 1) 39 | static let textColor = Preferences.Key("textColor", default: 0xFFFFFFFF) 40 | static let fontName = Preferences.Key("fontName", default: "HelveticaNeue") 41 | static let fontSize = Preferences.Key("fontSize", default: 20.0) 42 | static let backgroundColor = Preferences.Key("backgroundColor", default: 0x00000000) 43 | } 44 | 45 | extension Preferences.Key { 46 | func reset() { 47 | Defaults[self] = self.defaultValue 48 | } 49 | } 50 | 51 | extension NSNotification.Name { 52 | static let shouldChangeRssUrl = NSNotification.Name("shouldChangeRssUrl") 53 | static let shouldReloadUISettings = NSNotification.Name("shouldReloadUISettings") 54 | } 55 | -------------------------------------------------------------------------------- /pock_headline_news/Sources/Preferences/TextFieldAlert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextFieldAlert.swift 3 | // pock_headline_news 4 | // 5 | // Created by p-x9 on 2021/09/21. 6 | // 7 | // 8 | 9 | import Cocoa 10 | 11 | class TextFieldAlert: NSViewController { 12 | 13 | var titleMessage = String() { 14 | didSet { 15 | self.titleLabel?.stringValue = titleMessage 16 | } 17 | } 18 | 19 | var mainMessage = String() { 20 | didSet { 21 | self.messageLabel?.stringValue = mainMessage 22 | } 23 | } 24 | 25 | var placeholder = String() { 26 | didSet { 27 | self.textField?.placeholderString = placeholder 28 | } 29 | } 30 | 31 | var text: String { 32 | self.textField.stringValue 33 | } 34 | 35 | var cancelButtonHandler:(() -> Void)? 36 | var okButtonHandler:(() -> Void)? 37 | var textChangedHandler: ((String) -> Void)? 38 | 39 | @IBOutlet private var titleLabel: NSTextField! 40 | @IBOutlet private var messageLabel: NSTextField! 41 | @IBOutlet private var textField: NSTextField! 42 | 43 | override func viewDidLoad() { 44 | super.viewDidLoad() 45 | 46 | self.titleLabel.stringValue = titleMessage 47 | self.messageLabel.stringValue = mainMessage 48 | self.textField.placeholderString = placeholder 49 | } 50 | 51 | @IBAction private func handleOKButton(_ sender: Any) { 52 | self.okButtonHandler?() 53 | } 54 | 55 | @IBAction private func handleCancelButton(_ sender: Any) { 56 | self.cancelButtonHandler?() 57 | } 58 | 59 | @IBAction private func textFieldChanged(_ sender: Any) { 60 | self.textChangedHandler?(self.text) 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /pock_headline_news/Sources/Preferences/TextFieldAlert.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 65 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /pock_headline_news/Sources/Preferences/TextFieldTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextFieldTableViewCell.swift 3 | // pock_headline_news 4 | // 5 | // Created by p-x9 on 2021/09/20. 6 | // 7 | // 8 | 9 | import Cocoa 10 | import SnapKit 11 | 12 | class TextFieldTableViewCell: NSTableCellView { 13 | 14 | var isEditable = false { 15 | didSet { 16 | self.setEditable(self.isEditable) 17 | } 18 | } 19 | 20 | var text = ""{ 21 | didSet { 22 | self.valueTextField.stringValue = self.text 23 | } 24 | } 25 | 26 | var valueChangedHandler: ((String) -> Void)? 27 | var textDidEndEditingHandler: ((String) -> Void)? 28 | 29 | private let valueTextField: NSTextField = { 30 | let textField = NSTextField() 31 | 32 | textField.textColor = .labelColor 33 | textField.alignment = .left 34 | textField.lineBreakMode = .byClipping 35 | textField.cell?.isScrollable = true 36 | textField.cell?.wraps = false 37 | textField.wantsLayer = true 38 | 39 | return textField 40 | }() 41 | 42 | override init(frame frameRect: NSRect) { 43 | super.init(frame: frameRect) 44 | 45 | self.setup() 46 | self.setEditable(false) 47 | self.valueTextField.target = self 48 | self.valueTextField.action = #selector(textChanged) 49 | NotificationCenter.default.addObserver(self, selector: #selector(textDidEndEditing(_:)), 50 | name: NSTextField.textDidEndEditingNotification, object: nil) 51 | } 52 | 53 | required init?(coder: NSCoder) { 54 | super.init(coder: coder) 55 | 56 | self.setup() 57 | self.setEditable(false) 58 | self.valueTextField.target = self 59 | self.valueTextField.action = #selector(textChanged) 60 | NotificationCenter.default.addObserver(self, selector: #selector(textDidEndEditing(_:)), 61 | name: NSTextField.textDidEndEditingNotification, object: nil) 62 | } 63 | 64 | private func setup() { 65 | self.addSubview(self.valueTextField) 66 | self.valueTextField.snp.makeConstraints {make in 67 | make.leading.trailing.equalToSuperview() 68 | make.top.greaterThanOrEqualToSuperview() 69 | make.bottom.lessThanOrEqualToSuperview() 70 | make.centerY.equalToSuperview() 71 | } 72 | } 73 | 74 | private func setEditable(_ mode: Bool) { 75 | self.valueTextField.isEditable = mode 76 | self.valueTextField.isSelectable = mode 77 | self.valueTextField.drawsBackground = mode 78 | self.valueTextField.isBezeled = mode 79 | self.layer?.backgroundColor = mode ? NSColor.textBackgroundColor.cgColor : .clear 80 | 81 | if mode { 82 | self.valueTextField.becomeFirstResponder() 83 | } 84 | self.needsLayout = true 85 | } 86 | 87 | override func draw(_ dirtyRect: NSRect) { 88 | super.draw(dirtyRect) 89 | 90 | // Drawing code here. 91 | } 92 | 93 | @objc 94 | func textChanged() { 95 | self.valueChangedHandler?(self.valueTextField.stringValue) 96 | } 97 | 98 | @objc 99 | func textDidEndEditing(_ notification: Notification) { 100 | guard let textField = notification.object as? NSTextField else { 101 | return 102 | } 103 | switch textField { 104 | case self.valueTextField: 105 | self.textDidEndEditingHandler?(self.valueTextField.stringValue) 106 | default: 107 | return 108 | } 109 | } 110 | 111 | deinit { 112 | NotificationCenter.default.removeObserver(self, name: NSTextField.textDidEndEditingNotification, object: nil) 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /pock_headline_news/Sources/RSSParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RSSParser.swift 3 | // pock_headline_news 4 | // 5 | // Created by p-x9 on 2021/09/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | class RSSParser { 12 | 13 | init() {} 14 | 15 | func parse(url: URL, completion: @escaping (([Item], Error?) -> Void)) { 16 | var items: [Item] = [] 17 | let request = NSMutableURLRequest(url: url) 18 | request.timeoutInterval = 5 19 | request.httpMethod = "GET" 20 | 21 | let task = URLSession.shared.dataTask(with: request as URLRequest) { data, _, error in 22 | if error == nil , 23 | let data = data, 24 | let result = String(data: data, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue)) { 25 | print(result) 26 | items = self.parse(xml: result) 27 | } 28 | completion(items, error) 29 | } 30 | task.resume() 31 | } 32 | 33 | func parse(url: URL) -> [Item] { 34 | var items: [Item] = [] 35 | let request = NSMutableURLRequest(url: url) 36 | request.timeoutInterval = 5 37 | request.httpMethod = "GET" 38 | 39 | let semaphore = DispatchSemaphore(value: 0) 40 | let task = URLSession.shared.dataTask(with: request as URLRequest) { data, _, error in 41 | if error == nil , 42 | let data = data, 43 | let result = String(data: data, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue)) { 44 | print(result) 45 | items = self.parse(xml: result) 46 | } 47 | semaphore.signal() 48 | } 49 | task.resume() 50 | 51 | semaphore.wait() 52 | return items 53 | } 54 | 55 | func parse(urls: [URL]) -> [Item] { 56 | var items: [Item] = [] 57 | urls.forEach { 58 | items += self.parse(url: $0) 59 | } 60 | return items.sorted { $0.getPubDate() ?? Date() < $1.getPubDate() ?? Date() } 61 | } 62 | 63 | private func parse(xml: String) -> [Item] { 64 | if let doc = try? XMLDocument(xmlString: xml, options: .documentTidyXML), 65 | let root = doc.rootElement() { 66 | var items = [XMLNode]() 67 | do { 68 | items = try root.nodes(forXPath: "//item") 69 | 70 | } catch { 71 | print(error) 72 | } 73 | 74 | return items.compactMap { self.convertToItem(body: $0) } 75 | } 76 | return [] 77 | } 78 | 79 | private func convertToItem(body: XMLNode) -> Item? { 80 | do { 81 | let title = try body.nodes(forXPath: "title").first?.stringValue ?? "" 82 | let link = try body.nodes(forXPath: "link").first?.stringValue ?? "" 83 | let pubDate = try body.nodes(forXPath: "pubDate").first?.stringValue ?? "" 84 | let description = try body.nodes(forXPath: "description").first?.stringValue ?? "" 85 | 86 | return Item(title: title, pubDate: pubDate, link: link, description: description) 87 | } catch { 88 | print(error) 89 | } 90 | return nil 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /pock_headline_news/pock_headline_news-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | -------------------------------------------------------------------------------- /resources/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p-x9/pock-headline-news-widget/dda5be07d28867a7317484c8799b652f4ed6bb1d/resources/demo.gif --------------------------------------------------------------------------------