├── .github ├── FUNDING.yml └── workflows │ └── ci-mac.yaml ├── .gitignore ├── .gitmodules ├── Assets ├── Icon.psd └── Screenshot.png ├── CODE_OF_CONDUCT.md ├── LICENSE ├── MetaCopy.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── MetaCopy ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-1024.png │ │ ├── Icon-128.png │ │ ├── Icon-16.png │ │ ├── Icon-256 1.png │ │ ├── Icon-256.png │ │ ├── Icon-32 1.png │ │ ├── Icon-32.png │ │ ├── Icon-512 1.png │ │ ├── Icon-512.png │ │ └── Icon-64.png │ └── Contents.json ├── Base.lproj │ └── MainMenu.xib ├── Classes │ ├── AboutWindowController.swift │ ├── AboutWindowController.xib │ ├── ApplicationDelegate.swift │ ├── ArrayIsEmpty.swift │ ├── ArrayIsNotEmpty.swift │ ├── BackgroundView.swift │ ├── Helper.swift │ ├── ImageFile.swift │ ├── MainWindowController.swift │ ├── MainWindowController.xib │ ├── MetadataTag.swift │ ├── NSAlert.swift │ ├── NSAppearance.swift │ ├── NSError.swift │ ├── NonInteractiveImageView.swift │ ├── TagType.swift │ ├── TagValue.swift │ ├── TagView.swift │ └── UppercaseString.swift ├── Info.plist └── MetaCopy.entitlements └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: macmade 2 | -------------------------------------------------------------------------------- /.github/workflows/ci-mac.yaml: -------------------------------------------------------------------------------- 1 | name: ci-mac 2 | on: [push] 3 | jobs: 4 | ci: 5 | runs-on: macos-latest 6 | strategy: 7 | matrix: 8 | run-config: 9 | - { scheme: 'MetaCopy', configuration: 'Debug', project: 'MetaCopy.xcodeproj', build: 1, analyze: 1, test: 0, info: 1, destination: 'platform=macOS' } 10 | - { scheme: 'MetaCopy', configuration: 'Release', project: 'MetaCopy.xcodeproj', build: 1, analyze: 1, test: 0, info: 1, destination: 'platform=macOS' } 11 | steps: 12 | 13 | - uses: actions/checkout@v1 14 | with: 15 | submodules: 'recursive' 16 | 17 | - uses: macmade/action-xcodebuild@v1.0.0 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac Finder 2 | .DS_Store 3 | .Trashes 4 | Icon? 5 | 6 | # Thumbnails 7 | ._* 8 | 9 | # Files that might appear on external disk 10 | .Spotlight-V100 11 | .Trashes 12 | 13 | # Xcode 14 | *.pbxuser 15 | *.mode1v3 16 | *.mode2v3 17 | *.perspectivev3 18 | *.xccheckout 19 | *.profraw 20 | !default.pbxuser 21 | !default.mode1v3 22 | !default.mode2v3 23 | !default.perspectivev3 24 | xcuserdata 25 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Submodules/xcconfig"] 2 | path = Submodules/xcconfig 3 | url = https://github.com/macmade/xcconfig.git 4 | -------------------------------------------------------------------------------- /Assets/Icon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macmade/MetaCopy/9ef8b8816ed6215e10f81bdb6efa3d4729f234ac/Assets/Icon.psd -------------------------------------------------------------------------------- /Assets/Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macmade/MetaCopy/9ef8b8816ed6215e10f81bdb6efa3d4729f234ac/Assets/Screenshot.png -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | xs-labs.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Jean-David Gadina - www.xs-labs.com 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /MetaCopy.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 05638ED62B94A3630030FFE6 /* ApplicationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05638ED52B94A3630030FFE6 /* ApplicationDelegate.swift */; }; 11 | 05638ED82B94A3650030FFE6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 05638ED72B94A3650030FFE6 /* Assets.xcassets */; }; 12 | 05638EDB2B94A3650030FFE6 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 05638ED92B94A3650030FFE6 /* MainMenu.xib */; }; 13 | 05638EE52B94A3DC0030FFE6 /* MetadataTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05638EE42B94A3DC0030FFE6 /* MetadataTag.swift */; }; 14 | 05638EE72B94A8CC0030FFE6 /* MainWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05638EE62B94A8CC0030FFE6 /* MainWindowController.swift */; }; 15 | 05638EE92B94A8FB0030FFE6 /* MainWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 05638EE82B94A8FB0030FFE6 /* MainWindowController.xib */; }; 16 | 05638EEB2B94A93A0030FFE6 /* BackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05638EEA2B94A93A0030FFE6 /* BackgroundView.swift */; }; 17 | 05638EED2B94AE0A0030FFE6 /* NSAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05638EEC2B94AE0A0030FFE6 /* NSAppearance.swift */; }; 18 | 0568C7B22B966E3E004DF51A /* ArrayIsNotEmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0568C7B12B966E3E004DF51A /* ArrayIsNotEmpty.swift */; }; 19 | 0568C7B42B967C38004DF51A /* TagType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0568C7B32B967C38004DF51A /* TagType.swift */; }; 20 | 0568C7F02B9721CB004DF51A /* AboutWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0568C7EE2B9721CB004DF51A /* AboutWindowController.swift */; }; 21 | 0568C7F12B9721CB004DF51A /* AboutWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0568C7EF2B9721CB004DF51A /* AboutWindowController.xib */; }; 22 | 0568C8022B979116004DF51A /* Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0568C8012B979116004DF51A /* Helper.swift */; }; 23 | 0568C8042B979212004DF51A /* NSError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0568C8032B979212004DF51A /* NSError.swift */; }; 24 | 0568C8062B979277004DF51A /* NSAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0568C8052B979277004DF51A /* NSAlert.swift */; }; 25 | 0568C81D2B97A139004DF51A /* ArrayIsEmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0568C81C2B97A139004DF51A /* ArrayIsEmpty.swift */; }; 26 | 05E9E3452B96141800C7C80A /* NonInteractiveImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05E9E3442B96141800C7C80A /* NonInteractiveImageView.swift */; }; 27 | 05E9E34C2B96333100C7C80A /* ImageFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05E9E34B2B96333100C7C80A /* ImageFile.swift */; }; 28 | 05E9E34E2B9650DF00C7C80A /* TagValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05E9E34D2B9650DF00C7C80A /* TagValue.swift */; }; 29 | 05E9E3502B96511300C7C80A /* UppercaseString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05E9E34F2B96511300C7C80A /* UppercaseString.swift */; }; 30 | 05E9E3522B96519C00C7C80A /* TagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05E9E3512B96519C00C7C80A /* TagView.swift */; }; 31 | /* End PBXBuildFile section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | 05638ED22B94A3630030FFE6 /* MetaCopy.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MetaCopy.app; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 05638ED52B94A3630030FFE6 /* ApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationDelegate.swift; sourceTree = ""; }; 36 | 05638ED72B94A3650030FFE6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 37 | 05638EDA2B94A3650030FFE6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 38 | 05638EDC2B94A3650030FFE6 /* MetaCopy.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MetaCopy.entitlements; sourceTree = ""; }; 39 | 05638EE42B94A3DC0030FFE6 /* MetadataTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetadataTag.swift; sourceTree = ""; }; 40 | 05638EE62B94A8CC0030FFE6 /* MainWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainWindowController.swift; sourceTree = ""; }; 41 | 05638EE82B94A8FB0030FFE6 /* MainWindowController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainWindowController.xib; sourceTree = ""; }; 42 | 05638EEA2B94A93A0030FFE6 /* BackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundView.swift; sourceTree = ""; }; 43 | 05638EEC2B94AE0A0030FFE6 /* NSAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSAppearance.swift; sourceTree = ""; }; 44 | 0568C7B12B966E3E004DF51A /* ArrayIsNotEmpty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayIsNotEmpty.swift; sourceTree = ""; }; 45 | 0568C7B32B967C38004DF51A /* TagType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagType.swift; sourceTree = ""; }; 46 | 0568C7B62B9720AD004DF51A /* CODE_OF_CONDUCT.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CODE_OF_CONDUCT.md; sourceTree = ""; }; 47 | 0568C7B72B9720AD004DF51A /* Release - ccache.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Release - ccache.xcconfig"; sourceTree = ""; }; 48 | 0568C7B82B9720AD004DF51A /* Common.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Common.xcconfig; sourceTree = ""; }; 49 | 0568C7B92B9720AD004DF51A /* Debug - ccache.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Debug - ccache.xcconfig"; sourceTree = ""; }; 50 | 0568C7BA2B9720AD004DF51A /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; 51 | 0568C7BB2B9720AD004DF51A /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 52 | 0568C7BC2B9720AD004DF51A /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 53 | 0568C7BD2B9720AD004DF51A /* Release - Library.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Release - Library.xcconfig"; sourceTree = ""; }; 54 | 0568C7BF2B9720AD004DF51A /* Signing.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Signing.xcconfig; sourceTree = ""; }; 55 | 0568C7C02B9720AD004DF51A /* Architectures.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Architectures.xcconfig; sourceTree = ""; }; 56 | 0568C7C22B9720AD004DF51A /* Issues.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Issues.xcconfig; sourceTree = ""; }; 57 | 0568C7C42B9720AD004DF51A /* Apple-APIs.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Apple-APIs.xcconfig"; sourceTree = ""; }; 58 | 0568C7C52B9720AD004DF51A /* Generic-Issues.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Generic-Issues.xcconfig"; sourceTree = ""; }; 59 | 0568C7C62B9720AD004DF51A /* Security.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Security.xcconfig; sourceTree = ""; }; 60 | 0568C7C72B9720AD004DF51A /* Objective-C.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Objective-C.xcconfig"; sourceTree = ""; }; 61 | 0568C7C82B9720AD004DF51A /* Analysis-Policy.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Analysis-Policy.xcconfig"; sourceTree = ""; }; 62 | 0568C7C92B9720AD004DF51A /* Deployment.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Deployment.xcconfig; sourceTree = ""; }; 63 | 0568C7CA2B9720AD004DF51A /* Build-Options.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Build-Options.xcconfig"; sourceTree = ""; }; 64 | 0568C7CB2B9720AD004DF51A /* Swift-Compiler.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Swift-Compiler.xcconfig"; sourceTree = ""; }; 65 | 0568C7CC2B9720AD004DF51A /* Static-Analyzer.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Static-Analyzer.xcconfig"; sourceTree = ""; }; 66 | 0568C7CE2B9720AD004DF51A /* Warnings-Policies.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Warnings-Policies.xcconfig"; sourceTree = ""; }; 67 | 0568C7CF2B9720AD004DF51A /* Code-Generation.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Code-Generation.xcconfig"; sourceTree = ""; }; 68 | 0568C7D02B9720AD004DF51A /* Language.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Language.xcconfig; sourceTree = ""; }; 69 | 0568C7D12B9720AD004DF51A /* General.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = General.xcconfig; sourceTree = ""; }; 70 | 0568C7D22B9720AD004DF51A /* Search-Paths.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Search-Paths.xcconfig"; sourceTree = ""; }; 71 | 0568C7D32B9720AD004DF51A /* Apple-LLVM.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Apple-LLVM.xcconfig"; sourceTree = ""; }; 72 | 0568C7D62B9720AD004DF51A /* Modules.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Modules.xcconfig; sourceTree = ""; }; 73 | 0568C7D72B9720AD004DF51A /* Objective-C.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Objective-C.xcconfig"; sourceTree = ""; }; 74 | 0568C7D82B9720AD004DF51A /* C++.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "C++.xcconfig"; sourceTree = ""; }; 75 | 0568C7D92B9720AD004DF51A /* Code-Generation.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Code-Generation.xcconfig"; sourceTree = ""; }; 76 | 0568C7DA2B9720AD004DF51A /* Address-Sanitizer.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Address-Sanitizer.xcconfig"; sourceTree = ""; }; 77 | 0568C7DB2B9720AD004DF51A /* Language.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Language.xcconfig; sourceTree = ""; }; 78 | 0568C7DC2B9720AD004DF51A /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 79 | 0568C7DD2B9720AD004DF51A /* Undefined-Behavior-Sanitizer.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Undefined-Behavior-Sanitizer.xcconfig"; sourceTree = ""; }; 80 | 0568C7DE2B9720AD004DF51A /* Warning-Policies.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Warning-Policies.xcconfig"; sourceTree = ""; }; 81 | 0568C7E02B9720AD004DF51A /* Objective-C-ARC.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Objective-C-ARC.xcconfig"; sourceTree = ""; }; 82 | 0568C7E12B9720AD004DF51A /* Objective-C.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Objective-C.xcconfig"; sourceTree = ""; }; 83 | 0568C7E22B9720AD004DF51A /* C++.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "C++.xcconfig"; sourceTree = ""; }; 84 | 0568C7E32B9720AD004DF51A /* All-Languages.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "All-Languages.xcconfig"; sourceTree = ""; }; 85 | 0568C7E42B9720AD004DF51A /* Preprocessing.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Preprocessing.xcconfig; sourceTree = ""; }; 86 | 0568C7E52B9720AD004DF51A /* Debug - Library.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Debug - Library.xcconfig"; sourceTree = ""; }; 87 | 0568C7E62B9720AD004DF51A /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = ""; }; 88 | 0568C7E82B9720AD004DF51A /* ccache-config.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "ccache-config.sh"; sourceTree = ""; }; 89 | 0568C7E92B9720AD004DF51A /* ccache.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = ccache.sh; sourceTree = ""; }; 90 | 0568C7EB2B9720AD004DF51A /* FUNDING.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = FUNDING.yml; sourceTree = ""; }; 91 | 0568C7EC2B9720AD004DF51A /* Debug - zld.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Debug - zld.xcconfig"; sourceTree = ""; }; 92 | 0568C7ED2B97213C004DF51A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 93 | 0568C7EE2B9721CB004DF51A /* AboutWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutWindowController.swift; sourceTree = ""; }; 94 | 0568C7EF2B9721CB004DF51A /* AboutWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AboutWindowController.xib; sourceTree = ""; }; 95 | 0568C8012B979116004DF51A /* Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helper.swift; sourceTree = ""; }; 96 | 0568C8032B979212004DF51A /* NSError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSError.swift; sourceTree = ""; }; 97 | 0568C8052B979277004DF51A /* NSAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSAlert.swift; sourceTree = ""; }; 98 | 0568C81C2B97A139004DF51A /* ArrayIsEmpty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayIsEmpty.swift; sourceTree = ""; }; 99 | 05E9E3442B96141800C7C80A /* NonInteractiveImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonInteractiveImageView.swift; sourceTree = ""; }; 100 | 05E9E34B2B96333100C7C80A /* ImageFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageFile.swift; sourceTree = ""; }; 101 | 05E9E34D2B9650DF00C7C80A /* TagValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagValue.swift; sourceTree = ""; }; 102 | 05E9E34F2B96511300C7C80A /* UppercaseString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UppercaseString.swift; sourceTree = ""; }; 103 | 05E9E3512B96519C00C7C80A /* TagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagView.swift; sourceTree = ""; }; 104 | /* End PBXFileReference section */ 105 | 106 | /* Begin PBXFrameworksBuildPhase section */ 107 | 05638ECF2B94A3630030FFE6 /* Frameworks */ = { 108 | isa = PBXFrameworksBuildPhase; 109 | buildActionMask = 2147483647; 110 | files = ( 111 | ); 112 | runOnlyForDeploymentPostprocessing = 0; 113 | }; 114 | /* End PBXFrameworksBuildPhase section */ 115 | 116 | /* Begin PBXGroup section */ 117 | 05638EC92B94A3630030FFE6 = { 118 | isa = PBXGroup; 119 | children = ( 120 | 0568C7B52B9720AD004DF51A /* xcconfig */, 121 | 05638ED42B94A3630030FFE6 /* MetaCopy */, 122 | 05638ED32B94A3630030FFE6 /* Products */, 123 | ); 124 | sourceTree = ""; 125 | }; 126 | 05638ED32B94A3630030FFE6 /* Products */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | 05638ED22B94A3630030FFE6 /* MetaCopy.app */, 130 | ); 131 | name = Products; 132 | sourceTree = ""; 133 | }; 134 | 05638ED42B94A3630030FFE6 /* MetaCopy */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 05638EE22B94A3850030FFE6 /* Classes */, 138 | 05638EE32B94A38D0030FFE6 /* Supporting Files */, 139 | ); 140 | path = MetaCopy; 141 | sourceTree = ""; 142 | }; 143 | 05638EE22B94A3850030FFE6 /* Classes */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | 0568C7EE2B9721CB004DF51A /* AboutWindowController.swift */, 147 | 0568C7EF2B9721CB004DF51A /* AboutWindowController.xib */, 148 | 05638ED52B94A3630030FFE6 /* ApplicationDelegate.swift */, 149 | 0568C81C2B97A139004DF51A /* ArrayIsEmpty.swift */, 150 | 0568C7B12B966E3E004DF51A /* ArrayIsNotEmpty.swift */, 151 | 05638EEA2B94A93A0030FFE6 /* BackgroundView.swift */, 152 | 0568C8012B979116004DF51A /* Helper.swift */, 153 | 05E9E34B2B96333100C7C80A /* ImageFile.swift */, 154 | 05638EE62B94A8CC0030FFE6 /* MainWindowController.swift */, 155 | 05638EE82B94A8FB0030FFE6 /* MainWindowController.xib */, 156 | 05638EE42B94A3DC0030FFE6 /* MetadataTag.swift */, 157 | 05E9E3442B96141800C7C80A /* NonInteractiveImageView.swift */, 158 | 0568C8052B979277004DF51A /* NSAlert.swift */, 159 | 05638EEC2B94AE0A0030FFE6 /* NSAppearance.swift */, 160 | 0568C8032B979212004DF51A /* NSError.swift */, 161 | 0568C7B32B967C38004DF51A /* TagType.swift */, 162 | 05E9E34D2B9650DF00C7C80A /* TagValue.swift */, 163 | 05E9E3512B96519C00C7C80A /* TagView.swift */, 164 | 05E9E34F2B96511300C7C80A /* UppercaseString.swift */, 165 | ); 166 | path = Classes; 167 | sourceTree = ""; 168 | }; 169 | 05638EE32B94A38D0030FFE6 /* Supporting Files */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | 05638ED72B94A3650030FFE6 /* Assets.xcassets */, 173 | 0568C7ED2B97213C004DF51A /* Info.plist */, 174 | 05638ED92B94A3650030FFE6 /* MainMenu.xib */, 175 | 05638EDC2B94A3650030FFE6 /* MetaCopy.entitlements */, 176 | ); 177 | name = "Supporting Files"; 178 | sourceTree = ""; 179 | }; 180 | 0568C7B52B9720AD004DF51A /* xcconfig */ = { 181 | isa = PBXGroup; 182 | children = ( 183 | 0568C7B62B9720AD004DF51A /* CODE_OF_CONDUCT.md */, 184 | 0568C7B72B9720AD004DF51A /* Release - ccache.xcconfig */, 185 | 0568C7B82B9720AD004DF51A /* Common.xcconfig */, 186 | 0568C7B92B9720AD004DF51A /* Debug - ccache.xcconfig */, 187 | 0568C7BA2B9720AD004DF51A /* Debug.xcconfig */, 188 | 0568C7BB2B9720AD004DF51A /* Release.xcconfig */, 189 | 0568C7BC2B9720AD004DF51A /* README.md */, 190 | 0568C7BD2B9720AD004DF51A /* Release - Library.xcconfig */, 191 | 0568C7BE2B9720AD004DF51A /* Common */, 192 | 0568C7E52B9720AD004DF51A /* Debug - Library.xcconfig */, 193 | 0568C7E62B9720AD004DF51A /* .gitignore */, 194 | 0568C7E72B9720AD004DF51A /* Scripts */, 195 | 0568C7EA2B9720AD004DF51A /* .github */, 196 | 0568C7EC2B9720AD004DF51A /* Debug - zld.xcconfig */, 197 | ); 198 | name = xcconfig; 199 | path = Submodules/xcconfig; 200 | sourceTree = ""; 201 | }; 202 | 0568C7BE2B9720AD004DF51A /* Common */ = { 203 | isa = PBXGroup; 204 | children = ( 205 | 0568C7BF2B9720AD004DF51A /* Signing.xcconfig */, 206 | 0568C7C02B9720AD004DF51A /* Architectures.xcconfig */, 207 | 0568C7C12B9720AD004DF51A /* Static-Analyzer */, 208 | 0568C7C92B9720AD004DF51A /* Deployment.xcconfig */, 209 | 0568C7CA2B9720AD004DF51A /* Build-Options.xcconfig */, 210 | 0568C7CB2B9720AD004DF51A /* Swift-Compiler.xcconfig */, 211 | 0568C7CC2B9720AD004DF51A /* Static-Analyzer.xcconfig */, 212 | 0568C7CD2B9720AD004DF51A /* Swift-Compiler */, 213 | 0568C7D22B9720AD004DF51A /* Search-Paths.xcconfig */, 214 | 0568C7D32B9720AD004DF51A /* Apple-LLVM.xcconfig */, 215 | 0568C7D42B9720AD004DF51A /* Apple-LLVM */, 216 | ); 217 | path = Common; 218 | sourceTree = ""; 219 | }; 220 | 0568C7C12B9720AD004DF51A /* Static-Analyzer */ = { 221 | isa = PBXGroup; 222 | children = ( 223 | 0568C7C22B9720AD004DF51A /* Issues.xcconfig */, 224 | 0568C7C32B9720AD004DF51A /* Issues */, 225 | ); 226 | path = "Static-Analyzer"; 227 | sourceTree = ""; 228 | }; 229 | 0568C7C32B9720AD004DF51A /* Issues */ = { 230 | isa = PBXGroup; 231 | children = ( 232 | 0568C7C42B9720AD004DF51A /* Apple-APIs.xcconfig */, 233 | 0568C7C52B9720AD004DF51A /* Generic-Issues.xcconfig */, 234 | 0568C7C62B9720AD004DF51A /* Security.xcconfig */, 235 | 0568C7C72B9720AD004DF51A /* Objective-C.xcconfig */, 236 | 0568C7C82B9720AD004DF51A /* Analysis-Policy.xcconfig */, 237 | ); 238 | path = Issues; 239 | sourceTree = ""; 240 | }; 241 | 0568C7CD2B9720AD004DF51A /* Swift-Compiler */ = { 242 | isa = PBXGroup; 243 | children = ( 244 | 0568C7CE2B9720AD004DF51A /* Warnings-Policies.xcconfig */, 245 | 0568C7CF2B9720AD004DF51A /* Code-Generation.xcconfig */, 246 | 0568C7D02B9720AD004DF51A /* Language.xcconfig */, 247 | 0568C7D12B9720AD004DF51A /* General.xcconfig */, 248 | ); 249 | path = "Swift-Compiler"; 250 | sourceTree = ""; 251 | }; 252 | 0568C7D42B9720AD004DF51A /* Apple-LLVM */ = { 253 | isa = PBXGroup; 254 | children = ( 255 | 0568C7D52B9720AD004DF51A /* Language */, 256 | 0568C7D92B9720AD004DF51A /* Code-Generation.xcconfig */, 257 | 0568C7DA2B9720AD004DF51A /* Address-Sanitizer.xcconfig */, 258 | 0568C7DB2B9720AD004DF51A /* Language.xcconfig */, 259 | 0568C7DC2B9720AD004DF51A /* Warnings.xcconfig */, 260 | 0568C7DD2B9720AD004DF51A /* Undefined-Behavior-Sanitizer.xcconfig */, 261 | 0568C7DE2B9720AD004DF51A /* Warning-Policies.xcconfig */, 262 | 0568C7DF2B9720AD004DF51A /* Warnings */, 263 | 0568C7E42B9720AD004DF51A /* Preprocessing.xcconfig */, 264 | ); 265 | path = "Apple-LLVM"; 266 | sourceTree = ""; 267 | }; 268 | 0568C7D52B9720AD004DF51A /* Language */ = { 269 | isa = PBXGroup; 270 | children = ( 271 | 0568C7D62B9720AD004DF51A /* Modules.xcconfig */, 272 | 0568C7D72B9720AD004DF51A /* Objective-C.xcconfig */, 273 | 0568C7D82B9720AD004DF51A /* C++.xcconfig */, 274 | ); 275 | path = Language; 276 | sourceTree = ""; 277 | }; 278 | 0568C7DF2B9720AD004DF51A /* Warnings */ = { 279 | isa = PBXGroup; 280 | children = ( 281 | 0568C7E02B9720AD004DF51A /* Objective-C-ARC.xcconfig */, 282 | 0568C7E12B9720AD004DF51A /* Objective-C.xcconfig */, 283 | 0568C7E22B9720AD004DF51A /* C++.xcconfig */, 284 | 0568C7E32B9720AD004DF51A /* All-Languages.xcconfig */, 285 | ); 286 | path = Warnings; 287 | sourceTree = ""; 288 | }; 289 | 0568C7E72B9720AD004DF51A /* Scripts */ = { 290 | isa = PBXGroup; 291 | children = ( 292 | 0568C7E82B9720AD004DF51A /* ccache-config.sh */, 293 | 0568C7E92B9720AD004DF51A /* ccache.sh */, 294 | ); 295 | path = Scripts; 296 | sourceTree = ""; 297 | }; 298 | 0568C7EA2B9720AD004DF51A /* .github */ = { 299 | isa = PBXGroup; 300 | children = ( 301 | 0568C7EB2B9720AD004DF51A /* FUNDING.yml */, 302 | ); 303 | path = .github; 304 | sourceTree = ""; 305 | }; 306 | /* End PBXGroup section */ 307 | 308 | /* Begin PBXNativeTarget section */ 309 | 05638ED12B94A3630030FFE6 /* MetaCopy */ = { 310 | isa = PBXNativeTarget; 311 | buildConfigurationList = 05638EDF2B94A3650030FFE6 /* Build configuration list for PBXNativeTarget "MetaCopy" */; 312 | buildPhases = ( 313 | 05638ECE2B94A3630030FFE6 /* Sources */, 314 | 05638ECF2B94A3630030FFE6 /* Frameworks */, 315 | 0568C81B2B979FA4004DF51A /* ShellScript */, 316 | 05638ED02B94A3630030FFE6 /* Resources */, 317 | ); 318 | buildRules = ( 319 | ); 320 | dependencies = ( 321 | ); 322 | name = MetaCopy; 323 | productName = "EXIF Copy"; 324 | productReference = 05638ED22B94A3630030FFE6 /* MetaCopy.app */; 325 | productType = "com.apple.product-type.application"; 326 | }; 327 | /* End PBXNativeTarget section */ 328 | 329 | /* Begin PBXProject section */ 330 | 05638ECA2B94A3630030FFE6 /* Project object */ = { 331 | isa = PBXProject; 332 | attributes = { 333 | BuildIndependentTargetsInParallel = 1; 334 | LastSwiftUpdateCheck = 1520; 335 | LastUpgradeCheck = 1520; 336 | TargetAttributes = { 337 | 05638ED12B94A3630030FFE6 = { 338 | CreatedOnToolsVersion = 15.2; 339 | }; 340 | }; 341 | }; 342 | buildConfigurationList = 05638ECD2B94A3630030FFE6 /* Build configuration list for PBXProject "MetaCopy" */; 343 | compatibilityVersion = "Xcode 14.0"; 344 | developmentRegion = en; 345 | hasScannedForEncodings = 0; 346 | knownRegions = ( 347 | en, 348 | Base, 349 | ); 350 | mainGroup = 05638EC92B94A3630030FFE6; 351 | productRefGroup = 05638ED32B94A3630030FFE6 /* Products */; 352 | projectDirPath = ""; 353 | projectRoot = ""; 354 | targets = ( 355 | 05638ED12B94A3630030FFE6 /* MetaCopy */, 356 | ); 357 | }; 358 | /* End PBXProject section */ 359 | 360 | /* Begin PBXResourcesBuildPhase section */ 361 | 05638ED02B94A3630030FFE6 /* Resources */ = { 362 | isa = PBXResourcesBuildPhase; 363 | buildActionMask = 2147483647; 364 | files = ( 365 | 05638ED82B94A3650030FFE6 /* Assets.xcassets in Resources */, 366 | 0568C7F12B9721CB004DF51A /* AboutWindowController.xib in Resources */, 367 | 05638EE92B94A8FB0030FFE6 /* MainWindowController.xib in Resources */, 368 | 05638EDB2B94A3650030FFE6 /* MainMenu.xib in Resources */, 369 | ); 370 | runOnlyForDeploymentPostprocessing = 0; 371 | }; 372 | /* End PBXResourcesBuildPhase section */ 373 | 374 | /* Begin PBXShellScriptBuildPhase section */ 375 | 0568C81B2B979FA4004DF51A /* ShellScript */ = { 376 | isa = PBXShellScriptBuildPhase; 377 | alwaysOutOfDate = 1; 378 | buildActionMask = 2147483647; 379 | files = ( 380 | ); 381 | inputFileListPaths = ( 382 | ); 383 | inputPaths = ( 384 | ); 385 | outputFileListPaths = ( 386 | ); 387 | outputPaths = ( 388 | ); 389 | runOnlyForDeploymentPostprocessing = 0; 390 | shellPath = /bin/sh; 391 | shellScript = "#!/bin/bash\nif [ \"${CONFIGURATION}\" = \"Release\" ]; then\n plist=\"MetaCopy/Info.plist\"\n rev=$(git rev-list `git branch | grep -e \"^*\" | cut -d' ' -f 2` | wc -l)\n /usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $rev\" \"$plist\"\nfi\n"; 392 | }; 393 | /* End PBXShellScriptBuildPhase section */ 394 | 395 | /* Begin PBXSourcesBuildPhase section */ 396 | 05638ECE2B94A3630030FFE6 /* Sources */ = { 397 | isa = PBXSourcesBuildPhase; 398 | buildActionMask = 2147483647; 399 | files = ( 400 | 05638EE52B94A3DC0030FFE6 /* MetadataTag.swift in Sources */, 401 | 0568C8062B979277004DF51A /* NSAlert.swift in Sources */, 402 | 0568C8022B979116004DF51A /* Helper.swift in Sources */, 403 | 05E9E3522B96519C00C7C80A /* TagView.swift in Sources */, 404 | 05638ED62B94A3630030FFE6 /* ApplicationDelegate.swift in Sources */, 405 | 0568C7F02B9721CB004DF51A /* AboutWindowController.swift in Sources */, 406 | 05E9E3502B96511300C7C80A /* UppercaseString.swift in Sources */, 407 | 05E9E34E2B9650DF00C7C80A /* TagValue.swift in Sources */, 408 | 0568C81D2B97A139004DF51A /* ArrayIsEmpty.swift in Sources */, 409 | 05638EEB2B94A93A0030FFE6 /* BackgroundView.swift in Sources */, 410 | 0568C7B42B967C38004DF51A /* TagType.swift in Sources */, 411 | 05638EE72B94A8CC0030FFE6 /* MainWindowController.swift in Sources */, 412 | 05E9E3452B96141800C7C80A /* NonInteractiveImageView.swift in Sources */, 413 | 05E9E34C2B96333100C7C80A /* ImageFile.swift in Sources */, 414 | 05638EED2B94AE0A0030FFE6 /* NSAppearance.swift in Sources */, 415 | 0568C7B22B966E3E004DF51A /* ArrayIsNotEmpty.swift in Sources */, 416 | 0568C8042B979212004DF51A /* NSError.swift in Sources */, 417 | ); 418 | runOnlyForDeploymentPostprocessing = 0; 419 | }; 420 | /* End PBXSourcesBuildPhase section */ 421 | 422 | /* Begin PBXVariantGroup section */ 423 | 05638ED92B94A3650030FFE6 /* MainMenu.xib */ = { 424 | isa = PBXVariantGroup; 425 | children = ( 426 | 05638EDA2B94A3650030FFE6 /* Base */, 427 | ); 428 | name = MainMenu.xib; 429 | sourceTree = ""; 430 | }; 431 | /* End PBXVariantGroup section */ 432 | 433 | /* Begin XCBuildConfiguration section */ 434 | 05638EDD2B94A3650030FFE6 /* Debug */ = { 435 | isa = XCBuildConfiguration; 436 | baseConfigurationReference = 0568C7BA2B9720AD004DF51A /* Debug.xcconfig */; 437 | buildSettings = { 438 | MACOSX_DEPLOYMENT_TARGET = 13.0; 439 | }; 440 | name = Debug; 441 | }; 442 | 05638EDE2B94A3650030FFE6 /* Release */ = { 443 | isa = XCBuildConfiguration; 444 | baseConfigurationReference = 0568C7BB2B9720AD004DF51A /* Release.xcconfig */; 445 | buildSettings = { 446 | MACOSX_DEPLOYMENT_TARGET = 13.0; 447 | }; 448 | name = Release; 449 | }; 450 | 05638EE02B94A3650030FFE6 /* Debug */ = { 451 | isa = XCBuildConfiguration; 452 | buildSettings = { 453 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 454 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 455 | CODE_SIGN_ENTITLEMENTS = MetaCopy/MetaCopy.entitlements; 456 | INFOPLIST_FILE = MetaCopy/Info.plist; 457 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.photography"; 458 | LD_RUNPATH_SEARCH_PATHS = ( 459 | "$(inherited)", 460 | "@executable_path/../Frameworks", 461 | ); 462 | PRODUCT_BUNDLE_IDENTIFIER = "com.xs-labs.MetaCopy"; 463 | PRODUCT_NAME = "$(TARGET_NAME)"; 464 | }; 465 | name = Debug; 466 | }; 467 | 05638EE12B94A3650030FFE6 /* Release */ = { 468 | isa = XCBuildConfiguration; 469 | buildSettings = { 470 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 471 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 472 | CODE_SIGN_ENTITLEMENTS = MetaCopy/MetaCopy.entitlements; 473 | INFOPLIST_FILE = MetaCopy/Info.plist; 474 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.photography"; 475 | LD_RUNPATH_SEARCH_PATHS = ( 476 | "$(inherited)", 477 | "@executable_path/../Frameworks", 478 | ); 479 | PRODUCT_BUNDLE_IDENTIFIER = "com.xs-labs.MetaCopy"; 480 | PRODUCT_NAME = "$(TARGET_NAME)"; 481 | }; 482 | name = Release; 483 | }; 484 | /* End XCBuildConfiguration section */ 485 | 486 | /* Begin XCConfigurationList section */ 487 | 05638ECD2B94A3630030FFE6 /* Build configuration list for PBXProject "MetaCopy" */ = { 488 | isa = XCConfigurationList; 489 | buildConfigurations = ( 490 | 05638EDD2B94A3650030FFE6 /* Debug */, 491 | 05638EDE2B94A3650030FFE6 /* Release */, 492 | ); 493 | defaultConfigurationIsVisible = 0; 494 | defaultConfigurationName = Release; 495 | }; 496 | 05638EDF2B94A3650030FFE6 /* Build configuration list for PBXNativeTarget "MetaCopy" */ = { 497 | isa = XCConfigurationList; 498 | buildConfigurations = ( 499 | 05638EE02B94A3650030FFE6 /* Debug */, 500 | 05638EE12B94A3650030FFE6 /* Release */, 501 | ); 502 | defaultConfigurationIsVisible = 0; 503 | defaultConfigurationName = Release; 504 | }; 505 | /* End XCConfigurationList section */ 506 | }; 507 | rootObject = 05638ECA2B94A3630030FFE6 /* Project object */; 508 | } 509 | -------------------------------------------------------------------------------- /MetaCopy.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MetaCopy.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MetaCopy/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /MetaCopy/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon-16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "Icon-32 1.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "Icon-32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "Icon-64.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "Icon-128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "Icon-256 1.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "Icon-256.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "Icon-512 1.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "Icon-512.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "Icon-1024.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /MetaCopy/Assets.xcassets/AppIcon.appiconset/Icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macmade/MetaCopy/9ef8b8816ed6215e10f81bdb6efa3d4729f234ac/MetaCopy/Assets.xcassets/AppIcon.appiconset/Icon-1024.png -------------------------------------------------------------------------------- /MetaCopy/Assets.xcassets/AppIcon.appiconset/Icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macmade/MetaCopy/9ef8b8816ed6215e10f81bdb6efa3d4729f234ac/MetaCopy/Assets.xcassets/AppIcon.appiconset/Icon-128.png -------------------------------------------------------------------------------- /MetaCopy/Assets.xcassets/AppIcon.appiconset/Icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macmade/MetaCopy/9ef8b8816ed6215e10f81bdb6efa3d4729f234ac/MetaCopy/Assets.xcassets/AppIcon.appiconset/Icon-16.png -------------------------------------------------------------------------------- /MetaCopy/Assets.xcassets/AppIcon.appiconset/Icon-256 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macmade/MetaCopy/9ef8b8816ed6215e10f81bdb6efa3d4729f234ac/MetaCopy/Assets.xcassets/AppIcon.appiconset/Icon-256 1.png -------------------------------------------------------------------------------- /MetaCopy/Assets.xcassets/AppIcon.appiconset/Icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macmade/MetaCopy/9ef8b8816ed6215e10f81bdb6efa3d4729f234ac/MetaCopy/Assets.xcassets/AppIcon.appiconset/Icon-256.png -------------------------------------------------------------------------------- /MetaCopy/Assets.xcassets/AppIcon.appiconset/Icon-32 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macmade/MetaCopy/9ef8b8816ed6215e10f81bdb6efa3d4729f234ac/MetaCopy/Assets.xcassets/AppIcon.appiconset/Icon-32 1.png -------------------------------------------------------------------------------- /MetaCopy/Assets.xcassets/AppIcon.appiconset/Icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macmade/MetaCopy/9ef8b8816ed6215e10f81bdb6efa3d4729f234ac/MetaCopy/Assets.xcassets/AppIcon.appiconset/Icon-32.png -------------------------------------------------------------------------------- /MetaCopy/Assets.xcassets/AppIcon.appiconset/Icon-512 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macmade/MetaCopy/9ef8b8816ed6215e10f81bdb6efa3d4729f234ac/MetaCopy/Assets.xcassets/AppIcon.appiconset/Icon-512 1.png -------------------------------------------------------------------------------- /MetaCopy/Assets.xcassets/AppIcon.appiconset/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macmade/MetaCopy/9ef8b8816ed6215e10f81bdb6efa3d4729f234ac/MetaCopy/Assets.xcassets/AppIcon.appiconset/Icon-512.png -------------------------------------------------------------------------------- /MetaCopy/Assets.xcassets/AppIcon.appiconset/Icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macmade/MetaCopy/9ef8b8816ed6215e10f81bdb6efa3d4729f234ac/MetaCopy/Assets.xcassets/AppIcon.appiconset/Icon-64.png -------------------------------------------------------------------------------- /MetaCopy/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /MetaCopy/Base.lproj/MainMenu.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 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 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 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 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 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | Default 500 | 501 | 502 | 503 | 504 | 505 | 506 | Left to Right 507 | 508 | 509 | 510 | 511 | 512 | 513 | Right to Left 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | Default 525 | 526 | 527 | 528 | 529 | 530 | 531 | Left to Right 532 | 533 | 534 | 535 | 536 | 537 | 538 | Right to Left 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | -------------------------------------------------------------------------------- /MetaCopy/Classes/AboutWindowController.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Cocoa 26 | 27 | public class AboutWindowController: NSWindowController 28 | { 29 | @objc private dynamic var name: String? 30 | @objc private dynamic var version: String? 31 | @objc private dynamic var copyright: String? 32 | 33 | public override var windowNibName: NSNib.Name? 34 | { 35 | "AboutWindowController" 36 | } 37 | 38 | public override func windowDidLoad() 39 | { 40 | super.windowDidLoad() 41 | 42 | let version = Bundle.main.object( forInfoDictionaryKey: "CFBundleShortVersionString" ) as? String ?? "0.0.0" 43 | 44 | if let build = Bundle.main.object( forInfoDictionaryKey: "CFBundleVersion" ) as? String 45 | { 46 | self.version = "\( version ) (\( build ))" 47 | } 48 | else 49 | { 50 | self.version = version 51 | } 52 | 53 | self.name = Bundle.main.object( forInfoDictionaryKey: "CFBundleName" ) as? String 54 | self.copyright = Bundle.main.object( forInfoDictionaryKey: "NSHumanReadableCopyright" ) as? String 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /MetaCopy/Classes/AboutWindowController.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 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /MetaCopy/Classes/ApplicationDelegate.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Cocoa 26 | 27 | @main 28 | class ApplicationDelegate: NSObject, NSApplicationDelegate 29 | { 30 | private var controllers: [ MainWindowController ] = [] 31 | 32 | @objc public private( set ) dynamic var aboutController = AboutWindowController() 33 | 34 | func applicationDidFinishLaunching( _ notification: Notification ) 35 | { 36 | self.openNewWindow( restore: true ) 37 | 38 | NotificationCenter.default.addObserver( forName: NSWindow.willCloseNotification, object: nil, queue: nil ) 39 | { 40 | notification in self.controllers.removeAll 41 | { 42 | $0.window === notification.object as? NSWindow 43 | } 44 | } 45 | } 46 | 47 | func applicationWillTerminate( _ notification: Notification ) 48 | {} 49 | 50 | func applicationSupportsSecureRestorableState( _ app: NSApplication ) -> Bool 51 | { 52 | false 53 | } 54 | 55 | @IBAction 56 | public func showAboutWindow( _ sender: Any? ) 57 | { 58 | guard let window = self.aboutController.window 59 | else 60 | { 61 | return 62 | } 63 | 64 | if window.isVisible == false 65 | { 66 | window.center() 67 | } 68 | 69 | window.makeKeyAndOrderFront( sender ) 70 | } 71 | 72 | @IBAction 73 | public func invertAppearance( _ sender: Any? ) 74 | { 75 | switch NSApp.effectiveAppearance.name 76 | { 77 | case .accessibilityHighContrastAqua: NSApp.appearance = NSAppearance( named: .accessibilityHighContrastDarkAqua ) 78 | case .accessibilityHighContrastDarkAqua: NSApp.appearance = NSAppearance( named: .accessibilityHighContrastAqua ) 79 | case .accessibilityHighContrastVibrantLight: NSApp.appearance = NSAppearance( named: .accessibilityHighContrastVibrantDark ) 80 | case .accessibilityHighContrastVibrantDark: NSApp.appearance = NSAppearance( named: .accessibilityHighContrastVibrantLight ) 81 | case .aqua: NSApp.appearance = NSAppearance( named: .darkAqua ) 82 | case .darkAqua: NSApp.appearance = NSAppearance( named: .aqua ) 83 | case .vibrantLight: NSApp.appearance = NSAppearance( named: .vibrantDark ) 84 | case .vibrantDark: NSApp.appearance = NSAppearance( named: .vibrantLight ) 85 | 86 | default: break 87 | } 88 | } 89 | 90 | @IBAction 91 | public func newDocument( _ sender: Any? ) 92 | { 93 | self.openNewWindow( restore: false ) 94 | } 95 | 96 | private func openNewWindow( restore: Bool ) 97 | { 98 | let controller = MainWindowController( restore: restore ) 99 | 100 | controller.window?.makeKeyAndOrderFront( nil ) 101 | self.controllers.append( controller ) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /MetaCopy/Classes/ArrayIsEmpty.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Foundation 26 | 27 | @objc( ArrayIsEmpty ) 28 | public class ArrayIsEmpty: ValueTransformer 29 | { 30 | public override class func transformedValueClass() -> AnyClass 31 | { 32 | NSNumber.self 33 | } 34 | 35 | public override class func allowsReverseTransformation() -> Bool 36 | { 37 | false 38 | } 39 | 40 | public override func transformedValue( _ value: Any? ) -> Any? 41 | { 42 | if let array = value as? [ Any ], array.isEmpty == false 43 | { 44 | return NSNumber( booleanLiteral: false ) 45 | } 46 | 47 | return NSNumber( booleanLiteral: true ) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /MetaCopy/Classes/ArrayIsNotEmpty.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Foundation 26 | 27 | @objc( ArrayIsNotEmpty ) 28 | public class ArrayIsNotEmpty: ValueTransformer 29 | { 30 | public override class func transformedValueClass() -> AnyClass 31 | { 32 | NSNumber.self 33 | } 34 | 35 | public override class func allowsReverseTransformation() -> Bool 36 | { 37 | false 38 | } 39 | 40 | public override func transformedValue( _ value: Any? ) -> Any? 41 | { 42 | if let array = value as? [ Any ], array.isEmpty == false 43 | { 44 | return NSNumber( booleanLiteral: true ) 45 | } 46 | 47 | return NSNumber( booleanLiteral: false ) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /MetaCopy/Classes/BackgroundView.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Cocoa 26 | 27 | public class BackgroundView: NSView 28 | { 29 | @objc private dynamic var dragging = false 30 | { 31 | didSet 32 | { 33 | self.needsDisplay = true 34 | } 35 | } 36 | 37 | public var onDrag: ( ( NSDraggingInfo ) -> NSDragOperation )? 38 | public var onDrop: ( ( NSDraggingInfo ) -> Bool )? 39 | 40 | public override init( frame: NSRect ) 41 | { 42 | super.init( frame: frame ) 43 | self.registerForDraggedTypes( [ .fileURL ] ) 44 | } 45 | 46 | public required init?( coder: NSCoder ) 47 | { 48 | super.init( coder: coder ) 49 | self.registerForDraggedTypes( [ .fileURL ] ) 50 | } 51 | 52 | public override func draggingEntered( _ sender: NSDraggingInfo ) -> NSDragOperation 53 | { 54 | let operation = self.onDrag?( sender ) ?? [] 55 | self.dragging = operation.isEmpty == false 56 | 57 | return operation 58 | } 59 | 60 | public override func draggingExited( _ sender: NSDraggingInfo? ) 61 | { 62 | self.dragging = false 63 | } 64 | 65 | public override func performDragOperation( _ sender: NSDraggingInfo ) -> Bool 66 | { 67 | self.dragging = false 68 | 69 | return self.onDrop?( sender ) ?? false 70 | } 71 | 72 | public override func draw( _ rect: NSRect ) 73 | { 74 | let path = NSBezierPath( roundedRect: self.bounds, xRadius: 10, yRadius: 10 ) 75 | 76 | if self.effectiveAppearance.isDark 77 | { 78 | NSColor.white.withAlphaComponent( 0.1 ).setFill() 79 | } 80 | else 81 | { 82 | NSColor.black.withAlphaComponent( 0.1 ).setFill() 83 | } 84 | 85 | path.fill() 86 | 87 | if self.dragging 88 | { 89 | path.lineWidth = 1 90 | 91 | NSColor.controlAccentColor.setStroke() 92 | path.stroke() 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /MetaCopy/Classes/Helper.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Cocoa 26 | import UniformTypeIdentifiers 27 | 28 | public class Helper 29 | { 30 | private init() 31 | {} 32 | 33 | public class func isImage( url: URL ) -> Bool 34 | { 35 | UTType( filenameExtension: url.pathExtension )?.conforms( to: .image ) ?? false 36 | } 37 | 38 | public class func isRawImage( url: URL ) -> Bool 39 | { 40 | UTType( filenameExtension: url.pathExtension )?.conforms( to: .rawImage ) ?? false 41 | } 42 | 43 | public class func images( in pasteboard: NSPasteboard, allowRaw: Bool ) -> [ URL ] 44 | { 45 | guard let urls = pasteboard.readObjects( forClasses: [ NSURL.self ], options: nil ) as? [ NSURL ] 46 | else 47 | { 48 | return [] 49 | } 50 | 51 | return self.images( in: urls.map { $0 as URL }, allowRaw: allowRaw ) 52 | } 53 | 54 | public class func images( in urls: [ URL ], allowRaw: Bool ) -> [ URL ] 55 | { 56 | let images: [ [ URL ] ] = urls.map 57 | { 58 | url in 59 | 60 | var isDir = ObjCBool( booleanLiteral: false ) 61 | 62 | guard FileManager.default.fileExists( atPath: url.path( percentEncoded: false ), isDirectory: &isDir ) 63 | else 64 | { 65 | return [] 66 | } 67 | 68 | if isDir.boolValue 69 | { 70 | return FileManager.default.enumerator( atPath: url.path( percentEncoded: false ) )?.compactMap 71 | { 72 | if let component = $0 as? String 73 | { 74 | let url = url.appendingPathComponent( component ) 75 | 76 | if self.isImage( url: url ), ( allowRaw == true || self.isRawImage( url: url ) == false ) 77 | { 78 | return url 79 | } 80 | } 81 | 82 | return nil 83 | } 84 | ?? [] 85 | } 86 | else if self.isImage( url: url ), ( allowRaw == true || self.isRawImage( url: url ) == false ) 87 | { 88 | return [ url ] 89 | } 90 | else 91 | { 92 | return [] 93 | } 94 | } 95 | 96 | return images.flatMap { $0 } 97 | } 98 | 99 | public class func copyMetadata( from source: ImageFile, to destination: [ ImageFile ] ) throws 100 | { 101 | guard let originalMetadata = source.metadata 102 | else 103 | { 104 | throw NSError( message: "No metadata found in source image." ) 105 | } 106 | 107 | guard let metadata = CGImageMetadataCreateMutableCopy( originalMetadata ) 108 | else 109 | { 110 | throw NSError( message: "Cannot create a valid metadata container." ) 111 | } 112 | 113 | source.tags.forEach 114 | { 115 | if $0.selected == false, let id = $0.id 116 | { 117 | CGImageMetadataRemoveTagWithPath( metadata, nil, id as NSString ) 118 | } 119 | } 120 | 121 | try destination.forEach 122 | { 123 | guard let destination = CGImageDestinationCreateWithURL( $0.url as NSURL, $0.type as NSString, 1, nil ) 124 | else 125 | { 126 | throw NSError( message: "Cannot open file for writing: \( $0.url.lastPathComponent )" ) 127 | } 128 | 129 | guard let image = CGImageSourceCreateImageAtIndex( $0.source, 0, nil ) 130 | else 131 | { 132 | throw NSError( message: "Cannot read destination image: \( $0.url.lastPathComponent )" ) 133 | } 134 | 135 | CGImageDestinationAddImageAndMetadata( destination, image, metadata, nil ) 136 | 137 | if CGImageDestinationFinalize( destination ) == false 138 | { 139 | throw NSError( message: "Cannot write file: \( $0.url.lastPathComponent )" ) 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /MetaCopy/Classes/ImageFile.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Cocoa 26 | 27 | public class ImageFile: NSObject 28 | { 29 | @objc public private( set ) dynamic var source: CGImageSource 30 | @objc public private( set ) dynamic var metadata: CGImageMetadata? 31 | @objc public private( set ) dynamic var url: URL 32 | @objc public private( set ) dynamic var type: String 33 | @objc public private( set ) dynamic var name: String 34 | @objc public private( set ) dynamic var image: NSImage 35 | @objc public private( set ) dynamic var tags: [ MetadataTag ] = [] 36 | 37 | init?( url: URL, processInfo: Bool ) 38 | { 39 | guard FileManager.default.fileExists( atPath: url.path( percentEncoded: false ) ), 40 | let image = NSImage( contentsOf: url ) 41 | else 42 | { 43 | return nil 44 | } 45 | 46 | guard let source = CGImageSourceCreateWithURL( url as NSURL, nil ), 47 | CGImageSourceGetStatus( source ) == .statusComplete 48 | else 49 | { 50 | return nil 51 | } 52 | 53 | guard let type = CGImageSourceGetType( source ) 54 | else 55 | { 56 | return nil 57 | } 58 | 59 | self.source = source 60 | self.metadata = CGImageSourceCopyMetadataAtIndex( source, 0, nil ) 61 | self.type = type as String 62 | self.url = url 63 | self.name = url.lastPathComponent 64 | self.image = image 65 | 66 | if processInfo, let metadata = self.metadata 67 | { 68 | self.tags = MetadataTag.tags( for: metadata ) 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /MetaCopy/Classes/MainWindowController.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Cocoa 26 | import UniformTypeIdentifiers 27 | 28 | public class MainWindowController: NSWindowController, NSTableViewDelegate, NSTableViewDataSource 29 | { 30 | @objc private dynamic var sourceFile: ImageFile? 31 | { 32 | didSet 33 | { 34 | UserDefaults.standard.setValue( self.sourceFile?.url.path( percentEncoded: false ), forKey: "source" ) 35 | self.updateSelection() 36 | 37 | self.tagSelectionObservers = self.sourceFile?.tags.map 38 | { 39 | $0.observe( \.selected ) 40 | { 41 | [ weak self ] _, _ in self?.updateSelection() 42 | } 43 | } 44 | ?? [] 45 | } 46 | } 47 | 48 | @objc private dynamic var destinationFiles: [ ImageFile ] = [] 49 | { 50 | didSet 51 | { 52 | UserDefaults.standard.setValue( self.destinationFiles.map { $0.url.path( percentEncoded: false ) }, forKey: "destination" ) 53 | } 54 | } 55 | 56 | @objc private dynamic var allTagsSelected = false 57 | @objc private dynamic var processing = false 58 | 59 | private var restore: Bool 60 | private var tagSelectionObservers: [ NSKeyValueObservation ] = [] 61 | 62 | @IBOutlet private var destinationFilesController: NSArrayController? 63 | @IBOutlet private var tagsController: NSArrayController? 64 | @IBOutlet private var destinationFilesTableView: NSTableView? 65 | @IBOutlet private var tagsTableView: NSTableView? 66 | @IBOutlet private var destinationView: BackgroundView? 67 | @IBOutlet private var sourceView: BackgroundView? 68 | 69 | public init( restore: Bool ) 70 | { 71 | self.restore = restore 72 | 73 | super.init( window: nil ) 74 | } 75 | 76 | required init?( coder: NSCoder ) 77 | { 78 | nil 79 | } 80 | 81 | public override var windowNibName: NSNib.Name? 82 | { 83 | "MainWindowController" 84 | } 85 | 86 | public override func windowDidLoad() 87 | { 88 | super.windowDidLoad() 89 | 90 | if self.restore 91 | { 92 | if let source = UserDefaults.standard.string( forKey: "source" ) 93 | { 94 | self.sourceFile = ImageFile( url: URL( filePath: source ), processInfo: true ) 95 | } 96 | 97 | if let destination = UserDefaults.standard.value( forKey: "destination" ) as? [ String ] 98 | { 99 | self.destinationFiles = destination.compactMap { ImageFile( url: URL( filePath: $0 ), processInfo: false ) } 100 | } 101 | } 102 | 103 | self.destinationFilesController?.sortDescriptors = [ NSSortDescriptor( key: "name", ascending: true ) ] 104 | self.tagsController?.sortDescriptors = 105 | [ 106 | NSSortDescriptor( key: "prefix", ascending: true, selector: #selector( NSString.localizedCaseInsensitiveCompare( _: ) ) ), 107 | NSSortDescriptor( key: "name", ascending: true, selector: #selector( NSString.localizedCaseInsensitiveCompare( _: ) ) ), 108 | ] 109 | 110 | self.destinationFilesTableView?.sizeLastColumnToFit() 111 | self.tagsTableView?.sizeLastColumnToFit() 112 | self.updateSelection() 113 | 114 | self.sourceView?.onDrag = { [ weak self ] in self?.onSourceDrag( info: $0 ) ?? .copy } 115 | self.destinationView?.onDrag = { [ weak self ] in self?.onDestinationDrag( info: $0 ) ?? .copy } 116 | self.sourceView?.onDrop = { [ weak self ] in self?.onSourceDrop( info: $0 ) ?? false } 117 | self.destinationView?.onDrop = { [ weak self ] in self?.onDestinationDrop( info: $0 ) ?? false } 118 | } 119 | 120 | public func tableView( _ tableView: NSTableView, shouldSelectRow row: Int ) -> Bool 121 | { 122 | false 123 | } 124 | 125 | private func onSourceDrag( info: NSDraggingInfo ) -> NSDragOperation 126 | { 127 | Helper.images( in: info.draggingPasteboard, allowRaw: true ).count == 1 ? .copy : [] 128 | } 129 | 130 | private func onDestinationDrag( info: NSDraggingInfo ) -> NSDragOperation 131 | { 132 | Helper.images( in: info.draggingPasteboard, allowRaw: false ).count > 0 ? .copy : [] 133 | } 134 | 135 | private func onSourceDrop( info: NSDraggingInfo ) -> Bool 136 | { 137 | self.window?.makeKeyAndOrderFront( nil ) 138 | 139 | let images = Helper.images( in: info.draggingPasteboard, allowRaw: true ) 140 | 141 | if images.count == 1, let image = images.first 142 | { 143 | self.setSourceFile( url: image ) 144 | 145 | return true 146 | } 147 | 148 | return false 149 | } 150 | 151 | private func onDestinationDrop( info: NSDraggingInfo ) -> Bool 152 | { 153 | self.window?.makeKeyAndOrderFront( nil ) 154 | 155 | let images = Helper.images( in: info.draggingPasteboard, allowRaw: false ) 156 | 157 | if images.count > 0 158 | { 159 | self.setDestinationFiles( urls: images ) 160 | 161 | return true 162 | } 163 | 164 | return false 165 | } 166 | 167 | private func setSourceFile( url: URL ) 168 | { 169 | self.processing = true 170 | 171 | DispatchQueue.global( qos: .userInitiated ).async 172 | { 173 | let source = ImageFile( url: url, processInfo: true ) 174 | 175 | DispatchQueue.main.async 176 | { 177 | if source == nil 178 | { 179 | NSAlert.showError( message: "Cannot read the source image. Please make sure it is in a supported image format.", window: self.window ) 180 | } 181 | else if let source = source, source.tags.isEmpty 182 | { 183 | NSAlert.showError( message: "The source image does not appear to contain any metadata.", window: self.window ) 184 | } 185 | 186 | self.processing = false 187 | self.sourceFile = source 188 | } 189 | } 190 | } 191 | 192 | private func setDestinationFiles( urls: [ URL ] ) 193 | { 194 | self.processing = true 195 | 196 | DispatchQueue.global( qos: .userInitiated ).async 197 | { 198 | let files = Helper.images( in: urls, allowRaw: false ).compactMap { ImageFile( url: $0, processInfo: false ) } 199 | 200 | DispatchQueue.main.async 201 | { 202 | if files.isEmpty 203 | { 204 | NSAlert.showError( message: "No destination image was read.", window: self.window ) 205 | } 206 | 207 | self.processing = false 208 | self.destinationFiles = files 209 | } 210 | } 211 | } 212 | 213 | @IBAction 214 | public func selectSource( _ sender: Any? ) 215 | { 216 | guard let window = self.window 217 | else 218 | { 219 | NSSound.beep() 220 | 221 | return 222 | } 223 | 224 | let panel = NSOpenPanel() 225 | panel.canChooseFiles = true 226 | panel.canChooseDirectories = false 227 | panel.canDownloadUbiquitousContents = true 228 | panel.allowsMultipleSelection = false 229 | panel.allowedContentTypes = [ .image ] 230 | 231 | panel.beginSheetModal( for: window ) 232 | { 233 | if $0 == .OK, let url = panel.url 234 | { 235 | self.setSourceFile( url: url ) 236 | } 237 | } 238 | } 239 | 240 | @IBAction 241 | public func selectDestination( _ sender: Any? ) 242 | { 243 | guard let window = self.window 244 | else 245 | { 246 | NSSound.beep() 247 | 248 | return 249 | } 250 | 251 | let panel = NSOpenPanel() 252 | panel.canChooseFiles = true 253 | panel.canChooseDirectories = true 254 | panel.canDownloadUbiquitousContents = true 255 | panel.allowsMultipleSelection = true 256 | panel.allowedContentTypes = [ .image ] 257 | 258 | panel.beginSheetModal( for: window ) 259 | { 260 | if $0 == .OK 261 | { 262 | self.setDestinationFiles( urls: panel.urls ) 263 | } 264 | } 265 | } 266 | 267 | @IBAction 268 | private func selectAllTags( _ sender: Any? ) 269 | { 270 | let select = self.allTagsSelected ? true : false 271 | 272 | self.sourceFile?.tags.forEach 273 | { 274 | $0.selected = select 275 | } 276 | } 277 | 278 | @IBAction 279 | private func clearSource( _ sender: Any? ) 280 | { 281 | self.sourceFile = nil 282 | } 283 | 284 | @IBAction 285 | private func clearDestination( _ sender: Any? ) 286 | { 287 | self.destinationFiles = [] 288 | } 289 | 290 | private func updateSelection() 291 | { 292 | guard let source = self.sourceFile 293 | else 294 | { 295 | self.allTagsSelected = false 296 | 297 | return 298 | } 299 | 300 | let selected = source.tags.reduce( 0 ) 301 | { 302 | $0 + ( $1.selected ? 1 : 0 ) 303 | } 304 | 305 | if selected == source.tags.count 306 | { 307 | self.allTagsSelected = true 308 | } 309 | else 310 | { 311 | self.allTagsSelected = false 312 | } 313 | } 314 | 315 | @IBAction 316 | private func processImages( _ sender: Any? ) 317 | { 318 | guard let source = self.sourceFile, self.destinationFiles.isEmpty == false 319 | else 320 | { 321 | NSSound.beep() 322 | 323 | return 324 | } 325 | 326 | self.processing = true 327 | 328 | DispatchQueue.global( qos: .userInitiated ).async 329 | { 330 | defer 331 | { 332 | DispatchQueue.main.async 333 | { 334 | self.processing = false 335 | } 336 | } 337 | 338 | do 339 | { 340 | try Helper.copyMetadata( from: source, to: self.destinationFiles ) 341 | } 342 | catch let error 343 | { 344 | DispatchQueue.main.async 345 | { 346 | NSAlert.showError( error as NSError ) 347 | } 348 | } 349 | } 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /MetaCopy/Classes/MetadataTag.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Foundation 26 | import ImageIO 27 | 28 | @objc 29 | public class MetadataTag: NSObject 30 | { 31 | @objc public private( set ) var tag: CGImageMetadataTag 32 | @objc public private( set ) var id: String? 33 | @objc public private( set ) var namespace: String? 34 | @objc public private( set ) var prefix: String? 35 | @objc public private( set ) var name: String? 36 | @objc public private( set ) var value: CFTypeRef 37 | 38 | @objc public dynamic var selected = true 39 | 40 | public class func tags( for metadata: CGImageMetadata ) -> [ MetadataTag ] 41 | { 42 | var info = [ MetadataTag ]() 43 | 44 | CGImageMetadataEnumerateTagsUsingBlock( metadata, nil, nil ) 45 | { 46 | if let tag = MetadataTag( id: $0 as String, tag: $1 ) 47 | { 48 | info.append( tag ) 49 | } 50 | 51 | return true 52 | } 53 | 54 | return info 55 | } 56 | 57 | private init?( id: String, tag: CGImageMetadataTag ) 58 | { 59 | guard let value = CGImageMetadataTagCopyValue( tag ) 60 | else 61 | { 62 | return nil 63 | } 64 | 65 | self.tag = tag 66 | self.id = id 67 | self.prefix = CGImageMetadataTagCopyPrefix( tag ) as String? 68 | self.namespace = CGImageMetadataTagCopyNamespace( tag ) as String? 69 | self.name = CGImageMetadataTagCopyName( tag ) as String? ?? id 70 | self.value = value 71 | } 72 | 73 | public init?( tag: CGImageMetadataTag ) 74 | { 75 | guard let value = CGImageMetadataTagCopyValue( tag ) 76 | else 77 | { 78 | return nil 79 | } 80 | 81 | self.tag = tag 82 | self.id = nil 83 | self.prefix = CGImageMetadataTagCopyPrefix( tag ) as String? 84 | self.namespace = CGImageMetadataTagCopyNamespace( tag ) as String? 85 | self.name = CGImageMetadataTagCopyName( tag ) as String? 86 | self.value = value 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /MetaCopy/Classes/NSAlert.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Cocoa 26 | 27 | public extension NSAlert 28 | { 29 | class func showError( _ error: NSError, window: NSWindow? = nil ) 30 | { 31 | let title = error.userInfo[ NSLocalizedDescriptionKey ] as? String ?? "Error" 32 | let message = error.userInfo[ NSLocalizedRecoverySuggestionErrorKey ] as? String ?? "An unknown error occured." 33 | 34 | self.showError( title, message: message, window: window ) 35 | } 36 | 37 | class func showError( message: String, window: NSWindow? = nil ) 38 | { 39 | self.showError( "Error", message: message ) 40 | } 41 | 42 | class func showError( _ title: String, message: String, window: NSWindow? = nil ) 43 | { 44 | let alert = NSAlert() 45 | alert.messageText = title 46 | alert.informativeText = message 47 | 48 | alert.addButton( withTitle: "OK" ) 49 | 50 | if let window = window 51 | { 52 | alert.beginSheetModal( for: window ) 53 | } 54 | else 55 | { 56 | alert.runModal() 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /MetaCopy/Classes/NSAppearance.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Cocoa 26 | 27 | public extension NSAppearance 28 | { 29 | var isDark: Bool 30 | { 31 | switch NSApp.effectiveAppearance.name 32 | { 33 | case .accessibilityHighContrastDarkAqua: return true 34 | case .accessibilityHighContrastVibrantDark: return true 35 | case .darkAqua: return true 36 | case .vibrantDark: return true 37 | default: return false 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /MetaCopy/Classes/NSError.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Foundation 26 | 27 | public extension NSError 28 | { 29 | convenience init( title: String, message: String, code: Int = 0 ) 30 | { 31 | let info: [ String : Any ] = [ 32 | NSLocalizedDescriptionKey : title, 33 | NSLocalizedRecoverySuggestionErrorKey : message 34 | ] 35 | 36 | self.init( domain: NSCocoaErrorDomain, code: 0, userInfo: info ) 37 | } 38 | 39 | convenience init( message: String, code: Int = 0 ) 40 | { 41 | let info: [ String : Any ] = [ 42 | NSLocalizedRecoverySuggestionErrorKey : message 43 | ] 44 | 45 | self.init( domain: NSCocoaErrorDomain, code: 0, userInfo: info ) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /MetaCopy/Classes/NonInteractiveImageView.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Cocoa 26 | 27 | public class NonInteractiveImageView: NSImageView 28 | { 29 | public override init( frame: NSRect ) 30 | { 31 | super.init( frame: frame ) 32 | self.unregisterDraggedTypes() 33 | } 34 | 35 | public required init?( coder: NSCoder ) 36 | { 37 | super.init( coder: coder ) 38 | self.unregisterDraggedTypes() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /MetaCopy/Classes/TagType.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Foundation 26 | import ImageIO 27 | 28 | @objc( TagType ) 29 | public class TagType: ValueTransformer 30 | { 31 | public override class func transformedValueClass() -> AnyClass 32 | { 33 | NSString.self 34 | } 35 | 36 | public override class func allowsReverseTransformation() -> Bool 37 | { 38 | false 39 | } 40 | 41 | public override func transformedValue( _ value: Any? ) -> Any? 42 | { 43 | guard let tag = value as? MetadataTag 44 | else 45 | { 46 | return nil 47 | } 48 | 49 | return self.description( for: tag.value ) 50 | } 51 | 52 | private func description( for value: Any? ) -> String 53 | { 54 | if let _ = value as? String 55 | { 56 | return "String" 57 | } 58 | else if let _ = value as? NSNumber 59 | { 60 | return "Number" 61 | } 62 | else if let _ = value as? [ Any ] 63 | { 64 | return "Array" 65 | } 66 | else if let _ = value as? [ AnyHashable: Any ] 67 | { 68 | return "Dictionary" 69 | } 70 | 71 | return "--" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /MetaCopy/Classes/TagValue.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Foundation 26 | import ImageIO 27 | 28 | @objc( TagValue ) 29 | public class TagValue: ValueTransformer 30 | { 31 | public override class func transformedValueClass() -> AnyClass 32 | { 33 | NSString.self 34 | } 35 | 36 | public override class func allowsReverseTransformation() -> Bool 37 | { 38 | false 39 | } 40 | 41 | public override func transformedValue( _ value: Any? ) -> Any? 42 | { 43 | guard let tag = value as? MetadataTag 44 | else 45 | { 46 | return nil 47 | } 48 | 49 | return self.description( for: tag.value ) 50 | } 51 | 52 | private func description( for value: Any? ) -> String 53 | { 54 | if let value = value as? String, value.isEmpty == false 55 | { 56 | return value 57 | } 58 | else if let value = value as? NSNumber 59 | { 60 | return value.description 61 | } 62 | else if let value = value as? [ Any ], value.isEmpty == false 63 | { 64 | let description = value.map { self.description( for: $0 ) }.joined( separator: ", " ) 65 | 66 | return "[ \( description ) ]" 67 | } 68 | else if let value = value as? [ AnyHashable: Any ], value.isEmpty == false 69 | { 70 | let description = value.map 71 | { 72 | let key = self.description( for: $0.key ) 73 | let value = self.description( for: $0.value ) 74 | 75 | return "\( key ): \( value )" 76 | } 77 | .joined( separator: ", " ) 78 | 79 | return "[ \( description ) ]" 80 | } 81 | else if let value = value as? CFTypeRef 82 | { 83 | if CFGetTypeID( value ) == CGImageMetadataTagGetTypeID() 84 | { 85 | return self.transformedValue( MetadataTag( tag: value as! CGImageMetadataTag ) ) as? String ?? "--" 86 | } 87 | } 88 | 89 | return "--" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /MetaCopy/Classes/TagView.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Cocoa 26 | 27 | public class TagView: NSView 28 | { 29 | public override func draw( _ rect: NSRect ) 30 | { 31 | let path = NSBezierPath( roundedRect: self.bounds, xRadius: 5, yRadius: 5 ) 32 | 33 | NSColor.controlAccentColor.setFill() 34 | path.fill() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /MetaCopy/Classes/UppercaseString.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Foundation 26 | 27 | @objc( UppercaseString ) 28 | public class UppercaseString: ValueTransformer 29 | { 30 | public override class func transformedValueClass() -> AnyClass 31 | { 32 | NSString.self 33 | } 34 | 35 | public override class func allowsReverseTransformation() -> Bool 36 | { 37 | false 38 | } 39 | 40 | public override func transformedValue( _ value: Any? ) -> Any? 41 | { 42 | if let string = value as? String 43 | { 44 | return string.uppercased() 45 | } 46 | 47 | return nil 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /MetaCopy/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleVersion 20 | 14 21 | LSApplicationCategoryType 22 | public.app-category.photography 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2024 XS-Labs. All rights reserved. 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /MetaCopy/MetaCopy.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-write 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MetaCopy 2 | ======== 3 | 4 | [![Build Status](https://img.shields.io/github/actions/workflow/status/macmade/MetaCopy/ci-mac.yaml?label=macOS&logo=apple)](https://github.com/macmade/MetaCopy/actions/workflows/ci-mac.yaml) 5 | [![Issues](http://img.shields.io/github/issues/macmade/MetaCopy.svg?logo=github)](https://github.com/macmade/MetaCopy/issues) 6 | ![Status](https://img.shields.io/badge/status-active-brightgreen.svg?logo=git) 7 | ![License](https://img.shields.io/badge/license-mit-brightgreen.svg?logo=open-source-initiative) 8 | [![Contact](https://img.shields.io/badge/follow-@macmade-blue.svg?logo=twitter&style=social)](https://twitter.com/macmade) 9 | [![Sponsor](https://img.shields.io/badge/sponsor-macmade-pink.svg?logo=github-sponsors&style=social)](https://github.com/sponsors/macmade) 10 | 11 | ### About 12 | 13 | A macOS application for copying metadata to images. 14 | 15 | ![Screenshot](Assets/Screenshot.png "Screenshot") 16 | 17 | License 18 | ------- 19 | 20 | Project is released under the terms of the MIT License. 21 | 22 | Repository Infos 23 | ---------------- 24 | 25 | Owner: Jean-David Gadina - XS-Labs 26 | Web: www.xs-labs.com 27 | Blog: www.noxeos.com 28 | Twitter: @macmade 29 | GitHub: github.com/macmade 30 | LinkedIn: ch.linkedin.com/in/macmade/ 31 | StackOverflow: stackoverflow.com/users/182676/macmade 32 | --------------------------------------------------------------------------------