diff --git a/src/Catty.xcodeproj/project.pbxproj b/src/Catty.xcodeproj/project.pbxproj index aa51f32e84..707f6a8243 100644 --- a/src/Catty.xcodeproj/project.pbxproj +++ b/src/Catty.xcodeproj/project.pbxproj @@ -1191,6 +1191,11 @@ 972622DD25F51A8F00ABCC7A /* ChangeBrightnessByNBrick+CBXMLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 972622DC25F51A8F00ABCC7A /* ChangeBrightnessByNBrick+CBXMLHandler.swift */; }; 972622E225F51B4500ABCC7A /* SetBrightnessBrick+CBXMLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 972622E125F51B4500ABCC7A /* SetBrightnessBrick+CBXMLHandler.swift */; }; 9728AE9E25DEEE5A00708EB6 /* ProjectDetailStoreViewControllerReportExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9728AE9D25DEEE5A00708EB6 /* ProjectDetailStoreViewControllerReportExtension.swift */; }; + 973F1D3226B1923F0043108A /* CloneBrick+CBXMLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 973F1D3126B1923F0043108A /* CloneBrick+CBXMLHandler.swift */; }; + 9740489026B04FAE0047DEBB /* CloneBrick.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9740488F26B04FAE0047DEBB /* CloneBrick.swift */; }; + 9740489426B052950047DEBB /* CloneBrickCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9740489326B052950047DEBB /* CloneBrickCell.swift */; }; + 9740489626B0547D0047DEBB /* CloneBrick+Instruction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9740489526B0547D0047DEBB /* CloneBrick+Instruction.swift */; }; + 9740489E26B097500047DEBB /* CloneBrickTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9740489D26B097500047DEBB /* CloneBrickTests.swift */; }; 97417A9B265284400079A2A2 /* SoundsTableViewController+SelectFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97417A9A265284400079A2A2 /* SoundsTableViewController+SelectFile.swift */; }; 9767BAFA26668ECD009794E8 /* JoinThreeStringsFunctionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9767BAF926668ECD009794E8 /* JoinThreeStringsFunctionTest.swift */; }; 97770B8825E5A88C00F51EFA /* SetBrightnessBrick.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97770B8725E5A88C00F51EFA /* SetBrightnessBrick.swift */; }; @@ -3336,6 +3341,11 @@ 972622DC25F51A8F00ABCC7A /* ChangeBrightnessByNBrick+CBXMLHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChangeBrightnessByNBrick+CBXMLHandler.swift"; sourceTree = ""; }; 972622E125F51B4500ABCC7A /* SetBrightnessBrick+CBXMLHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SetBrightnessBrick+CBXMLHandler.swift"; sourceTree = ""; }; 9728AE9D25DEEE5A00708EB6 /* ProjectDetailStoreViewControllerReportExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectDetailStoreViewControllerReportExtension.swift; sourceTree = ""; }; + 973F1D3126B1923F0043108A /* CloneBrick+CBXMLHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "CloneBrick+CBXMLHandler.swift"; path = "CattyTests/Bricks/CloneBrick+CBXMLHandler.swift"; sourceTree = SOURCE_ROOT; }; + 9740488F26B04FAE0047DEBB /* CloneBrick.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloneBrick.swift; sourceTree = ""; }; + 9740489326B052950047DEBB /* CloneBrickCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloneBrickCell.swift; sourceTree = ""; }; + 9740489526B0547D0047DEBB /* CloneBrick+Instruction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CloneBrick+Instruction.swift"; sourceTree = ""; }; + 9740489D26B097500047DEBB /* CloneBrickTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloneBrickTests.swift; sourceTree = ""; }; 97417A9A265284400079A2A2 /* SoundsTableViewController+SelectFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SoundsTableViewController+SelectFile.swift"; sourceTree = ""; }; 9767BAF926668ECD009794E8 /* JoinThreeStringsFunctionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinThreeStringsFunctionTest.swift; sourceTree = ""; }; 97770B8725E5A88C00F51EFA /* SetBrightnessBrick.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetBrightnessBrick.swift; sourceTree = ""; }; @@ -3994,6 +4004,7 @@ C286B2695528549DC9B2916D /* af */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.strings; name = af; path = af.lproj/Localizable.strings; sourceTree = ""; }; C42D117BCEADFF7368CA3159 /* bn */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.strings; name = bn; path = bn.lproj/Localizable.strings; sourceTree = ""; }; C85A5C9F267A218F009BA454 /* Functions_0993.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = Functions_0993.xml; sourceTree = ""; }; + C88FFF5D26B830E100D381D8 /* BrickObjectWithOutBackgroundProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BrickObjectWithOutBackgroundProtocol.h; sourceTree = ""; }; C8A0338226064F3C00702911 /* SetTempoToBrick+CBXMLHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "SetTempoToBrick+CBXMLHandler.swift"; path = "Catty/Views/Custom/CollectionViewCells/BrickCells/Sound/SetTempoToBrick+CBXMLHandler.swift"; sourceTree = SOURCE_ROOT; }; C8CD7DD325E63D0A0018C655 /* BrickCategoryOverviewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrickCategoryOverviewControllerTests.swift; sourceTree = ""; }; C8D010C3264BD2B700896DEB /* JoinThreeStringsFunction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinThreeStringsFunction.swift; sourceTree = ""; }; @@ -5610,6 +5621,7 @@ 9990BB6C1D89D89A0088357A /* BrickStaticChoiceProtocol.h */, 4C1EB20F1AC19B3D0001F431 /* BrickTextProtocol.h */, 4CC0D51F1B01FB73006193C4 /* BrickVariableProtocol.h */, + C88FFF5D26B830E100D381D8 /* BrickObjectWithOutBackgroundProtocol.h */, ); path = BrickData; sourceTree = ""; @@ -5965,6 +5977,7 @@ AA74EEDA1BC057B900D1E954 /* WaitBrick+CBXMLHandler.m */, BA987D042194DDCF002DAA05 /* WaitUntilBrick+CBXMLHandler.h */, BA987D032194DDCF002DAA05 /* WaitUntilBrick+CBXMLHandler.m */, + 973F1D3126B1923F0043108A /* CloneBrick+CBXMLHandler.swift */, ); name = Control; path = ControlBricks; @@ -6804,6 +6817,7 @@ 971C6F20260526ED00AD4857 /* SetTempoToBrickTests.swift */, 83F8230725EBA9610093DD9A /* SetBackgroundByIndexBrickTests.swift */, 2E8780A72542BCE200816B52 /* WebRequestBrickTests.swift */, + 9740489D26B097500047DEBB /* CloneBrickTests.swift */, ); path = Bricks; sourceTree = ""; @@ -9283,6 +9297,7 @@ AA74EE1F1BC053FD00D1E954 /* BroadcastWaitBrick+Instruction.swift */, AA74EE201BC053FD00D1E954 /* WaitBrick+Instruction.swift */, BA987D0E2194EA1F002DAA05 /* WaitUntilBrick+Instruction.swift */, + 9740489526B0547D0047DEBB /* CloneBrick+Instruction.swift */, ); name = Control; sourceTree = ""; @@ -9416,6 +9431,7 @@ AA74EF951BC05B5F00D1E954 /* WaitBrick.m */, BA987D072194E158002DAA05 /* WaitUntilBrick.h */, BA987D062194E157002DAA05 /* WaitUntilBrick.m */, + 9740488F26B04FAE0047DEBB /* CloneBrick.swift */, ); path = Control; sourceTree = ""; @@ -9656,6 +9672,7 @@ 9EDCD22422886FD90040EFE3 /* WaitBrickCell.swift */, BA987D0A2194E25A002DAA05 /* WaitUntilBrickCell.h */, BA987D092194E25A002DAA05 /* WaitUntilBrickCell.m */, + 9740489326B052950047DEBB /* CloneBrickCell.swift */, ); path = Control; sourceTree = ""; @@ -11448,6 +11465,7 @@ 9E24D7032326E8E600608203 /* TurnLeftBrickTests.swift in Sources */, E57E6D872540414700E775DF /* SetVolumeToBrickTests.swift in Sources */, 4C968C401F00288500355C0D /* BrickTests.swift in Sources */, + 9740489E26B097500047DEBB /* CloneBrickTests.swift in Sources */, E579F10B253D98B0009107C8 /* PhiroPlayToneBrickTests.swift in Sources */, E579F114253DCA96009107C8 /* ThinkBubbleBrickTests.swift in Sources */, 4C0F9FD9204BD3D500E71B2D /* RepeatUntilBrickTests.swift in Sources */, @@ -11783,6 +11801,7 @@ AA74F0BD1BC05FCE00D1E954 /* PlaceAtBrickCell.m in Sources */, 18390A6924D0576100A07DFD /* StampBrick.swift in Sources */, CA3E72E81B0C00A500D6B184 /* CBStack.swift in Sources */, + 9740489426B052950047DEBB /* CloneBrickCell.swift in Sources */, AA74EFFE1BC05B5F00D1E954 /* ComeToFrontBrick.m in Sources */, 92FF31051A24DCAA00093DA7 /* WhenScript.m in Sources */, E564EBE1255D60F5001F0CD0 /* PlayButton.swift in Sources */, @@ -11801,6 +11820,7 @@ 2D6E3F3B210A0AB700FB8139 /* ChartProjectsStoreViewController.swift in Sources */, 92FF2EA41A24C7D800093DA7 /* Util.m in Sources */, AA74EFEC1BC05B5F00D1E954 /* ChangeVariableBrick.m in Sources */, + 973F1D3226B1923F0043108A /* CloneBrick+CBXMLHandler.swift in Sources */, 92FF31031A24DCAA00093DA7 /* Script.m in Sources */, 4C822693213FBC4400F3D750 /* MultiFingerTouchedFunction.swift in Sources */, 4420ACB1250929AE00951328 /* AskBrick+CBXMLHandler.swift in Sources */, @@ -11808,10 +11828,12 @@ 4C0F9F9E204BD2B100E71B2D /* SayBubbleBrickCell.m in Sources */, 4C2EE41E1B555B55006DE9B8 /* CBXMLOpenedNestingBricksStack.m in Sources */, 5EFBD5F92145533B003B3CDC /* ProjectDescriptionViewController.swift in Sources */, + 9740489626B0547D0047DEBB /* CloneBrick+Instruction.swift in Sources */, 92FF31571A24DEB300093DA7 /* ObjectTableViewController.m in Sources */, 4CE3D68F2107B68600005629 /* FaceDetectionManagerProtocol.swift in Sources */, 929CC0EF1BC39B8C0027DEC0 /* PhiroMotorStopBrickCell.m in Sources */, 4C0F9F64204BD18600E71B2D /* SayForBubbleBrick+CBXMLHandler.m in Sources */, + 9740489026B04FAE0047DEBB /* CloneBrick.swift in Sources */, 92FF32BB1A24E2F400093DA7 /* DarkBlueGradientImageCell.m in Sources */, 92FF314E1A24DEB300093DA7 /* LookImageViewController.m in Sources */, AA74F0A41BC05FCE00D1E954 /* LoopEndBrickCell.m in Sources */, diff --git a/src/Catty/CloneBrick+Instruction.swift b/src/Catty/CloneBrick+Instruction.swift new file mode 100644 index 0000000000..260db8a7f3 --- /dev/null +++ b/src/Catty/CloneBrick+Instruction.swift @@ -0,0 +1,44 @@ +/** + * Copyright (C) 2010-2021 The Catrobat Team + * (http://developer.catrobat.org/credits) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * An additional term exception under section 7 of the GNU Affero + * General Public License, version 3, is available at + * (http://developer.catrobat.org/license_additional_term) + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +@objc extension CloneBrick: CBInstructionProtocol { + + @nonobjc func instruction() -> CBInstruction { + .action { context in SKAction.run(self.actionBlock(context.formulaInterpreter)) } + } + + func actionBlock(_ formulaInterpreter: FormulaInterpreterProtocol) -> () -> Void { + guard let objectToClone = self.objectToClone + else { fatalError("This should never happen!") } + + return { + let object = SpriteObject() + object.name = objectToClone.name + object.userData = objectToClone.userData + object.scriptList = objectToClone.scriptList + object.soundList = objectToClone.soundList + object.spriteNode = objectToClone.spriteNode + object.lookList = objectToClone.lookList + self.objectToClone?.scene.add(object: object) + } + } +} diff --git a/src/Catty/DataModel/Bricks/Control/CloneBrick.swift b/src/Catty/DataModel/Bricks/Control/CloneBrick.swift new file mode 100644 index 0000000000..aa506cadc3 --- /dev/null +++ b/src/Catty/DataModel/Bricks/Control/CloneBrick.swift @@ -0,0 +1,78 @@ +/** + * Copyright (C) 2010-2021 The Catrobat Team + * (http://developer.catrobat.org/credits) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * An additional term exception under section 7 of the GNU Affero + * General Public License, version 3, is available at + * (http://developer.catrobat.org/license_additional_term) + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +@objc(CloneBrick) +@objcMembers class CloneBrick: Brick, BrickObjectWithOutBackgroundProtocol { + + var objectToClone: SpriteObject? + + override required init() { + super.init() + } + + func category() -> kBrickCategoryType { + kBrickCategoryType.eventBrick + } + + override class func description() -> String { + "CloneBrick" + } + + override func getRequiredResources() -> Int { + ResourceType.noResources.rawValue + } + + override func brickCell() -> BrickCellProtocol.Type! { + CloneBrickCell.self as BrickCellProtocol.Type + } + + func setObject(_ object: SpriteObject?, forLineNumber lineNumber: Int, andParameterNumber paramNumber: Int) { + if let object = object { + objectToClone = object + } + } + + func object(forLineNumber lineNumber: Int, andParameterNumber paramNumber: Int) -> SpriteObject? { + self.objectToClone + } + + override func setDefaultValuesFor(_ spriteObject: SpriteObject!) { + if spriteObject != nil { + objectToClone = spriteObject + } else { + objectToClone = self.script.object + } + } + + override func isDisabledForBackground() -> Bool { + true + } + + @objc(mutableCopyWithContext:) + override func mutableCopy(with context: CBMutableCopyContext) -> Any { + let brick = CloneBrick() + if self.objectToClone != nil { + brick.objectToClone = self.objectToClone + } + return brick + } +} diff --git a/src/Catty/DataModel/Bricks/Motion/PointToBrick.h b/src/Catty/DataModel/Bricks/Motion/PointToBrick.h index 841eea04b0..e0ab6e9caf 100644 --- a/src/Catty/DataModel/Bricks/Motion/PointToBrick.h +++ b/src/Catty/DataModel/Bricks/Motion/PointToBrick.h @@ -23,6 +23,7 @@ #import "Brick.h" #import "BrickObjectProtocol.h" +#import "BrickObjectWithOutBackgroundProtocol.h" @class SpriteObject; diff --git a/src/Catty/DataModel/Scene/Scene.swift b/src/Catty/DataModel/Scene/Scene.swift index 8074470e2a..d022287a66 100644 --- a/src/Catty/DataModel/Scene/Scene.swift +++ b/src/Catty/DataModel/Scene/Scene.swift @@ -78,6 +78,11 @@ self._objects } + func objectsWithoutBackground() -> [SpriteObject] { + let backgroundObjects = self.numberOfBackgroundObjects() + return [SpriteObject](self._objects[backgroundObjects...]) + } + @objc(objectAtIndex:) func object(at index: Int) -> SpriteObject? { if index >= 0 && index < self._objects.count { diff --git a/src/Catty/Defines/LanguageTranslationDefines.h b/src/Catty/Defines/LanguageTranslationDefines.h index 85afcc4de6..9edc72686a 100644 --- a/src/Catty/Defines/LanguageTranslationDefines.h +++ b/src/Catty/Defines/LanguageTranslationDefines.h @@ -371,6 +371,7 @@ #define kLocalizedEndOfLoop NSLocalizedString(@"End of Loop", nil) #define kLocalizedWhen NSLocalizedString(@"When", nil) #define kLocalizedBecomesTrue NSLocalizedString(@"becomes true", nil) +#define kLocalizedCreateCloneOf NSLocalizedString(@"Create clone of", nil) // motion bricks #define kLocalizedPlaceAt NSLocalizedString(@"Place at ", nil) diff --git a/src/Catty/Defines/LanguageTranslationDefinesSwift.swift b/src/Catty/Defines/LanguageTranslationDefinesSwift.swift index 4a20e205e6..4bad3671c8 100644 --- a/src/Catty/Defines/LanguageTranslationDefinesSwift.swift +++ b/src/Catty/Defines/LanguageTranslationDefinesSwift.swift @@ -371,6 +371,7 @@ let kLocalizedTimes = NSLocalizedString("times", comment: "") let kLocalizedEndOfLoop = NSLocalizedString("End of Loop", comment: "") let kLocalizedWhen = NSLocalizedString("When", comment: "") let kLocalizedBecomesTrue = NSLocalizedString("becomes true", comment: "") +let kLocalizedCreateCloneOf = NSLocalizedString("Create clone of", comment: "") // motion bricks let kLocalizedPlaceAt = NSLocalizedString("Place at ", comment: "") diff --git a/src/Catty/Extension&Delegate&Protocol/Protocols/Bricks/BrickData/BrickObjectWithOutBackgroundProtocol.h b/src/Catty/Extension&Delegate&Protocol/Protocols/Bricks/BrickData/BrickObjectWithOutBackgroundProtocol.h new file mode 100644 index 0000000000..81104c3ba9 --- /dev/null +++ b/src/Catty/Extension&Delegate&Protocol/Protocols/Bricks/BrickData/BrickObjectWithOutBackgroundProtocol.h @@ -0,0 +1,33 @@ +/** + * Copyright (C) 2010-2021 The Catrobat Team + * (http://developer.catrobat.org/credits) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * An additional term exception under section 7 of the GNU Affero + * General Public License, version 3, is available at + * (http://developer.catrobat.org/license_additional_term) + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#import + +@class Brick; +@class SpriteObject; + +@protocol BrickObjectWithOutBackgroundProtocol + +- (SpriteObject*)objectForLineNumber:(NSInteger)lineNumber andParameterNumber:(NSInteger)paramNumber; +- (void)setObject:(SpriteObject*)object forLineNumber:(NSInteger)lineNumber andParameterNumber:(NSInteger)paramNumber; + +@end diff --git a/src/Catty/Resources/Localization/en.lproj/Localizable.strings b/src/Catty/Resources/Localization/en.lproj/Localizable.strings index 7cf81ef7d7..84157e9df3 100644 --- a/src/Catty/Resources/Localization/en.lproj/Localizable.strings +++ b/src/Catty/Resources/Localization/en.lproj/Localizable.strings @@ -379,6 +379,9 @@ /* No comment provided by engineer. */ "Create account" = "Create account"; +/* No comment provided by engineer. */ +"Create clone of" = "Create clone of"; + /* No comment provided by engineer. */ "Create patterns for stiching machines" = "Create patterns for stiching machines"; diff --git a/src/Catty/Setup/CatrobatSetup+Bricks.swift b/src/Catty/Setup/CatrobatSetup+Bricks.swift index 101e03a216..ef2b2ba2c3 100644 --- a/src/Catty/Setup/CatrobatSetup+Bricks.swift +++ b/src/Catty/Setup/CatrobatSetup+Bricks.swift @@ -33,6 +33,7 @@ BroadcastBrick(), BroadcastWaitBrick(), WhenBackgroundChangesScript(), + CloneBrick(), // control bricks WaitBrick(), IfThenLogicBeginBrick(), diff --git a/src/Catty/ViewController/Continue&New/MaintainObject/MaintainScript/ScriptCollectionViewController.m b/src/Catty/ViewController/Continue&New/MaintainObject/MaintainScript/ScriptCollectionViewController.m index 6a94a11850..f4b856b07c 100644 --- a/src/Catty/ViewController/Continue&New/MaintainObject/MaintainScript/ScriptCollectionViewController.m +++ b/src/Catty/ViewController/Continue&New/MaintainObject/MaintainScript/ScriptCollectionViewController.m @@ -38,6 +38,7 @@ #import "BrickLookProtocol.h" #import "BrickSoundProtocol.h" #import "BrickObjectProtocol.h" +#import "BrickObjectWithOutBackgroundProtocol.h" #import "BrickMessageProtocol.h" #import "BrickStaticChoiceProtocol.h" #import "BrickVariableProtocol.h" @@ -1109,7 +1110,7 @@ - (void)updateBrickCellData:(id)brickCellData withValue:( [soundBrick setSound:[Util soundWithName:(NSString*)value forObject:self.object] forLineNumber:line andParameterNumber:parameter]; } } else - if ([brickCellData isKindOfClass:[BrickCellObjectData class]] && [brick conformsToProtocol:@protocol(BrickObjectProtocol)]) { + if (([brickCellData isKindOfClass:[BrickCellObjectData class]] && [brick conformsToProtocol:@protocol(BrickObjectProtocol)]) || ([brickCellData isKindOfClass:[BrickCellObjectData class]] && [brick conformsToProtocol:@protocol(BrickObjectWithOutBackgroundProtocol)])) { Brick *objectBrick = (Brick*)brick; if([(NSString*)value isEqualToString:kLocalizedNewElement]) { SceneTableViewController *ptvc = [self.storyboard instantiateViewControllerWithIdentifier:kSceneTableViewControllerIdentifier]; @@ -1123,7 +1124,10 @@ - (void)updateBrickCellData:(id)brickCellData withValue:( [self.navigationController pushViewController:ptvc animated:YES]; return; } else { - [objectBrick setObject:[Util objectWithName:(NSString*)value forScene:self.object.scene] forLineNumber:line andParameterNumber:parameter]; + if ([value isEqualToString:@"yourself"]) + [objectBrick setObject:objectBrick.script.object forLineNumber:line andParameterNumber:parameter]; + else + [objectBrick setObject:[Util objectWithName:(NSString*)value forScene:self.object.scene] forLineNumber:line andParameterNumber:parameter]; } } else if ([brickCellData isKindOfClass:[BrickCellFormulaData class]] && [brick conformsToProtocol:@protocol(BrickFormulaProtocol)]) { diff --git a/src/Catty/Views/Custom/CollectionViewCells/BrickCellData/BrickCellObjectData.m b/src/Catty/Views/Custom/CollectionViewCells/BrickCellData/BrickCellObjectData.m index 2183825591..6d9bf886f6 100644 --- a/src/Catty/Views/Custom/CollectionViewCells/BrickCellData/BrickCellObjectData.m +++ b/src/Catty/Views/Custom/CollectionViewCells/BrickCellData/BrickCellObjectData.m @@ -25,6 +25,7 @@ #import "Script.h" #import "Brick.h" #import "BrickObjectProtocol.h" +#import "BrickObjectWithOutBackgroundProtocol.h" #import "Pocket_Code-Swift.h" @implementation BrickCellObjectData @@ -54,6 +55,22 @@ - (instancetype)initWithFrame:(CGRect)frame andBrickCell:(BrickCell*)brickCell a currentOptionIndex = optionIndex; } } + if([self.brickCell.scriptOrBrick conformsToProtocol:@protocol(BrickObjectWithOutBackgroundProtocol)]) { + Brick *objectBrick = (Brick*)self.brickCell.scriptOrBrick; + SpriteObject *currentObject = [objectBrick objectForLineNumber:self.lineNumber andParameterNumber:self.parameterNumber]; + for(SpriteObject *object in objectBrick.script.object.scene.objectsWithoutBackground) { + if([objectBrick.script.object.name isEqualToString:object.name]) { + [options addObject:@"yourself"]; + if([currentObject.name isEqualToString:objectBrick.script.object.name]) + currentOptionIndex = optionIndex; + } + else + [options addObject:object.name]; + if([currentObject.name isEqualToString:object.name]) + currentOptionIndex = optionIndex; + optionIndex++; + } + } } [self setValues:options]; [self setCurrentValue:options[currentOptionIndex]]; diff --git a/src/Catty/Views/Custom/CollectionViewCells/BrickCells/Control/CloneBrickCell.swift b/src/Catty/Views/Custom/CollectionViewCells/BrickCells/Control/CloneBrickCell.swift new file mode 100644 index 0000000000..64399bd823 --- /dev/null +++ b/src/Catty/Views/Custom/CollectionViewCells/BrickCells/Control/CloneBrickCell.swift @@ -0,0 +1,53 @@ +/** + * Copyright (C) 2010-2021 The Catrobat Team + * (http://developer.catrobat.org/credits) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * An additional term exception under section 7 of the GNU Affero + * General Public License, version 3, is available at + * (http://developer.catrobat.org/license_additional_term) + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +import Foundation + +@objc(CloneBrickCell) class CloneBrickCell: BrickCell, BrickCellProtocol { + + public var textLabel: UILabel? + public var objectComboBoxView: iOSCombobox? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + + static func cellHeight() -> CGFloat { + UIDefines.brickHeight2h + } + + override func hookUpSubViews(_ inlineViewSubViews: [Any]!) { + self.textLabel = inlineViewSubViews[0] as? UILabel + self.objectComboBoxView = inlineViewSubViews[1] as? iOSCombobox + } + + func brickTitle(forBackground isBackground: Bool, andInsertionScreen isInsertion: Bool) -> String! { + kLocalizedCreateCloneOf.appending("\n%@") + } + + override func parameters() -> [String] { + ["{OBJECT}"] + } +} diff --git a/src/Catty/XML/Context/Abstract/CBXMLAbstractContext.h b/src/Catty/XML/Context/Abstract/CBXMLAbstractContext.h index 371e6502eb..dd5542ab02 100644 --- a/src/Catty/XML/Context/Abstract/CBXMLAbstractContext.h +++ b/src/Catty/XML/Context/Abstract/CBXMLAbstractContext.h @@ -38,6 +38,7 @@ // ressources data used while traversing the tree //------------------------------------------------------------------------------------------------------------ @property (nonatomic, strong) NSMutableArray *pointedSpriteObjectList; // contains all already parsed pointed (!!) SpriteObjects +@property (nonatomic, strong) NSMutableArray *clonedSpriteObjectList; // contains all already parsed cloned (!!) SpriteObjects @property (nonatomic, strong) NSMutableArray *spriteObjectList; // contains all known SpriteObjects @property (nonatomic, strong) SpriteObject *spriteObject; // contains all looks, sounds, bricks, ... of currently parsed/serialized SpriteObject // TODO: refactor this later: remove brickList here and dynamically find brick in scriptList. maybe scripts should be referenced in bricks as well!! diff --git a/src/Catty/XML/Context/Abstract/CBXMLAbstractContext.m b/src/Catty/XML/Context/Abstract/CBXMLAbstractContext.m index f43cdbdf0b..92a21e629e 100644 --- a/src/Catty/XML/Context/Abstract/CBXMLAbstractContext.m +++ b/src/Catty/XML/Context/Abstract/CBXMLAbstractContext.m @@ -42,6 +42,14 @@ - (NSMutableArray*)pointedSpriteObjectList return _pointedSpriteObjectList; } +- (NSMutableArray*)clonedSpriteObjectList +{ + if (! _clonedSpriteObjectList) { + _clonedSpriteObjectList = [NSMutableArray array]; + } + return _clonedSpriteObjectList; +} + - (NSMutableArray*)spriteObjectList { if (! _spriteObjectList) { diff --git a/src/Catty/XML/XMLHandler/SpriteObject/SpriteObject+CBXMLHandler.h b/src/Catty/XML/XMLHandler/SpriteObject/SpriteObject+CBXMLHandler.h index c811ef90b8..5e6797098c 100644 --- a/src/Catty/XML/XMLHandler/SpriteObject/SpriteObject+CBXMLHandler.h +++ b/src/Catty/XML/XMLHandler/SpriteObject/SpriteObject+CBXMLHandler.h @@ -30,6 +30,8 @@ + (NSMutableArray*)parseAndCreateSounds:(GDataXMLElement*)objectElement withContext:(CBXMLParserContext*)context; #endif +- (GDataXMLElement*)xmlElementWithContext:(CBXMLSerializerContext*)context asPointedObject:(BOOL)asPointedObject asGoToObject:(BOOL)asGoToObject asCloneOfObject:(BOOL )asCloneOfObject; + - (GDataXMLElement*)xmlElementWithContext:(CBXMLSerializerContext*)context asPointedObject:(BOOL)asPointedObject asGoToObject:(BOOL)asGoToObject; @end diff --git a/src/Catty/XML/XMLHandler/SpriteObject/SpriteObject+CBXMLHandler.m b/src/Catty/XML/XMLHandler/SpriteObject/SpriteObject+CBXMLHandler.m index a34000d6e3..d6c13e8176 100644 --- a/src/Catty/XML/XMLHandler/SpriteObject/SpriteObject+CBXMLHandler.m +++ b/src/Catty/XML/XMLHandler/SpriteObject/SpriteObject+CBXMLHandler.m @@ -39,11 +39,11 @@ @implementation SpriteObject (CBXMLHandler) + (instancetype)parseFromElement:(GDataXMLElement*)xmlElement withContext:(CBXMLParserContext*)context { [XMLError exceptionIfNil:xmlElement message:@"The rootElement nil"]; - if (! [xmlElement.name isEqualToString:@"object"] && ![xmlElement.name isEqualToString:@"pointedObject"] && ! [xmlElement.name isEqualToString:@"destinationSprite"]) { + if (! [xmlElement.name isEqualToString:@"object"] && ![xmlElement.name isEqualToString:@"pointedObject"] && ! [xmlElement.name isEqualToString:@"destinationSprite"] && ![xmlElement.name isEqualToString:@"objectToClone"]) { [XMLError exceptionIfString:xmlElement.name isNotEqualToString:@"object" message:@"The name of the rootElement is '%@' but should be '%@'", - xmlElement.name, @"object, pointedObject or destinationSprite"]; + xmlElement.name, @"object, pointedObject, destinationSprite or objectToClone"]; } NSArray *attributes = [xmlElement attributes]; @@ -78,6 +78,8 @@ + (instancetype)parseFromElement:(GDataXMLElement*)xmlElement withContext:(CBXML [XMLError exceptionIfNode:referencedObjectElement isNilOrNodeNameNotEquals:@"object"]; } else if([referencedObjectElement.name isEqualToString:@"destinationSprite"]) { [XMLError exceptionIfNode:referencedObjectElement isNilOrNodeNameNotEquals:@"destinationSprite"]; + } if ([referencedObjectElement.name isEqualToString:@"objectToClone"]) { + [XMLError exceptionIfNode:referencedObjectElement isNilOrNodeNameNotEquals:@"objectToClone"]; } else { [XMLError exceptionIfNode:referencedObjectElement isNilOrNodeNameNotEquals:@"pointedObject"]; } @@ -174,10 +176,15 @@ + (NSMutableArray*)parseAndCreateScripts:(GDataXMLElement*)objectElement #pragma mark - Serialization - (GDataXMLElement*)xmlElementWithContext:(CBXMLSerializerContext*)context { - return [self xmlElementWithContext:context asPointedObject:NO asGoToObject:NO]; + return [self xmlElementWithContext:context asPointedObject:NO asGoToObject:NO asCloneOfObject:NO]; } - (GDataXMLElement*)xmlElementWithContext:(CBXMLSerializerContext*)context asPointedObject:(BOOL)asPointedObject asGoToObject:(BOOL)asGoToObject +{ + return [self xmlElementWithContext:context asPointedObject:asPointedObject asGoToObject:asGoToObject asCloneOfObject:NO]; +} + +- (GDataXMLElement*)xmlElementWithContext:(CBXMLSerializerContext*)context asPointedObject:(BOOL)asPointedObject asGoToObject:(BOOL)asGoToObject asCloneOfObject:(BOOL )asCloneOfObject { SpriteObject *previousObject = context.spriteObject; [context.soundNamePositions removeAllObjects]; @@ -187,11 +194,12 @@ - (GDataXMLElement*)xmlElementWithContext:(CBXMLSerializerContext*)context asPoi // generate xml element for sprite object GDataXMLElement *xmlElement = nil; - if (! asPointedObject && !asGoToObject) { + if (! asPointedObject && !asGoToObject && !asCloneOfObject) { NSUInteger indexOfSpriteObject = [CBXMLSerializerHelper indexOfElement:self inArray:context.spriteObjectList]; xmlElement = [GDataXMLElement elementWithName:@"object" xPathIndex:(indexOfSpriteObject+1) context:context]; } else { NSString* elementName = asGoToObject ? @"destinationSprite" : @"pointedObject"; + elementName = asCloneOfObject ? @"objectToClone" : elementName; xmlElement = [GDataXMLElement elementWithName:elementName context:context]; } diff --git a/src/CattyTests/Bricks/CloneBrick+CBXMLHandler.swift b/src/CattyTests/Bricks/CloneBrick+CBXMLHandler.swift new file mode 100644 index 0000000000..a533815321 --- /dev/null +++ b/src/CattyTests/Bricks/CloneBrick+CBXMLHandler.swift @@ -0,0 +1,104 @@ +/** + * Copyright (C) 2010-2021 The Catrobat Team + * (http://developer.catrobat.org/credits) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * An additional term exception under section 7 of the GNU Affero + * General Public License, version 3, is available at + * (http://developer.catrobat.org/license_additional_term) + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +extension CloneBrick: CBXMLNodeProtocol { + static func parse(from xmlElement: GDataXMLElement, with context: CBXMLParserContext) -> Self { + let childCount = xmlElement.childrenWithoutCommentsAndCommentedOutTag().count + if childCount > 1 { + fatalError("Too many child nodes found... (0 or 1 expected, actual \(childCount)") + } + let cloneBrick = self.init() + + if childCount == 1 { + CBXMLParserHelper.validate(xmlElement, forNumberOfChildNodes: 1) + + let objectToCloneElement = xmlElement.child(withElementName: "objectToClone") + guard let _ = objectToCloneElement else { + fatalError("No clonedObject element found...") + } + let newContext = context + var spriteObject = newContext.parse(from: objectToCloneElement, withClass: SpriteObject.self) as! SpriteObject + context.spriteObjectList = newContext.spriteObjectList + context.clonedSpriteObjectList = newContext.clonedSpriteObjectList + let alreadyExistantSpriteObject = CBXMLParserHelper.findSpriteObject(in: context.clonedSpriteObjectList as? [Any], withName: spriteObject.name) + + if alreadyExistantSpriteObject != nil { + spriteObject = alreadyExistantSpriteObject! + } else { + context.clonedSpriteObjectList.add(spriteObject) + } + + cloneBrick.objectToClone = spriteObject + + } else { + cloneBrick.objectToClone = context.spriteObject + } + return cloneBrick + } + + func xmlElement(with context: CBXMLSerializerContext) -> GDataXMLElement? { + let brick = super.xmlElement(for: "CloneBrick", with: context) + + guard let _ = self.objectToClone else { + fatalError("No sprite object given in CloneBrick") + } + + guard let _ = self.script.object else { + fatalError("Missing reference to brick's sprite object") + } + + if self.objectToClone != self.script.object { + let indexOfClonedObject = CBXMLSerializerHelper.index(ofElement: self.objectToClone, in: context.spriteObjectList as? [Any]) + let indexOfSpriteObject = CBXMLSerializerHelper.index(ofElement: self.script.object, in: context.spriteObjectList as? [Any]) + if indexOfClonedObject == Foundation.NSNotFound { + fatalError("Cloned object does not exist in spriteObject list") + } + if indexOfSpriteObject == Foundation.NSNotFound { + fatalError("Sprite object does not exist in spriteObject list") + } + + let positionStackOfSpriteObject = context.spriteObjectNamePositions[self.objectToClone!.name as Any] + if positionStackOfSpriteObject != nil { + let clonedObjectXmlElement = GDataXMLElement.init(name: "objectToClone", context: context) + let currentPositionStack = context.currentPositionStack + + let refPath = CBXMLSerializerHelper.relativeXPath(fromSourcePositionStack: currentPositionStack, toDestinationPositionStack: positionStackOfSpriteObject as? CBXMLPositionStack) + + clonedObjectXmlElement?.addAttribute(GDataXMLElement.attribute(withName: "reference", escapedStringValue: refPath) as? GDataXMLNode) + brick?.addChild(clonedObjectXmlElement, context: context) + + } else { + let newContext = context + newContext.currentPositionStack = context.currentPositionStack + let clonedObjectXmlElement = self.objectToClone?.xmlElement(with: newContext, asPointedObject: false, asGoToObject: false, asCloneOfObject: true) + context.spriteObjectNamePositions = newContext.spriteObjectNamePositions + context.spriteObjectNameUserVariableListPositions = newContext.spriteObjectNameUserVariableListPositions + context.projectUserVariableNamePositions = newContext.projectUserVariableNamePositions + context.projectUserListNamePositions = newContext.projectUserListNamePositions + context.clonedSpriteObjectList = newContext.clonedSpriteObjectList + brick?.addChild(clonedObjectXmlElement, context: context) + context.clonedSpriteObjectList.add(self.objectToClone!) + } + } + return brick + } +} diff --git a/src/CattyTests/Bricks/CloneBrickTests.swift b/src/CattyTests/Bricks/CloneBrickTests.swift new file mode 100644 index 0000000000..3bc7e83c2b --- /dev/null +++ b/src/CattyTests/Bricks/CloneBrickTests.swift @@ -0,0 +1,110 @@ +/** + * Copyright (C) 2010-2021 The Catrobat Team + * (http://developer.catrobat.org/credits) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * An additional term exception under section 7 of the GNU Affero + * General Public License, version 3, is available at + * (http://developer.catrobat.org/license_additional_term) + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +import XCTest + +@testable import Pocket_Code + +final class CloneBrickTests: AbstractBrickTest { + + var firstSpriteNode: CBSpriteNode! + var script: Script! + var brick: CloneBrick! + var scene: Scene! + + override func setUp() { + super.setUp() + self.stage = StageBuilder(project: ProjectMock()).build() + + let firstObject = SpriteObject() + scene = Scene(name: "testScene") + firstObject.scene = scene + firstSpriteNode = CBSpriteNode(spriteObject: firstObject) + firstObject.spriteNode = firstSpriteNode + + self.stage.addChild(firstSpriteNode) + scene.add(object: firstObject) + + script = WhenScript() + script.object = firstObject + + brick = CloneBrick() + brick.script = script + brick.objectToClone = firstObject + } + + func testCreateClone() { + XCTAssertEqual(brick.objectToClone!.scene.objects().count, 1) + let name = "test" + brick.objectToClone?.name = name + let userData = UserDataContainer() + brick.objectToClone?.userData = userData + let scriptList: NSMutableArray = [Script()] + brick.objectToClone?.scriptList = scriptList + let soundList: NSMutableArray = [Sound(name: "test", fileName: "testName")] + brick.objectToClone?.soundList = soundList + let lookList: NSMutableArray = [Look(name: "test", filePath: "testPath")] + brick.objectToClone?.lookList = lookList + + let action = brick.actionBlock(self.formulaInterpreter) + action() + + XCTAssertEqual(brick.objectToClone!.scene.objects().count, 2) + + let firstObject = brick.objectToClone!.scene.object(at: 0) + let secondObject = brick.objectToClone!.scene.object(at: 1) + + XCTAssertEqual(firstObject?.name, name) + XCTAssertEqual(secondObject?.name, name) + XCTAssertEqual(firstObject?.userData, userData) + XCTAssertEqual(secondObject?.userData, userData) + XCTAssertEqual(firstObject?.spriteNode, firstSpriteNode) + XCTAssertEqual(firstObject?.spriteNode, firstSpriteNode) + XCTAssertEqual(secondObject?.scriptList, scriptList) + XCTAssertEqual(secondObject?.scriptList, scriptList) + XCTAssertEqual(secondObject?.lookList, lookList) + XCTAssertEqual(secondObject?.lookList, lookList) + XCTAssertEqual(secondObject?.soundList, soundList) + XCTAssertEqual(secondObject?.soundList, soundList) + } + + func testMutableCopy() { + let brick = CloneBrick() + let script = Script() + let object = SpriteObject() + let scene = Scene(name: "testScene") + object.scene = scene + + script.object = object + brick.script = script + let clonedObject = SpriteObject() + clonedObject.name = "clonedObject" + brick.objectToClone = clonedObject + + let copiedBrick: CloneBrick = brick.mutableCopy(with: CBMutableCopyContext()) as! CloneBrick + + XCTAssertTrue(brick.isEqual(to: copiedBrick)) + XCTAssertFalse(brick === copiedBrick) + XCTAssertTrue(brick.objectToClone!.isEqual(to: copiedBrick.objectToClone)) + XCTAssertTrue(brick.objectToClone === copiedBrick.objectToClone) + } +} diff --git a/src/CattyUITests/Defines/LanguageTranslationDefinesUI.swift b/src/CattyUITests/Defines/LanguageTranslationDefinesUI.swift index 12c281f25b..710d157a4b 100644 --- a/src/CattyUITests/Defines/LanguageTranslationDefinesUI.swift +++ b/src/CattyUITests/Defines/LanguageTranslationDefinesUI.swift @@ -371,6 +371,7 @@ let kLocalizedTimes = NSLocalizedString("times", bundle: Bundle(for: LanguageTra let kLocalizedEndOfLoop = NSLocalizedString("End of Loop", bundle: Bundle(for: LanguageTranslation.self), comment: "") let kLocalizedWhen = NSLocalizedString("When", bundle: Bundle(for: LanguageTranslation.self), comment: "") let kLocalizedBecomesTrue = NSLocalizedString("becomes true", bundle: Bundle(for: LanguageTranslation.self), comment: "") +let kLocalizedCreateCloneOf = NSLocalizedString("Create clone of", bundle: Bundle(for: LanguageTranslation.self), comment: "") // motion bricks let kLocalizedPlaceAt = NSLocalizedString("Place at ", bundle: Bundle(for: LanguageTranslation.self), comment: "")