From 765a15e00c4f2081d0c4a0a9399f4b79803e4569 Mon Sep 17 00:00:00 2001 From: Mathieu Fourment Date: Mon, 18 May 2015 17:11:09 +1000 Subject: [PATCH] first commit --- .gitignore | 6 + LICENSE | 674 +++++ README.md | 18 + Seqotron.xcodeproj/project.pbxproj | 984 +++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/Seqotron.xccheckout | 41 + Seqotron/Base.lproj/MFDocument.xib | 337 +++ Seqotron/Base.lproj/MainMenu.xib | 602 +++++ .../Amino Acid/color-amino-acid.plist | 149 ++ .../Amino Acid/monochrome-amino-acid.plist | 149 ++ .../Nucleotide/black-nucleotide.plist | 53 + .../Nucleotide/clustal-nucleotide.plist | 98 + .../Nucleotide/color-nucleotide.plist | 53 + Seqotron/Coloring/Nucleotide/geneious.plist | 112 + Seqotron/DBPrefsWindowController.h | 44 + Seqotron/DBPrefsWindowController.m | 301 +++ .../Alternative-Flatworm-Mitochondrial.plist | 596 +++++ .../Alternative-Yeast-Nuclear.plist | 596 +++++ .../Ascidian-Mitochondrial.plist | 596 +++++ ...acterial,-Archaeal-and-Plant-Plastid.plist | 596 +++++ ...ate-Division-SR1-and-Gracilibacteria.plist | 595 +++++ .../Chlorophycean-Mitochondrial.plist | 596 +++++ ...,-Dasycladacean-and-Hexamita-Nuclear.plist | 596 +++++ ...chinoderm-and-Flatworm-Mitochondrial.plist | 596 +++++ .../GeneticCodeTables/Euplotid-Nuclear.plist | 596 +++++ .../Invertebrate-Mitochondrial.plist | 596 +++++ ...-Code-and-the-Mycoplasma-Spiroplasma.plist | 595 +++++ .../Pterobranchia-Mitochondrial.plist | 595 +++++ .../Scenedesmus-obliquus-Mitochondrial.plist | 596 +++++ Seqotron/GeneticCodeTables/Standard.plist | 596 +++++ .../Thraustochytrium-Mitochondrial.plist | 596 +++++ .../Trematode-Mitochondrial.plist | 596 +++++ .../Yeast-Mitochondrial.plist | 596 +++++ .../vertebrate-mitochondrial.plist | 596 +++++ .../AppIcon.appiconset/Contents.json | 68 + .../AppIcon.appiconset/icon_128x128.png | Bin 0 -> 21253 bytes .../AppIcon.appiconset/icon_128x128@2x.png | Bin 0 -> 45356 bytes .../AppIcon.appiconset/icon_16x16.png | Bin 0 -> 992 bytes .../AppIcon.appiconset/icon_16x16@2x.png | Bin 0 -> 2945 bytes .../AppIcon.appiconset/icon_256x256.png | Bin 0 -> 45862 bytes .../AppIcon.appiconset/icon_256x256@2x.png | Bin 0 -> 89576 bytes .../AppIcon.appiconset/icon_32x32.png | Bin 0 -> 2945 bytes .../AppIcon.appiconset/icon_32x32@2x.png | Bin 0 -> 8765 bytes .../AppIcon.appiconset/icon_512x512.png | Bin 0 -> 89121 bytes .../AppIcon.appiconset/icon_512x512@2x.png | Bin 0 -> 182239 bytes Seqotron/MFAbstractSequencesView.h | 78 + Seqotron/MFAbstractSequencesView.m | 269 ++ Seqotron/MFAligner.xib | 143 + Seqotron/MFAlignerController.h | 53 + Seqotron/MFAlignerController.m | 227 ++ Seqotron/MFAppDelegate.h | 44 + Seqotron/MFAppDelegate.m | 226 ++ Seqotron/MFClustalImporter.h | 31 + Seqotron/MFClustalImporter.m | 135 + Seqotron/MFColorManager.h | 39 + Seqotron/MFColorManager.m | 162 ++ Seqotron/MFDataType.h | 52 + Seqotron/MFDataType.m | 78 + Seqotron/MFDefines.h | 79 + Seqotron/MFDistanceMatrix.h | 58 + Seqotron/MFDistanceMatrix.m | 131 + Seqotron/MFDistanceMatrixOperation.h | 41 + Seqotron/MFDistanceMatrixOperation.m | 94 + Seqotron/MFDistanceMatrixWindow.xib | 194 ++ Seqotron/MFDistanceWindowController.h | 48 + Seqotron/MFDistanceWindowController.m | 278 ++ Seqotron/MFDocument.h | 49 + Seqotron/MFDocument.m | 209 ++ Seqotron/MFDocumentController.h | 28 + Seqotron/MFDocumentController.m | 82 + Seqotron/MFExternalOperation.h | 46 + Seqotron/MFExternalOperation.m | 115 + Seqotron/MFFASTAImporter.h | 32 + Seqotron/MFFASTAImporter.m | 110 + Seqotron/MFFileReader.h | 38 + Seqotron/MFFileReader.m | 166 ++ Seqotron/MFGDEImporter.h | 30 + Seqotron/MFGDEImporter.m | 127 + Seqotron/MFGraphic.h | 102 + Seqotron/MFGraphic.m | 252 ++ Seqotron/MFJukeCantorDistanceMatrix.h | 28 + Seqotron/MFJukeCantorDistanceMatrix.m | 47 + Seqotron/MFK2PDistanceMatrix.h | 28 + Seqotron/MFK2PDistanceMatrix.m | 59 + Seqotron/MFK83DistanceMatrix.h | 28 + Seqotron/MFK83DistanceMatrix.m | 48 + Seqotron/MFLineGraphic.h | 41 + Seqotron/MFLineGraphic.m | 155 ++ Seqotron/MFMEGAImporter.h | 30 + Seqotron/MFMEGAImporter.m | 197 ++ Seqotron/MFNBRFImporter.h | 30 + Seqotron/MFNBRFImporter.m | 143 + Seqotron/MFNamesView.h | 45 + Seqotron/MFNamesView.m | 294 +++ Seqotron/MFNeighborJoining.h | 48 + Seqotron/MFNeighborJoining.m | 154 ++ Seqotron/MFNewickExporter.h | 31 + Seqotron/MFNewickExporter.m | 140 + Seqotron/MFNewickImporter.h | 30 + Seqotron/MFNewickImporter.m | 90 + Seqotron/MFNexusExporter.h | 34 + Seqotron/MFNexusExporter.m | 117 + Seqotron/MFNexusImporter.h | 43 + Seqotron/MFNexusImporter.m | 541 ++++ Seqotron/MFNode.h | 64 + Seqotron/MFNode.m | 114 + Seqotron/MFNucleotide.h | 31 + Seqotron/MFNucleotide.m | 104 + Seqotron/MFOperation.h | 49 + Seqotron/MFOperation.m | 87 + Seqotron/MFOperationBuilder.h | 32 + Seqotron/MFOperationDelegate.h | 41 + Seqotron/MFOperationTransalign.h | 37 + Seqotron/MFOperationTransalign.m | 97 + Seqotron/MFPhylipImporter.h | 36 + Seqotron/MFPhylipImporter.m | 227 ++ Seqotron/MFPrefs.xib | 266 ++ Seqotron/MFPrefsWindowController.h | 54 + Seqotron/MFPrefsWindowController.m | 630 +++++ Seqotron/MFProgressController.h | 43 + Seqotron/MFProgressController.m | 81 + Seqotron/MFProgressWindow.xib | 82 + Seqotron/MFProtein.h | 29 + Seqotron/MFProtein.m | 76 + Seqotron/MFReaderCluster.h | 42 + Seqotron/MFReaderCluster.m | 65 + Seqotron/MFRulerView.h | 44 + Seqotron/MFRulerView.m | 253 ++ Seqotron/MFScaleBar.h | 39 + Seqotron/MFScaleBar.m | 39 + Seqotron/MFSearchController.h | 21 + Seqotron/MFSearchController.m | 63 + Seqotron/MFSearchController.xib | 211 ++ Seqotron/MFSequence+MFSequenceDrawing.h | 35 + Seqotron/MFSequence+MFSequenceDrawing.m | 120 + Seqotron/MFSequence.h | 102 + Seqotron/MFSequence.m | 492 ++++ Seqotron/MFSequenceImporter.h | 38 + Seqotron/MFSequenceReader.h | 41 + Seqotron/MFSequenceReader.m | 277 ++ Seqotron/MFSequenceSet.h | 73 + Seqotron/MFSequenceSet.m | 239 ++ Seqotron/MFSequenceUtils.h | 40 + Seqotron/MFSequenceUtils.m | 179 ++ Seqotron/MFSequenceWriter.h | 83 + Seqotron/MFSequenceWriter.m | 477 ++++ Seqotron/MFSequencesView.h | 118 + Seqotron/MFSequencesView.m | 1212 +++++++++ Seqotron/MFSimpleAlignmentView.h | 47 + Seqotron/MFSimpleAlignmentView.m | 198 ++ Seqotron/MFStockholmImporter.h | 32 + Seqotron/MFStockholmImporter.m | 106 + Seqotron/MFString.h | 46 + Seqotron/MFString.m | 144 + Seqotron/MFStringReader.h | 34 + Seqotron/MFStringReader.m | 58 + Seqotron/MFSyncronizedScrollView.h | 35 + Seqotron/MFSyncronizedScrollView.m | 118 + Seqotron/MFTextGraphic.h | 39 + Seqotron/MFTextGraphic.m | 233 ++ Seqotron/MFTree.h | 73 + Seqotron/MFTree.m | 416 +++ Seqotron/MFTreeBuilderController.h | 77 + Seqotron/MFTreeBuilderController.m | 321 +++ Seqotron/MFTreeBuilderController.xib | 352 +++ Seqotron/MFTreeDocument.h | 36 + Seqotron/MFTreeDocument.m | 177 ++ Seqotron/MFTreeExporter.h | 65 + Seqotron/MFTreeExporter.m | 92 + Seqotron/MFTreeImporter.h | 37 + Seqotron/MFTreeReader.h | 36 + Seqotron/MFTreeReader.m | 119 + Seqotron/MFTreeView.h | 129 + Seqotron/MFTreeView.m | 1020 +++++++ Seqotron/MFTreeWindow.xib | 246 ++ Seqotron/MFTreeWindowController.h | 62 + Seqotron/MFTreeWindowController.m | 595 +++++ Seqotron/MFTreeWriter.h | 53 + Seqotron/MFTreeWriter.m | 113 + Seqotron/MFWindowController.h | 114 + Seqotron/MFWindowController.m | 2349 +++++++++++++++++ Seqotron/NSArray+IndexSetAddition.h | 30 + Seqotron/NSArray+IndexSetAddition.m | 44 + Seqotron/NSObject+TDBindings.h | 30 + Seqotron/NSObject+TDBindings.m | 71 + Seqotron/Seqotron-Info.plist | 69 + Seqotron/Seqotron-Prefix.pch | 9 + Seqotron/en.lproj/Credits.rtf | 32 + Seqotron/en.lproj/InfoPlist.strings | 2 + Seqotron/main.m | 29 + .../project.pbxproj | 443 ++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/SeqotronQuicklook.xccheckout | 41 + .../SeqotronQuicklook/GeneratePreviewForURL.m | 152 ++ .../GenerateThumbnailForURL.c | 24 + .../SeqotronQuicklook/Info.plist | 91 + SeqotronQuicklook/SeqotronQuicklook/main.c | 218 ++ SeqotronTests/SeqotronTests-Info.plist | 22 + SeqotronTests/SeqotronTests.m | 29 + SeqotronTests/en.lproj/InfoPlist.strings | 2 + packaging.sh | 163 ++ 201 files changed, 36231 insertions(+) create mode 100644 .gitignore create mode 100755 LICENSE create mode 100755 README.md create mode 100755 Seqotron.xcodeproj/project.pbxproj create mode 100755 Seqotron.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 Seqotron.xcodeproj/project.xcworkspace/xcshareddata/Seqotron.xccheckout create mode 100755 Seqotron/Base.lproj/MFDocument.xib create mode 100755 Seqotron/Base.lproj/MainMenu.xib create mode 100755 Seqotron/Coloring/Amino Acid/color-amino-acid.plist create mode 100755 Seqotron/Coloring/Amino Acid/monochrome-amino-acid.plist create mode 100755 Seqotron/Coloring/Nucleotide/black-nucleotide.plist create mode 100755 Seqotron/Coloring/Nucleotide/clustal-nucleotide.plist create mode 100755 Seqotron/Coloring/Nucleotide/color-nucleotide.plist create mode 100755 Seqotron/Coloring/Nucleotide/geneious.plist create mode 100755 Seqotron/DBPrefsWindowController.h create mode 100755 Seqotron/DBPrefsWindowController.m create mode 100755 Seqotron/GeneticCodeTables/Alternative-Flatworm-Mitochondrial.plist create mode 100755 Seqotron/GeneticCodeTables/Alternative-Yeast-Nuclear.plist create mode 100755 Seqotron/GeneticCodeTables/Ascidian-Mitochondrial.plist create mode 100755 Seqotron/GeneticCodeTables/Bacterial,-Archaeal-and-Plant-Plastid.plist create mode 100755 Seqotron/GeneticCodeTables/Candidate-Division-SR1-and-Gracilibacteria.plist create mode 100755 Seqotron/GeneticCodeTables/Chlorophycean-Mitochondrial.plist create mode 100755 Seqotron/GeneticCodeTables/Ciliate,-Dasycladacean-and-Hexamita-Nuclear.plist create mode 100755 Seqotron/GeneticCodeTables/Echinoderm-and-Flatworm-Mitochondrial.plist create mode 100755 Seqotron/GeneticCodeTables/Euplotid-Nuclear.plist create mode 100755 Seqotron/GeneticCodeTables/Invertebrate-Mitochondrial.plist create mode 100755 Seqotron/GeneticCodeTables/Mold,-Protozoan,-and-Coelenterate-Mitochondrial-Code-and-the-Mycoplasma-Spiroplasma.plist create mode 100755 Seqotron/GeneticCodeTables/Pterobranchia-Mitochondrial.plist create mode 100755 Seqotron/GeneticCodeTables/Scenedesmus-obliquus-Mitochondrial.plist create mode 100755 Seqotron/GeneticCodeTables/Standard.plist create mode 100755 Seqotron/GeneticCodeTables/Thraustochytrium-Mitochondrial.plist create mode 100755 Seqotron/GeneticCodeTables/Trematode-Mitochondrial.plist create mode 100755 Seqotron/GeneticCodeTables/Yeast-Mitochondrial.plist create mode 100755 Seqotron/GeneticCodeTables/vertebrate-mitochondrial.plist create mode 100755 Seqotron/Images.xcassets/AppIcon.appiconset/Contents.json create mode 100644 Seqotron/Images.xcassets/AppIcon.appiconset/icon_128x128.png create mode 100644 Seqotron/Images.xcassets/AppIcon.appiconset/icon_128x128@2x.png create mode 100644 Seqotron/Images.xcassets/AppIcon.appiconset/icon_16x16.png create mode 100644 Seqotron/Images.xcassets/AppIcon.appiconset/icon_16x16@2x.png create mode 100644 Seqotron/Images.xcassets/AppIcon.appiconset/icon_256x256.png create mode 100644 Seqotron/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png create mode 100644 Seqotron/Images.xcassets/AppIcon.appiconset/icon_32x32.png create mode 100644 Seqotron/Images.xcassets/AppIcon.appiconset/icon_32x32@2x.png create mode 100644 Seqotron/Images.xcassets/AppIcon.appiconset/icon_512x512.png create mode 100644 Seqotron/Images.xcassets/AppIcon.appiconset/icon_512x512@2x.png create mode 100755 Seqotron/MFAbstractSequencesView.h create mode 100755 Seqotron/MFAbstractSequencesView.m create mode 100644 Seqotron/MFAligner.xib create mode 100644 Seqotron/MFAlignerController.h create mode 100644 Seqotron/MFAlignerController.m create mode 100755 Seqotron/MFAppDelegate.h create mode 100755 Seqotron/MFAppDelegate.m create mode 100755 Seqotron/MFClustalImporter.h create mode 100755 Seqotron/MFClustalImporter.m create mode 100644 Seqotron/MFColorManager.h create mode 100644 Seqotron/MFColorManager.m create mode 100755 Seqotron/MFDataType.h create mode 100755 Seqotron/MFDataType.m create mode 100755 Seqotron/MFDefines.h create mode 100755 Seqotron/MFDistanceMatrix.h create mode 100755 Seqotron/MFDistanceMatrix.m create mode 100644 Seqotron/MFDistanceMatrixOperation.h create mode 100644 Seqotron/MFDistanceMatrixOperation.m create mode 100644 Seqotron/MFDistanceMatrixWindow.xib create mode 100644 Seqotron/MFDistanceWindowController.h create mode 100644 Seqotron/MFDistanceWindowController.m create mode 100755 Seqotron/MFDocument.h create mode 100755 Seqotron/MFDocument.m create mode 100644 Seqotron/MFDocumentController.h create mode 100644 Seqotron/MFDocumentController.m create mode 100644 Seqotron/MFExternalOperation.h create mode 100644 Seqotron/MFExternalOperation.m create mode 100755 Seqotron/MFFASTAImporter.h create mode 100755 Seqotron/MFFASTAImporter.m create mode 100755 Seqotron/MFFileReader.h create mode 100755 Seqotron/MFFileReader.m create mode 100755 Seqotron/MFGDEImporter.h create mode 100755 Seqotron/MFGDEImporter.m create mode 100755 Seqotron/MFGraphic.h create mode 100755 Seqotron/MFGraphic.m create mode 100755 Seqotron/MFJukeCantorDistanceMatrix.h create mode 100755 Seqotron/MFJukeCantorDistanceMatrix.m create mode 100755 Seqotron/MFK2PDistanceMatrix.h create mode 100755 Seqotron/MFK2PDistanceMatrix.m create mode 100644 Seqotron/MFK83DistanceMatrix.h create mode 100644 Seqotron/MFK83DistanceMatrix.m create mode 100755 Seqotron/MFLineGraphic.h create mode 100755 Seqotron/MFLineGraphic.m create mode 100755 Seqotron/MFMEGAImporter.h create mode 100755 Seqotron/MFMEGAImporter.m create mode 100755 Seqotron/MFNBRFImporter.h create mode 100755 Seqotron/MFNBRFImporter.m create mode 100755 Seqotron/MFNamesView.h create mode 100755 Seqotron/MFNamesView.m create mode 100755 Seqotron/MFNeighborJoining.h create mode 100755 Seqotron/MFNeighborJoining.m create mode 100644 Seqotron/MFNewickExporter.h create mode 100644 Seqotron/MFNewickExporter.m create mode 100644 Seqotron/MFNewickImporter.h create mode 100644 Seqotron/MFNewickImporter.m create mode 100644 Seqotron/MFNexusExporter.h create mode 100644 Seqotron/MFNexusExporter.m create mode 100755 Seqotron/MFNexusImporter.h create mode 100755 Seqotron/MFNexusImporter.m create mode 100755 Seqotron/MFNode.h create mode 100755 Seqotron/MFNode.m create mode 100755 Seqotron/MFNucleotide.h create mode 100755 Seqotron/MFNucleotide.m create mode 100644 Seqotron/MFOperation.h create mode 100644 Seqotron/MFOperation.m create mode 100644 Seqotron/MFOperationBuilder.h create mode 100644 Seqotron/MFOperationDelegate.h create mode 100644 Seqotron/MFOperationTransalign.h create mode 100644 Seqotron/MFOperationTransalign.m create mode 100755 Seqotron/MFPhylipImporter.h create mode 100755 Seqotron/MFPhylipImporter.m create mode 100644 Seqotron/MFPrefs.xib create mode 100644 Seqotron/MFPrefsWindowController.h create mode 100644 Seqotron/MFPrefsWindowController.m create mode 100644 Seqotron/MFProgressController.h create mode 100644 Seqotron/MFProgressController.m create mode 100644 Seqotron/MFProgressWindow.xib create mode 100755 Seqotron/MFProtein.h create mode 100755 Seqotron/MFProtein.m create mode 100755 Seqotron/MFReaderCluster.h create mode 100755 Seqotron/MFReaderCluster.m create mode 100755 Seqotron/MFRulerView.h create mode 100755 Seqotron/MFRulerView.m create mode 100644 Seqotron/MFScaleBar.h create mode 100644 Seqotron/MFScaleBar.m create mode 100644 Seqotron/MFSearchController.h create mode 100644 Seqotron/MFSearchController.m create mode 100644 Seqotron/MFSearchController.xib create mode 100755 Seqotron/MFSequence+MFSequenceDrawing.h create mode 100755 Seqotron/MFSequence+MFSequenceDrawing.m create mode 100755 Seqotron/MFSequence.h create mode 100755 Seqotron/MFSequence.m create mode 100755 Seqotron/MFSequenceImporter.h create mode 100755 Seqotron/MFSequenceReader.h create mode 100755 Seqotron/MFSequenceReader.m create mode 100755 Seqotron/MFSequenceSet.h create mode 100755 Seqotron/MFSequenceSet.m create mode 100755 Seqotron/MFSequenceUtils.h create mode 100755 Seqotron/MFSequenceUtils.m create mode 100755 Seqotron/MFSequenceWriter.h create mode 100755 Seqotron/MFSequenceWriter.m create mode 100755 Seqotron/MFSequencesView.h create mode 100755 Seqotron/MFSequencesView.m create mode 100644 Seqotron/MFSimpleAlignmentView.h create mode 100644 Seqotron/MFSimpleAlignmentView.m create mode 100755 Seqotron/MFStockholmImporter.h create mode 100755 Seqotron/MFStockholmImporter.m create mode 100755 Seqotron/MFString.h create mode 100755 Seqotron/MFString.m create mode 100755 Seqotron/MFStringReader.h create mode 100755 Seqotron/MFStringReader.m create mode 100755 Seqotron/MFSyncronizedScrollView.h create mode 100755 Seqotron/MFSyncronizedScrollView.m create mode 100755 Seqotron/MFTextGraphic.h create mode 100755 Seqotron/MFTextGraphic.m create mode 100755 Seqotron/MFTree.h create mode 100755 Seqotron/MFTree.m create mode 100644 Seqotron/MFTreeBuilderController.h create mode 100644 Seqotron/MFTreeBuilderController.m create mode 100644 Seqotron/MFTreeBuilderController.xib create mode 100644 Seqotron/MFTreeDocument.h create mode 100644 Seqotron/MFTreeDocument.m create mode 100644 Seqotron/MFTreeExporter.h create mode 100644 Seqotron/MFTreeExporter.m create mode 100644 Seqotron/MFTreeImporter.h create mode 100644 Seqotron/MFTreeReader.h create mode 100644 Seqotron/MFTreeReader.m create mode 100755 Seqotron/MFTreeView.h create mode 100755 Seqotron/MFTreeView.m create mode 100644 Seqotron/MFTreeWindow.xib create mode 100644 Seqotron/MFTreeWindowController.h create mode 100644 Seqotron/MFTreeWindowController.m create mode 100644 Seqotron/MFTreeWriter.h create mode 100644 Seqotron/MFTreeWriter.m create mode 100755 Seqotron/MFWindowController.h create mode 100755 Seqotron/MFWindowController.m create mode 100755 Seqotron/NSArray+IndexSetAddition.h create mode 100755 Seqotron/NSArray+IndexSetAddition.m create mode 100644 Seqotron/NSObject+TDBindings.h create mode 100644 Seqotron/NSObject+TDBindings.m create mode 100755 Seqotron/Seqotron-Info.plist create mode 100755 Seqotron/Seqotron-Prefix.pch create mode 100755 Seqotron/en.lproj/Credits.rtf create mode 100755 Seqotron/en.lproj/InfoPlist.strings create mode 100755 Seqotron/main.m create mode 100755 SeqotronQuicklook/SeqotronQuicklook.xcodeproj/project.pbxproj create mode 100755 SeqotronQuicklook/SeqotronQuicklook.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 SeqotronQuicklook/SeqotronQuicklook.xcodeproj/project.xcworkspace/xcshareddata/SeqotronQuicklook.xccheckout create mode 100755 SeqotronQuicklook/SeqotronQuicklook/GeneratePreviewForURL.m create mode 100755 SeqotronQuicklook/SeqotronQuicklook/GenerateThumbnailForURL.c create mode 100755 SeqotronQuicklook/SeqotronQuicklook/Info.plist create mode 100755 SeqotronQuicklook/SeqotronQuicklook/main.c create mode 100755 SeqotronTests/SeqotronTests-Info.plist create mode 100755 SeqotronTests/SeqotronTests.m create mode 100755 SeqotronTests/en.lproj/InfoPlist.strings create mode 100755 packaging.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea1b40f --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +/DerivedData +xcuserdata +*.dmg +/Seqotron/bin +/SeqotronQuicklook/DerivedData diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..20d40b6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100755 index 0000000..b49bf24 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +![Sequence viewer](http://mateo.fourment.free.fr/software/seqotron/seqotron-screenshot.png "Seqotron sequence viewer") + +Seqotron is a user-friendly sequence editor. It is written in Objective-C/Cocoa and runs on Mac OS X. + +## Features + +* Visualisation and manual editing of nucleotide and amino acid sequences. +* Alignment through [MUSCLE](http://www.drive5.com/muscle). +* Phylogenetic tree inference with [Physher](http://github.com/4ment/physher). +* Open source under [GNU GPL](http://www.gnu.org/copyleft/gpl.html). + +## Getting started + +[See the wiki](https://github.com/4ment/seqotron/wiki) + +## Reference + +Mathieu Fourment and Edward C. Holmes. Seqotron: a user-friendly sequence editor. (Submitted) \ No newline at end of file diff --git a/Seqotron.xcodeproj/project.pbxproj b/Seqotron.xcodeproj/project.pbxproj new file mode 100755 index 0000000..e82fc8e --- /dev/null +++ b/Seqotron.xcodeproj/project.pbxproj @@ -0,0 +1,984 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 4E1214C719EFA52200463E9B /* GeneticCodeTables in Resources */ = {isa = PBXBuildFile; fileRef = 4E1214C619EFA52200463E9B /* GeneticCodeTables */; }; + 4E28172219F0C93400860621 /* Coloring in Resources */ = {isa = PBXBuildFile; fileRef = 4E28172119F0C93400860621 /* Coloring */; }; + 4E62FC691A93107600CFD8D0 /* MFScaleBar.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E62FC681A93107600CFD8D0 /* MFScaleBar.m */; }; + 4E63B7211AC2146A00643129 /* NSObject+TDBindings.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E63B7201AC2146A00643129 /* NSObject+TDBindings.m */; }; + 4E8120341B044F9B004A3F3E /* MFSearchController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E8120321B044F9B004A3F3E /* MFSearchController.m */; }; + 4E8120351B044F9B004A3F3E /* MFSearchController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4E8120331B044F9B004A3F3E /* MFSearchController.xib */; }; + 4E8120381B057BE5004A3F3E /* MFK83DistanceMatrix.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E8120371B057BE5004A3F3E /* MFK83DistanceMatrix.m */; }; + 4E8922E01982277900A0DC64 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E8922DF1982277900A0DC64 /* Cocoa.framework */; }; + 4E8922EA1982277900A0DC64 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4E8922E81982277900A0DC64 /* InfoPlist.strings */; }; + 4E8922EC1982277900A0DC64 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E8922EB1982277900A0DC64 /* main.m */; }; + 4E8922F01982277900A0DC64 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 4E8922EE1982277900A0DC64 /* Credits.rtf */; }; + 4E8922F31982277900A0DC64 /* MFDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E8922F21982277900A0DC64 /* MFDocument.m */; }; + 4E8922F61982277900A0DC64 /* MFDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4E8922F41982277900A0DC64 /* MFDocument.xib */; }; + 4E8922F91982277900A0DC64 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4E8922F71982277900A0DC64 /* MainMenu.xib */; }; + 4E8922FB1982277900A0DC64 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4E8922FA1982277900A0DC64 /* Images.xcassets */; }; + 4E8923021982277900A0DC64 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E8923011982277900A0DC64 /* XCTest.framework */; }; + 4E8923031982277900A0DC64 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E8922DF1982277900A0DC64 /* Cocoa.framework */; }; + 4E89230B1982277900A0DC64 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4E8923091982277900A0DC64 /* InfoPlist.strings */; }; + 4E89230D1982277900A0DC64 /* SeqotronTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E89230C1982277900A0DC64 /* SeqotronTests.m */; }; + 4E892332198227C200A0DC64 /* MFAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E892319198227C200A0DC64 /* MFAppDelegate.m */; }; + 4E892333198227C200A0DC64 /* MFDataType.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E89231B198227C200A0DC64 /* MFDataType.m */; }; + 4E892334198227C200A0DC64 /* MFNamesView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E89231E198227C200A0DC64 /* MFNamesView.m */; }; + 4E892335198227C200A0DC64 /* MFNucleotide.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E892320198227C200A0DC64 /* MFNucleotide.m */; }; + 4E892337198227C200A0DC64 /* MFProtein.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E892324198227C200A0DC64 /* MFProtein.m */; }; + 4E892338198227C200A0DC64 /* MFSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E892326198227C200A0DC64 /* MFSequence.m */; }; + 4E892339198227C200A0DC64 /* MFSequenceReader.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E892328198227C200A0DC64 /* MFSequenceReader.m */; }; + 4E89233A198227C200A0DC64 /* MFSequenceSet.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E89232A198227C200A0DC64 /* MFSequenceSet.m */; }; + 4E89233B198227C200A0DC64 /* MFSequencesView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E89232C198227C200A0DC64 /* MFSequencesView.m */; }; + 4E89233C198227C200A0DC64 /* MFSequenceWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E89232E198227C200A0DC64 /* MFSequenceWriter.m */; }; + 4E89233D198227C200A0DC64 /* MFSyncronizedScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E892330198227C200A0DC64 /* MFSyncronizedScrollView.m */; }; + 4E892342198227E800A0DC64 /* MFRulerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E89233F198227E800A0DC64 /* MFRulerView.m */; }; + 4E892343198227E800A0DC64 /* MFString.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E892341198227E800A0DC64 /* MFString.m */; }; + 4E92745E1A0C429200DA48F8 /* MFFileReader.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E92745D1A0C429200DA48F8 /* MFFileReader.m */; }; + 4E9274611A0C4A7300DA48F8 /* MFFASTAImporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E9274601A0C4A7300DA48F8 /* MFFASTAImporter.m */; }; + 4E9274641A0C4DE400DA48F8 /* MFReaderCluster.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E9274631A0C4DE400DA48F8 /* MFReaderCluster.m */; }; + 4E9274671A0C525F00DA48F8 /* MFStringReader.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E9274661A0C525F00DA48F8 /* MFStringReader.m */; }; + 4E92746A1A0C64B000DA48F8 /* MFClustalImporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E9274691A0C64B000DA48F8 /* MFClustalImporter.m */; }; + 4E9B69581A37F637001157D7 /* MFDistanceMatrixOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E9B69571A37F637001157D7 /* MFDistanceMatrixOperation.m */; }; + 4E9B69611A3D72D7001157D7 /* MFTreeWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E9B695F1A3D72D7001157D7 /* MFTreeWindowController.m */; }; + 4E9B69621A3D72D7001157D7 /* MFTreeWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4E9B69601A3D72D7001157D7 /* MFTreeWindow.xib */; }; + 4E9B69661A3D74AA001157D7 /* MFTreeDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E9B69651A3D74AA001157D7 /* MFTreeDocument.m */; }; + 4E9B696A1A3E982E001157D7 /* MFNewickImporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E9B69691A3E982E001157D7 /* MFNewickImporter.m */; }; + 4E9B696D1A3E9C1F001157D7 /* MFTreeReader.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E9B696C1A3E9C1F001157D7 /* MFTreeReader.m */; }; + 4E9B69701A401631001157D7 /* MFDocumentController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E9B696F1A401631001157D7 /* MFDocumentController.m */; }; + 4E9B69821A4294EB001157D7 /* MFDistanceWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E9B69801A4294EB001157D7 /* MFDistanceWindowController.m */; }; + 4E9B69831A4294EB001157D7 /* MFDistanceMatrixWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4E9B69811A4294EB001157D7 /* MFDistanceMatrixWindow.xib */; }; + 4E9B69871A42B31D001157D7 /* MFTreeBuilderController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E9B69851A42B31D001157D7 /* MFTreeBuilderController.m */; }; + 4E9B69881A42B31D001157D7 /* MFTreeBuilderController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4E9B69861A42B31D001157D7 /* MFTreeBuilderController.xib */; }; + 4EA0D7631A22F4EC0084E04C /* NSArray+IndexSetAddition.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EA0D7621A22F4EC0084E04C /* NSArray+IndexSetAddition.m */; }; + 4EAA0DF0199204E1007FEE79 /* MFWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EAA0DEF199204E1007FEE79 /* MFWindowController.m */; }; + 4EAA0DF3199238A5007FEE79 /* MFSequence+MFSequenceDrawing.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EAA0DF2199238A5007FEE79 /* MFSequence+MFSequenceDrawing.m */; }; + 4EAA0DF619BFF03F007FEE79 /* MFAbstractSequencesView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EAA0DF519BFF03F007FEE79 /* MFAbstractSequencesView.m */; }; + 4ECFCE8B1AA551B500245B86 /* DBPrefsWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ECFCE8A1AA551B500245B86 /* DBPrefsWindowController.m */; }; + 4ECFCE8E1AA551F200245B86 /* MFPrefsWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ECFCE8D1AA551F200245B86 /* MFPrefsWindowController.m */; }; + 4ECFCE901AA5546200245B86 /* MFPrefs.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4ECFCE8F1AA5546200245B86 /* MFPrefs.xib */; }; + 4ECFCE931AA5BED600245B86 /* MFSimpleAlignmentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ECFCE921AA5BED600245B86 /* MFSimpleAlignmentView.m */; }; + 4ECFCE961AA674C700245B86 /* MFColorManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ECFCE951AA674C700245B86 /* MFColorManager.m */; }; + 4ECFCE991AAAABBB00245B86 /* MFAlignerController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ECFCE981AAAABBB00245B86 /* MFAlignerController.m */; }; + 4ECFCE9B1AAAABF000245B86 /* MFAligner.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4ECFCE9A1AAAABF000245B86 /* MFAligner.xib */; }; + 4ECFCEA41AAD29FF00245B86 /* bin in Resources */ = {isa = PBXBuildFile; fileRef = 4ECFCEA31AAD29FF00245B86 /* bin */; }; + 4ECFCEA71AAD473400245B86 /* MFOperationTransalign.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ECFCEA61AAD473400245B86 /* MFOperationTransalign.m */; }; + 4ECFCEAD1AAE54B300245B86 /* MFOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ECFCEAC1AAE54B300245B86 /* MFOperation.m */; }; + 4ECFCEB21AAE943C00245B86 /* MFTreeWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ECFCEB11AAE943C00245B86 /* MFTreeWriter.m */; }; + 4EDDA2421A00A90D009970DB /* MFSequenceUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EDDA2411A00A90D009970DB /* MFSequenceUtils.m */; }; + 4EDDA2481A0855A7009970DB /* MFNexusImporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EDDA2471A0855A7009970DB /* MFNexusImporter.m */; }; + 4EE911C41A2EB6BD009A11D7 /* MFStockholmImporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EE911C31A2EB6BD009A11D7 /* MFStockholmImporter.m */; }; + 4EE911C91A2EC4B4009A11D7 /* MFNeighborJoining.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EE911C81A2EC4B4009A11D7 /* MFNeighborJoining.m */; }; + 4EE911CD1A3133D3009A11D7 /* MFJukeCantorDistanceMatrix.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EE911CC1A3133D3009A11D7 /* MFJukeCantorDistanceMatrix.m */; }; + 4EE911D01A31357E009A11D7 /* MFK2PDistanceMatrix.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EE911CF1A31357E009A11D7 /* MFK2PDistanceMatrix.m */; }; + 4EE911D31A3153F7009A11D7 /* MFGraphic.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EE911D21A3153F7009A11D7 /* MFGraphic.m */; }; + 4EE911D61A3156DC009A11D7 /* MFLineGraphic.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EE911D51A3156DC009A11D7 /* MFLineGraphic.m */; }; + 4EE911D91A315E8C009A11D7 /* MFTextGraphic.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EE911D81A315E8C009A11D7 /* MFTextGraphic.m */; }; + 4EF044821A0C665D00DB9C8E /* MFMEGAImporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EF044811A0C665D00DB9C8E /* MFMEGAImporter.m */; }; + 4EF044851A0C671B00DB9C8E /* MFGDEImporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EF044841A0C671B00DB9C8E /* MFGDEImporter.m */; }; + 4EF044881A0C67AB00DB9C8E /* MFNBRFImporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EF044871A0C67AB00DB9C8E /* MFNBRFImporter.m */; }; + 4EF0448C1A0C68BB00DB9C8E /* MFPhylipImporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EF0448B1A0C68BB00DB9C8E /* MFPhylipImporter.m */; }; + 4EF0448F1A0C853B00DB9C8E /* MFDistanceMatrix.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EF0448E1A0C853B00DB9C8E /* MFDistanceMatrix.m */; }; + 4EF044921A0C8F4200DB9C8E /* MFNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EF044911A0C8F4200DB9C8E /* MFNode.m */; }; + 4EF044951A0C901700DB9C8E /* MFTree.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EF044941A0C901700DB9C8E /* MFTree.m */; }; + 4EF0449A1A11B1B900DB9C8E /* MFTreeView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EF044991A11B1B900DB9C8E /* MFTreeView.m */; }; + 4EF236121A66728000FDB4D7 /* MFExternalOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EF236111A66728000FDB4D7 /* MFExternalOperation.m */; }; + 4EF236161A69FF4800FDB4D7 /* MFProgressController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EF236141A69FF4800FDB4D7 /* MFProgressController.m */; }; + 4EF236171A69FF4800FDB4D7 /* MFProgressWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4EF236151A69FF4800FDB4D7 /* MFProgressWindow.xib */; }; + 4EF2361F1A6F598E00FDB4D7 /* MFNexusExporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EF2361E1A6F598E00FDB4D7 /* MFNexusExporter.m */; }; + 4EF236221A6F6C0800FDB4D7 /* MFNewickExporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EF236211A6F6C0800FDB4D7 /* MFNewickExporter.m */; }; + 4EF236251A6F6D9300FDB4D7 /* MFTreeExporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EF236241A6F6D9300FDB4D7 /* MFTreeExporter.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 4E8923041982277900A0DC64 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 4E8922D41982277900A0DC64 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4E8922DB1982277900A0DC64; + remoteInfo = Seqotron; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 4E1214C619EFA52200463E9B /* GeneticCodeTables */ = {isa = PBXFileReference; lastKnownFileType = folder; path = GeneticCodeTables; sourceTree = ""; }; + 4E28172119F0C93400860621 /* Coloring */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Coloring; sourceTree = ""; }; + 4E62FC671A93107500CFD8D0 /* MFScaleBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = MFScaleBar.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 4E62FC681A93107600CFD8D0 /* MFScaleBar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MFScaleBar.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 4E63B71F1AC2146A00643129 /* NSObject+TDBindings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+TDBindings.h"; sourceTree = ""; }; + 4E63B7201AC2146A00643129 /* NSObject+TDBindings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+TDBindings.m"; sourceTree = ""; }; + 4E8120311B044F9B004A3F3E /* MFSearchController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFSearchController.h; sourceTree = ""; }; + 4E8120321B044F9B004A3F3E /* MFSearchController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFSearchController.m; sourceTree = ""; }; + 4E8120331B044F9B004A3F3E /* MFSearchController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MFSearchController.xib; sourceTree = ""; }; + 4E8120361B057BE5004A3F3E /* MFK83DistanceMatrix.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFK83DistanceMatrix.h; sourceTree = ""; }; + 4E8120371B057BE5004A3F3E /* MFK83DistanceMatrix.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFK83DistanceMatrix.m; sourceTree = ""; }; + 4E8922DC1982277900A0DC64 /* Seqotron.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Seqotron.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 4E8922DF1982277900A0DC64 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; + 4E8922E21982277900A0DC64 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; + 4E8922E31982277900A0DC64 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; + 4E8922E41982277900A0DC64 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 4E8922E71982277900A0DC64 /* Seqotron-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Seqotron-Info.plist"; sourceTree = ""; }; + 4E8922E91982277900A0DC64 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + 4E8922EB1982277900A0DC64 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 4E8922ED1982277900A0DC64 /* Seqotron-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Seqotron-Prefix.pch"; sourceTree = ""; }; + 4E8922EF1982277900A0DC64 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/Credits.rtf; sourceTree = ""; }; + 4E8922F11982277900A0DC64 /* MFDocument.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MFDocument.h; sourceTree = ""; }; + 4E8922F21982277900A0DC64 /* MFDocument.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MFDocument.m; sourceTree = ""; }; + 4E8922F51982277900A0DC64 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MFDocument.xib; sourceTree = ""; }; + 4E8922F81982277900A0DC64 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 4E8922FA1982277900A0DC64 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + 4E8923001982277900A0DC64 /* SeqotronTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SeqotronTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 4E8923011982277900A0DC64 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + 4E8923081982277900A0DC64 /* SeqotronTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SeqotronTests-Info.plist"; sourceTree = ""; }; + 4E89230A1982277900A0DC64 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + 4E89230C1982277900A0DC64 /* SeqotronTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SeqotronTests.m; sourceTree = ""; }; + 4E892318198227C200A0DC64 /* MFAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFAppDelegate.h; sourceTree = ""; }; + 4E892319198227C200A0DC64 /* MFAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFAppDelegate.m; sourceTree = ""; }; + 4E89231A198227C200A0DC64 /* MFDataType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFDataType.h; sourceTree = ""; }; + 4E89231B198227C200A0DC64 /* MFDataType.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFDataType.m; sourceTree = ""; }; + 4E89231C198227C200A0DC64 /* MFDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFDefines.h; sourceTree = ""; }; + 4E89231D198227C200A0DC64 /* MFNamesView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFNamesView.h; sourceTree = ""; }; + 4E89231E198227C200A0DC64 /* MFNamesView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFNamesView.m; sourceTree = ""; }; + 4E89231F198227C200A0DC64 /* MFNucleotide.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFNucleotide.h; sourceTree = ""; }; + 4E892320198227C200A0DC64 /* MFNucleotide.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFNucleotide.m; sourceTree = ""; }; + 4E892323198227C200A0DC64 /* MFProtein.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFProtein.h; sourceTree = ""; }; + 4E892324198227C200A0DC64 /* MFProtein.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFProtein.m; sourceTree = ""; }; + 4E892325198227C200A0DC64 /* MFSequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFSequence.h; sourceTree = ""; }; + 4E892326198227C200A0DC64 /* MFSequence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFSequence.m; sourceTree = ""; }; + 4E892327198227C200A0DC64 /* MFSequenceReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFSequenceReader.h; sourceTree = ""; }; + 4E892328198227C200A0DC64 /* MFSequenceReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFSequenceReader.m; sourceTree = ""; }; + 4E892329198227C200A0DC64 /* MFSequenceSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFSequenceSet.h; sourceTree = ""; }; + 4E89232A198227C200A0DC64 /* MFSequenceSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFSequenceSet.m; sourceTree = ""; }; + 4E89232B198227C200A0DC64 /* MFSequencesView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFSequencesView.h; sourceTree = ""; }; + 4E89232C198227C200A0DC64 /* MFSequencesView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFSequencesView.m; sourceTree = ""; }; + 4E89232D198227C200A0DC64 /* MFSequenceWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFSequenceWriter.h; sourceTree = ""; }; + 4E89232E198227C200A0DC64 /* MFSequenceWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFSequenceWriter.m; sourceTree = ""; }; + 4E89232F198227C200A0DC64 /* MFSyncronizedScrollView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFSyncronizedScrollView.h; sourceTree = ""; }; + 4E892330198227C200A0DC64 /* MFSyncronizedScrollView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFSyncronizedScrollView.m; sourceTree = ""; }; + 4E89233E198227E800A0DC64 /* MFRulerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFRulerView.h; sourceTree = ""; }; + 4E89233F198227E800A0DC64 /* MFRulerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFRulerView.m; sourceTree = ""; }; + 4E892340198227E800A0DC64 /* MFString.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFString.h; sourceTree = ""; }; + 4E892341198227E800A0DC64 /* MFString.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFString.m; sourceTree = ""; }; + 4E92745C1A0C429200DA48F8 /* MFFileReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFFileReader.h; sourceTree = ""; }; + 4E92745D1A0C429200DA48F8 /* MFFileReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFFileReader.m; sourceTree = ""; }; + 4E92745F1A0C4A7300DA48F8 /* MFFASTAImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFFASTAImporter.h; sourceTree = ""; }; + 4E9274601A0C4A7300DA48F8 /* MFFASTAImporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFFASTAImporter.m; sourceTree = ""; }; + 4E9274621A0C4DE400DA48F8 /* MFReaderCluster.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = MFReaderCluster.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 4E9274631A0C4DE400DA48F8 /* MFReaderCluster.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MFReaderCluster.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 4E9274651A0C525F00DA48F8 /* MFStringReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFStringReader.h; sourceTree = ""; }; + 4E9274661A0C525F00DA48F8 /* MFStringReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFStringReader.m; sourceTree = ""; }; + 4E9274681A0C64B000DA48F8 /* MFClustalImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFClustalImporter.h; sourceTree = ""; }; + 4E9274691A0C64B000DA48F8 /* MFClustalImporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFClustalImporter.m; sourceTree = ""; }; + 4E9B69561A37F637001157D7 /* MFDistanceMatrixOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MFDistanceMatrixOperation.h; path = Seqotron/MFDistanceMatrixOperation.h; sourceTree = ""; }; + 4E9B69571A37F637001157D7 /* MFDistanceMatrixOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MFDistanceMatrixOperation.m; path = Seqotron/MFDistanceMatrixOperation.m; sourceTree = ""; }; + 4E9B695E1A3D72D7001157D7 /* MFTreeWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFTreeWindowController.h; sourceTree = ""; }; + 4E9B695F1A3D72D7001157D7 /* MFTreeWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFTreeWindowController.m; sourceTree = ""; }; + 4E9B69601A3D72D7001157D7 /* MFTreeWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MFTreeWindow.xib; sourceTree = ""; }; + 4E9B69641A3D74AA001157D7 /* MFTreeDocument.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFTreeDocument.h; sourceTree = ""; }; + 4E9B69651A3D74AA001157D7 /* MFTreeDocument.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFTreeDocument.m; sourceTree = ""; }; + 4E9B69671A3D8D29001157D7 /* MFTreeImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFTreeImporter.h; sourceTree = ""; }; + 4E9B69681A3E982E001157D7 /* MFNewickImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = MFNewickImporter.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 4E9B69691A3E982E001157D7 /* MFNewickImporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MFNewickImporter.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 4E9B696B1A3E9C1F001157D7 /* MFTreeReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFTreeReader.h; sourceTree = ""; }; + 4E9B696C1A3E9C1F001157D7 /* MFTreeReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFTreeReader.m; sourceTree = ""; }; + 4E9B696E1A401631001157D7 /* MFDocumentController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFDocumentController.h; sourceTree = ""; }; + 4E9B696F1A401631001157D7 /* MFDocumentController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFDocumentController.m; sourceTree = ""; }; + 4E9B697F1A4294EB001157D7 /* MFDistanceWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFDistanceWindowController.h; sourceTree = ""; }; + 4E9B69801A4294EB001157D7 /* MFDistanceWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFDistanceWindowController.m; sourceTree = ""; }; + 4E9B69811A4294EB001157D7 /* MFDistanceMatrixWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MFDistanceMatrixWindow.xib; sourceTree = ""; }; + 4E9B69841A42B31D001157D7 /* MFTreeBuilderController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFTreeBuilderController.h; sourceTree = ""; }; + 4E9B69851A42B31D001157D7 /* MFTreeBuilderController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFTreeBuilderController.m; sourceTree = ""; }; + 4E9B69861A42B31D001157D7 /* MFTreeBuilderController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MFTreeBuilderController.xib; sourceTree = ""; }; + 4EA0D7611A22F4EC0084E04C /* NSArray+IndexSetAddition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+IndexSetAddition.h"; sourceTree = ""; }; + 4EA0D7621A22F4EC0084E04C /* NSArray+IndexSetAddition.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+IndexSetAddition.m"; sourceTree = ""; }; + 4EAA0DEE199204E1007FEE79 /* MFWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFWindowController.h; sourceTree = ""; }; + 4EAA0DEF199204E1007FEE79 /* MFWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFWindowController.m; sourceTree = ""; }; + 4EAA0DF1199238A5007FEE79 /* MFSequence+MFSequenceDrawing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MFSequence+MFSequenceDrawing.h"; sourceTree = ""; }; + 4EAA0DF2199238A5007FEE79 /* MFSequence+MFSequenceDrawing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MFSequence+MFSequenceDrawing.m"; sourceTree = ""; }; + 4EAA0DF419BFF03F007FEE79 /* MFAbstractSequencesView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFAbstractSequencesView.h; sourceTree = ""; }; + 4EAA0DF519BFF03F007FEE79 /* MFAbstractSequencesView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFAbstractSequencesView.m; sourceTree = ""; }; + 4ECFCE891AA551B500245B86 /* DBPrefsWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBPrefsWindowController.h; sourceTree = ""; }; + 4ECFCE8A1AA551B500245B86 /* DBPrefsWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBPrefsWindowController.m; sourceTree = ""; }; + 4ECFCE8C1AA551F200245B86 /* MFPrefsWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = MFPrefsWindowController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 4ECFCE8D1AA551F200245B86 /* MFPrefsWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MFPrefsWindowController.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 4ECFCE8F1AA5546200245B86 /* MFPrefs.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MFPrefs.xib; sourceTree = ""; }; + 4ECFCE911AA5BED600245B86 /* MFSimpleAlignmentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFSimpleAlignmentView.h; sourceTree = ""; }; + 4ECFCE921AA5BED600245B86 /* MFSimpleAlignmentView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFSimpleAlignmentView.m; sourceTree = ""; }; + 4ECFCE941AA674C700245B86 /* MFColorManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFColorManager.h; sourceTree = ""; }; + 4ECFCE951AA674C700245B86 /* MFColorManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFColorManager.m; sourceTree = ""; }; + 4ECFCE971AAAABBB00245B86 /* MFAlignerController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFAlignerController.h; sourceTree = ""; }; + 4ECFCE981AAAABBB00245B86 /* MFAlignerController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFAlignerController.m; sourceTree = ""; }; + 4ECFCE9A1AAAABF000245B86 /* MFAligner.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MFAligner.xib; sourceTree = ""; }; + 4ECFCE9C1AAC0F5100245B86 /* MFOperationBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; name = MFOperationBuilder.h; path = Seqotron/MFOperationBuilder.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 4ECFCEA31AAD29FF00245B86 /* bin */ = {isa = PBXFileReference; lastKnownFileType = folder; path = bin; sourceTree = ""; }; + 4ECFCEA51AAD473400245B86 /* MFOperationTransalign.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; name = MFOperationTransalign.h; path = Seqotron/MFOperationTransalign.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 4ECFCEA61AAD473400245B86 /* MFOperationTransalign.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = MFOperationTransalign.m; path = Seqotron/MFOperationTransalign.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 4ECFCEAB1AAE54B300245B86 /* MFOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; name = MFOperation.h; path = Seqotron/MFOperation.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 4ECFCEAC1AAE54B300245B86 /* MFOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = MFOperation.m; path = Seqotron/MFOperation.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 4ECFCEB01AAE943C00245B86 /* MFTreeWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFTreeWriter.h; sourceTree = ""; }; + 4ECFCEB11AAE943C00245B86 /* MFTreeWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFTreeWriter.m; sourceTree = ""; }; + 4EDDA2401A00A90D009970DB /* MFSequenceUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFSequenceUtils.h; sourceTree = ""; }; + 4EDDA2411A00A90D009970DB /* MFSequenceUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFSequenceUtils.m; sourceTree = ""; }; + 4EDDA2451A0854D0009970DB /* MFSequenceImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFSequenceImporter.h; sourceTree = ""; }; + 4EDDA2461A0855A7009970DB /* MFNexusImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = MFNexusImporter.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 4EDDA2471A0855A7009970DB /* MFNexusImporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MFNexusImporter.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 4EE911C31A2EB6BD009A11D7 /* MFStockholmImporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFStockholmImporter.m; sourceTree = ""; }; + 4EE911C51A2EB6EC009A11D7 /* MFStockholmImporter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MFStockholmImporter.h; sourceTree = ""; }; + 4EE911C71A2EC4B4009A11D7 /* MFNeighborJoining.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = MFNeighborJoining.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 4EE911C81A2EC4B4009A11D7 /* MFNeighborJoining.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MFNeighborJoining.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 4EE911CB1A3133D3009A11D7 /* MFJukeCantorDistanceMatrix.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFJukeCantorDistanceMatrix.h; sourceTree = ""; }; + 4EE911CC1A3133D3009A11D7 /* MFJukeCantorDistanceMatrix.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFJukeCantorDistanceMatrix.m; sourceTree = ""; }; + 4EE911CE1A31357E009A11D7 /* MFK2PDistanceMatrix.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFK2PDistanceMatrix.h; sourceTree = ""; }; + 4EE911CF1A31357E009A11D7 /* MFK2PDistanceMatrix.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFK2PDistanceMatrix.m; sourceTree = ""; }; + 4EE911D11A3153F7009A11D7 /* MFGraphic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFGraphic.h; sourceTree = ""; }; + 4EE911D21A3153F7009A11D7 /* MFGraphic.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFGraphic.m; sourceTree = ""; }; + 4EE911D41A3156DC009A11D7 /* MFLineGraphic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = MFLineGraphic.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 4EE911D51A3156DC009A11D7 /* MFLineGraphic.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MFLineGraphic.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 4EE911D71A315E8C009A11D7 /* MFTextGraphic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFTextGraphic.h; sourceTree = ""; }; + 4EE911D81A315E8C009A11D7 /* MFTextGraphic.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFTextGraphic.m; sourceTree = ""; }; + 4EF044801A0C665D00DB9C8E /* MFMEGAImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = MFMEGAImporter.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 4EF044811A0C665D00DB9C8E /* MFMEGAImporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MFMEGAImporter.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 4EF044831A0C671B00DB9C8E /* MFGDEImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFGDEImporter.h; sourceTree = ""; }; + 4EF044841A0C671B00DB9C8E /* MFGDEImporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFGDEImporter.m; sourceTree = ""; }; + 4EF044861A0C67AB00DB9C8E /* MFNBRFImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = MFNBRFImporter.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 4EF044871A0C67AB00DB9C8E /* MFNBRFImporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MFNBRFImporter.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 4EF0448A1A0C68BB00DB9C8E /* MFPhylipImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = MFPhylipImporter.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 4EF0448B1A0C68BB00DB9C8E /* MFPhylipImporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MFPhylipImporter.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 4EF0448D1A0C853B00DB9C8E /* MFDistanceMatrix.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFDistanceMatrix.h; sourceTree = ""; }; + 4EF0448E1A0C853B00DB9C8E /* MFDistanceMatrix.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFDistanceMatrix.m; sourceTree = ""; }; + 4EF044901A0C8F4200DB9C8E /* MFNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = MFNode.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 4EF044911A0C8F4200DB9C8E /* MFNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MFNode.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 4EF044931A0C901700DB9C8E /* MFTree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFTree.h; sourceTree = ""; }; + 4EF044941A0C901700DB9C8E /* MFTree.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFTree.m; sourceTree = ""; }; + 4EF044981A11B1B900DB9C8E /* MFTreeView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFTreeView.h; sourceTree = ""; }; + 4EF044991A11B1B900DB9C8E /* MFTreeView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFTreeView.m; sourceTree = ""; }; + 4EF236101A66728000FDB4D7 /* MFExternalOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MFExternalOperation.h; path = Seqotron/MFExternalOperation.h; sourceTree = ""; }; + 4EF236111A66728000FDB4D7 /* MFExternalOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MFExternalOperation.m; path = Seqotron/MFExternalOperation.m; sourceTree = ""; }; + 4EF236131A69FF4800FDB4D7 /* MFProgressController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = MFProgressController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 4EF236141A69FF4800FDB4D7 /* MFProgressController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MFProgressController.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 4EF236151A69FF4800FDB4D7 /* MFProgressWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MFProgressWindow.xib; sourceTree = ""; }; + 4EF236191A6A1CEB00FDB4D7 /* MFOperationDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MFOperationDelegate.h; path = Seqotron/MFOperationDelegate.h; sourceTree = ""; }; + 4EF2361D1A6F598E00FDB4D7 /* MFNexusExporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = MFNexusExporter.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 4EF2361E1A6F598E00FDB4D7 /* MFNexusExporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MFNexusExporter.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 4EF236201A6F6C0800FDB4D7 /* MFNewickExporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = MFNewickExporter.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 4EF236211A6F6C0800FDB4D7 /* MFNewickExporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MFNewickExporter.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 4EF236231A6F6D9300FDB4D7 /* MFTreeExporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFTreeExporter.h; sourceTree = ""; }; + 4EF236241A6F6D9300FDB4D7 /* MFTreeExporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFTreeExporter.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 4E8922D91982277900A0DC64 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4E8922E01982277900A0DC64 /* Cocoa.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4E8922FD1982277900A0DC64 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4E8923031982277900A0DC64 /* Cocoa.framework in Frameworks */, + 4E8923021982277900A0DC64 /* XCTest.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 4E1214B619E1184400463E9B /* Interfaces */ = { + isa = PBXGroup; + children = ( + 4E8922F71982277900A0DC64 /* MainMenu.xib */, + 4ECFCE9A1AAAABF000245B86 /* MFAligner.xib */, + 4E9B69811A4294EB001157D7 /* MFDistanceMatrixWindow.xib */, + 4E8922F41982277900A0DC64 /* MFDocument.xib */, + 4ECFCE8F1AA5546200245B86 /* MFPrefs.xib */, + 4EF236151A69FF4800FDB4D7 /* MFProgressWindow.xib */, + 4E8120331B044F9B004A3F3E /* MFSearchController.xib */, + 4E9B69861A42B31D001157D7 /* MFTreeBuilderController.xib */, + 4E9B69601A3D72D7001157D7 /* MFTreeWindow.xib */, + ); + name = Interfaces; + sourceTree = ""; + }; + 4E1799181A3535A600EF60F8 /* WindowControllers */ = { + isa = PBXGroup; + children = ( + 4ECFCE891AA551B500245B86 /* DBPrefsWindowController.h */, + 4ECFCE8A1AA551B500245B86 /* DBPrefsWindowController.m */, + 4ECFCE971AAAABBB00245B86 /* MFAlignerController.h */, + 4ECFCE981AAAABBB00245B86 /* MFAlignerController.m */, + 4E9B697F1A4294EB001157D7 /* MFDistanceWindowController.h */, + 4E9B69801A4294EB001157D7 /* MFDistanceWindowController.m */, + 4ECFCE8C1AA551F200245B86 /* MFPrefsWindowController.h */, + 4ECFCE8D1AA551F200245B86 /* MFPrefsWindowController.m */, + 4EF236131A69FF4800FDB4D7 /* MFProgressController.h */, + 4EF236141A69FF4800FDB4D7 /* MFProgressController.m */, + 4E8120311B044F9B004A3F3E /* MFSearchController.h */, + 4E8120321B044F9B004A3F3E /* MFSearchController.m */, + 4E9B69841A42B31D001157D7 /* MFTreeBuilderController.h */, + 4E9B69851A42B31D001157D7 /* MFTreeBuilderController.m */, + 4E9B695E1A3D72D7001157D7 /* MFTreeWindowController.h */, + 4E9B695F1A3D72D7001157D7 /* MFTreeWindowController.m */, + 4EAA0DEE199204E1007FEE79 /* MFWindowController.h */, + 4EAA0DEF199204E1007FEE79 /* MFWindowController.m */, + ); + name = WindowControllers; + sourceTree = ""; + }; + 4E8922D31982277900A0DC64 = { + isa = PBXGroup; + children = ( + 4E8922E51982277900A0DC64 /* Seqotron */, + 4E8923061982277900A0DC64 /* SeqotronTests */, + 4E8922DE1982277900A0DC64 /* Frameworks */, + 4E8922DD1982277900A0DC64 /* Products */, + ); + sourceTree = ""; + }; + 4E8922DD1982277900A0DC64 /* Products */ = { + isa = PBXGroup; + children = ( + 4E8922DC1982277900A0DC64 /* Seqotron.app */, + 4E8923001982277900A0DC64 /* SeqotronTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 4E8922DE1982277900A0DC64 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 4E8922DF1982277900A0DC64 /* Cocoa.framework */, + 4E8923011982277900A0DC64 /* XCTest.framework */, + 4E8922E11982277900A0DC64 /* Other Frameworks */, + ); + name = Frameworks; + sourceTree = ""; + }; + 4E8922E11982277900A0DC64 /* Other Frameworks */ = { + isa = PBXGroup; + children = ( + 4E8922E21982277900A0DC64 /* AppKit.framework */, + 4E8922E31982277900A0DC64 /* CoreData.framework */, + 4E8922E41982277900A0DC64 /* Foundation.framework */, + ); + name = "Other Frameworks"; + sourceTree = ""; + }; + 4E8922E51982277900A0DC64 /* Seqotron */ = { + isa = PBXGroup; + children = ( + 4ECFCEA31AAD29FF00245B86 /* bin */, + 4E28172119F0C93400860621 /* Coloring */, + 4E9B69631A3D72F8001157D7 /* Documents */, + 4E1214C619EFA52200463E9B /* GeneticCodeTables */, + 4E8922FA1982277900A0DC64 /* Images.xcassets */, + 4EF044891A0C684700DB9C8E /* Importers */, + 4E1214B619E1184400463E9B /* Interfaces */, + 4EAA0DF419BFF03F007FEE79 /* MFAbstractSequencesView.h */, + 4EAA0DF519BFF03F007FEE79 /* MFAbstractSequencesView.m */, + 4E892318198227C200A0DC64 /* MFAppDelegate.h */, + 4E892319198227C200A0DC64 /* MFAppDelegate.m */, + 4ECFCE941AA674C700245B86 /* MFColorManager.h */, + 4ECFCE951AA674C700245B86 /* MFColorManager.m */, + 4E89231A198227C200A0DC64 /* MFDataType.h */, + 4E89231B198227C200A0DC64 /* MFDataType.m */, + 4E89231C198227C200A0DC64 /* MFDefines.h */, + 4E9B696E1A401631001157D7 /* MFDocumentController.h */, + 4E9B696F1A401631001157D7 /* MFDocumentController.m */, + 4E92745C1A0C429200DA48F8 /* MFFileReader.h */, + 4E92745D1A0C429200DA48F8 /* MFFileReader.m */, + 4E89231D198227C200A0DC64 /* MFNamesView.h */, + 4E89231E198227C200A0DC64 /* MFNamesView.m */, + 4EF2361D1A6F598E00FDB4D7 /* MFNexusExporter.h */, + 4EF2361E1A6F598E00FDB4D7 /* MFNexusExporter.m */, + 4E89231F198227C200A0DC64 /* MFNucleotide.h */, + 4E892320198227C200A0DC64 /* MFNucleotide.m */, + 4E892323198227C200A0DC64 /* MFProtein.h */, + 4E892324198227C200A0DC64 /* MFProtein.m */, + 4E9274621A0C4DE400DA48F8 /* MFReaderCluster.h */, + 4E9274631A0C4DE400DA48F8 /* MFReaderCluster.m */, + 4E89233E198227E800A0DC64 /* MFRulerView.h */, + 4E89233F198227E800A0DC64 /* MFRulerView.m */, + 4E892325198227C200A0DC64 /* MFSequence.h */, + 4E892326198227C200A0DC64 /* MFSequence.m */, + 4EAA0DF1199238A5007FEE79 /* MFSequence+MFSequenceDrawing.h */, + 4EAA0DF2199238A5007FEE79 /* MFSequence+MFSequenceDrawing.m */, + 4EDDA2451A0854D0009970DB /* MFSequenceImporter.h */, + 4E892327198227C200A0DC64 /* MFSequenceReader.h */, + 4E892328198227C200A0DC64 /* MFSequenceReader.m */, + 4E892329198227C200A0DC64 /* MFSequenceSet.h */, + 4E89232A198227C200A0DC64 /* MFSequenceSet.m */, + 4E89232B198227C200A0DC64 /* MFSequencesView.h */, + 4E89232C198227C200A0DC64 /* MFSequencesView.m */, + 4EDDA2401A00A90D009970DB /* MFSequenceUtils.h */, + 4EDDA2411A00A90D009970DB /* MFSequenceUtils.m */, + 4E89232D198227C200A0DC64 /* MFSequenceWriter.h */, + 4E89232E198227C200A0DC64 /* MFSequenceWriter.m */, + 4ECFCE911AA5BED600245B86 /* MFSimpleAlignmentView.h */, + 4ECFCE921AA5BED600245B86 /* MFSimpleAlignmentView.m */, + 4E892340198227E800A0DC64 /* MFString.h */, + 4E892341198227E800A0DC64 /* MFString.m */, + 4E9274651A0C525F00DA48F8 /* MFStringReader.h */, + 4E9274661A0C525F00DA48F8 /* MFStringReader.m */, + 4E89232F198227C200A0DC64 /* MFSyncronizedScrollView.h */, + 4E892330198227C200A0DC64 /* MFSyncronizedScrollView.m */, + 4EA0D7611A22F4EC0084E04C /* NSArray+IndexSetAddition.h */, + 4EA0D7621A22F4EC0084E04C /* NSArray+IndexSetAddition.m */, + 4E63B71F1AC2146A00643129 /* NSObject+TDBindings.h */, + 4E63B7201AC2146A00643129 /* NSObject+TDBindings.m */, + 4ECFCEAE1AAE704800245B86 /* Operations */, + 4EE911C61A2EC3CF009A11D7 /* Phylogenetics */, + 4E8922E61982277900A0DC64 /* Supporting Files */, + 4E1799181A3535A600EF60F8 /* WindowControllers */, + ); + path = Seqotron; + sourceTree = ""; + }; + 4E8922E61982277900A0DC64 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 4E8922E71982277900A0DC64 /* Seqotron-Info.plist */, + 4E8922E81982277900A0DC64 /* InfoPlist.strings */, + 4E8922EB1982277900A0DC64 /* main.m */, + 4E8922ED1982277900A0DC64 /* Seqotron-Prefix.pch */, + 4E8922EE1982277900A0DC64 /* Credits.rtf */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 4E8923061982277900A0DC64 /* SeqotronTests */ = { + isa = PBXGroup; + children = ( + 4E89230C1982277900A0DC64 /* SeqotronTests.m */, + 4E8923071982277900A0DC64 /* Supporting Files */, + ); + path = SeqotronTests; + sourceTree = ""; + }; + 4E8923071982277900A0DC64 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 4E8923081982277900A0DC64 /* SeqotronTests-Info.plist */, + 4E8923091982277900A0DC64 /* InfoPlist.strings */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 4E9B69631A3D72F8001157D7 /* Documents */ = { + isa = PBXGroup; + children = ( + 4E8922F11982277900A0DC64 /* MFDocument.h */, + 4E8922F21982277900A0DC64 /* MFDocument.m */, + 4E9B69641A3D74AA001157D7 /* MFTreeDocument.h */, + 4E9B69651A3D74AA001157D7 /* MFTreeDocument.m */, + ); + name = Documents; + sourceTree = ""; + }; + 4ECFCEAE1AAE704800245B86 /* Operations */ = { + isa = PBXGroup; + children = ( + 4ECFCE9C1AAC0F5100245B86 /* MFOperationBuilder.h */, + 4EF236191A6A1CEB00FDB4D7 /* MFOperationDelegate.h */, + 4E9B69561A37F637001157D7 /* MFDistanceMatrixOperation.h */, + 4E9B69571A37F637001157D7 /* MFDistanceMatrixOperation.m */, + 4EF236101A66728000FDB4D7 /* MFExternalOperation.h */, + 4EF236111A66728000FDB4D7 /* MFExternalOperation.m */, + 4ECFCEAB1AAE54B300245B86 /* MFOperation.h */, + 4ECFCEAC1AAE54B300245B86 /* MFOperation.m */, + 4ECFCEA51AAD473400245B86 /* MFOperationTransalign.h */, + 4ECFCEA61AAD473400245B86 /* MFOperationTransalign.m */, + ); + name = Operations; + path = ..; + sourceTree = ""; + }; + 4EE911C61A2EC3CF009A11D7 /* Phylogenetics */ = { + isa = PBXGroup; + children = ( + 4EF0448D1A0C853B00DB9C8E /* MFDistanceMatrix.h */, + 4EF0448E1A0C853B00DB9C8E /* MFDistanceMatrix.m */, + 4EE911D11A3153F7009A11D7 /* MFGraphic.h */, + 4EE911D21A3153F7009A11D7 /* MFGraphic.m */, + 4EE911CB1A3133D3009A11D7 /* MFJukeCantorDistanceMatrix.h */, + 4EE911CC1A3133D3009A11D7 /* MFJukeCantorDistanceMatrix.m */, + 4EE911CE1A31357E009A11D7 /* MFK2PDistanceMatrix.h */, + 4EE911CF1A31357E009A11D7 /* MFK2PDistanceMatrix.m */, + 4E8120361B057BE5004A3F3E /* MFK83DistanceMatrix.h */, + 4E8120371B057BE5004A3F3E /* MFK83DistanceMatrix.m */, + 4EE911D41A3156DC009A11D7 /* MFLineGraphic.h */, + 4EE911D51A3156DC009A11D7 /* MFLineGraphic.m */, + 4EE911C71A2EC4B4009A11D7 /* MFNeighborJoining.h */, + 4EE911C81A2EC4B4009A11D7 /* MFNeighborJoining.m */, + 4EF236201A6F6C0800FDB4D7 /* MFNewickExporter.h */, + 4EF236211A6F6C0800FDB4D7 /* MFNewickExporter.m */, + 4EF044901A0C8F4200DB9C8E /* MFNode.h */, + 4EF044911A0C8F4200DB9C8E /* MFNode.m */, + 4E62FC671A93107500CFD8D0 /* MFScaleBar.h */, + 4E62FC681A93107600CFD8D0 /* MFScaleBar.m */, + 4EE911D71A315E8C009A11D7 /* MFTextGraphic.h */, + 4EE911D81A315E8C009A11D7 /* MFTextGraphic.m */, + 4EF044931A0C901700DB9C8E /* MFTree.h */, + 4EF044941A0C901700DB9C8E /* MFTree.m */, + 4EF236231A6F6D9300FDB4D7 /* MFTreeExporter.h */, + 4EF236241A6F6D9300FDB4D7 /* MFTreeExporter.m */, + 4E9B69671A3D8D29001157D7 /* MFTreeImporter.h */, + 4E9B696B1A3E9C1F001157D7 /* MFTreeReader.h */, + 4E9B696C1A3E9C1F001157D7 /* MFTreeReader.m */, + 4EF044981A11B1B900DB9C8E /* MFTreeView.h */, + 4EF044991A11B1B900DB9C8E /* MFTreeView.m */, + 4ECFCEB01AAE943C00245B86 /* MFTreeWriter.h */, + 4ECFCEB11AAE943C00245B86 /* MFTreeWriter.m */, + ); + name = Phylogenetics; + sourceTree = ""; + }; + 4EF044891A0C684700DB9C8E /* Importers */ = { + isa = PBXGroup; + children = ( + 4E9274681A0C64B000DA48F8 /* MFClustalImporter.h */, + 4E9274691A0C64B000DA48F8 /* MFClustalImporter.m */, + 4E92745F1A0C4A7300DA48F8 /* MFFASTAImporter.h */, + 4E9274601A0C4A7300DA48F8 /* MFFASTAImporter.m */, + 4EF044831A0C671B00DB9C8E /* MFGDEImporter.h */, + 4EF044841A0C671B00DB9C8E /* MFGDEImporter.m */, + 4EF044801A0C665D00DB9C8E /* MFMEGAImporter.h */, + 4EF044811A0C665D00DB9C8E /* MFMEGAImporter.m */, + 4EF044861A0C67AB00DB9C8E /* MFNBRFImporter.h */, + 4EF044871A0C67AB00DB9C8E /* MFNBRFImporter.m */, + 4E9B69681A3E982E001157D7 /* MFNewickImporter.h */, + 4E9B69691A3E982E001157D7 /* MFNewickImporter.m */, + 4EDDA2461A0855A7009970DB /* MFNexusImporter.h */, + 4EDDA2471A0855A7009970DB /* MFNexusImporter.m */, + 4EF0448A1A0C68BB00DB9C8E /* MFPhylipImporter.h */, + 4EF0448B1A0C68BB00DB9C8E /* MFPhylipImporter.m */, + 4EE911C51A2EB6EC009A11D7 /* MFStockholmImporter.h */, + 4EE911C31A2EB6BD009A11D7 /* MFStockholmImporter.m */, + ); + name = Importers; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 4E8922DB1982277900A0DC64 /* Seqotron */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4E8923101982277900A0DC64 /* Build configuration list for PBXNativeTarget "Seqotron" */; + buildPhases = ( + 4E8922D81982277900A0DC64 /* Sources */, + 4E8922D91982277900A0DC64 /* Frameworks */, + 4E8922DA1982277900A0DC64 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Seqotron; + productName = Seqotron; + productReference = 4E8922DC1982277900A0DC64 /* Seqotron.app */; + productType = "com.apple.product-type.application"; + }; + 4E8922FF1982277900A0DC64 /* SeqotronTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4E8923131982277900A0DC64 /* Build configuration list for PBXNativeTarget "SeqotronTests" */; + buildPhases = ( + 4E8922FC1982277900A0DC64 /* Sources */, + 4E8922FD1982277900A0DC64 /* Frameworks */, + 4E8922FE1982277900A0DC64 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 4E8923051982277900A0DC64 /* PBXTargetDependency */, + ); + name = SeqotronTests; + productName = SeqotronTests; + productReference = 4E8923001982277900A0DC64 /* SeqotronTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 4E8922D41982277900A0DC64 /* Project object */ = { + isa = PBXProject; + attributes = { + CLASSPREFIX = MF; + LastUpgradeCheck = 0510; + ORGANIZATIONNAME = "University of Sydney"; + TargetAttributes = { + 4E8922FF1982277900A0DC64 = { + TestTargetID = 4E8922DB1982277900A0DC64; + }; + }; + }; + buildConfigurationList = 4E8922D71982277900A0DC64 /* Build configuration list for PBXProject "Seqotron" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 4E8922D31982277900A0DC64; + productRefGroup = 4E8922DD1982277900A0DC64 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 4E8922DB1982277900A0DC64 /* Seqotron */, + 4E8922FF1982277900A0DC64 /* SeqotronTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 4E8922DA1982277900A0DC64 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4E28172219F0C93400860621 /* Coloring in Resources */, + 4E8922EA1982277900A0DC64 /* InfoPlist.strings in Resources */, + 4EF236171A69FF4800FDB4D7 /* MFProgressWindow.xib in Resources */, + 4ECFCE9B1AAAABF000245B86 /* MFAligner.xib in Resources */, + 4E8120351B044F9B004A3F3E /* MFSearchController.xib in Resources */, + 4E8922FB1982277900A0DC64 /* Images.xcassets in Resources */, + 4E8922F61982277900A0DC64 /* MFDocument.xib in Resources */, + 4E8922F01982277900A0DC64 /* Credits.rtf in Resources */, + 4E9B69831A4294EB001157D7 /* MFDistanceMatrixWindow.xib in Resources */, + 4E8922F91982277900A0DC64 /* MainMenu.xib in Resources */, + 4ECFCE901AA5546200245B86 /* MFPrefs.xib in Resources */, + 4E9B69881A42B31D001157D7 /* MFTreeBuilderController.xib in Resources */, + 4ECFCEA41AAD29FF00245B86 /* bin in Resources */, + 4E9B69621A3D72D7001157D7 /* MFTreeWindow.xib in Resources */, + 4E1214C719EFA52200463E9B /* GeneticCodeTables in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4E8922FE1982277900A0DC64 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4E89230B1982277900A0DC64 /* InfoPlist.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 4E8922D81982277900A0DC64 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4EE911D01A31357E009A11D7 /* MFK2PDistanceMatrix.m in Sources */, + 4EF236161A69FF4800FDB4D7 /* MFProgressController.m in Sources */, + 4ECFCE961AA674C700245B86 /* MFColorManager.m in Sources */, + 4E892342198227E800A0DC64 /* MFRulerView.m in Sources */, + 4EA0D7631A22F4EC0084E04C /* NSArray+IndexSetAddition.m in Sources */, + 4ECFCEAD1AAE54B300245B86 /* MFOperation.m in Sources */, + 4E9B696D1A3E9C1F001157D7 /* MFTreeReader.m in Sources */, + 4EAA0DF3199238A5007FEE79 /* MFSequence+MFSequenceDrawing.m in Sources */, + 4ECFCEB21AAE943C00245B86 /* MFTreeWriter.m in Sources */, + 4EF044951A0C901700DB9C8E /* MFTree.m in Sources */, + 4EF236121A66728000FDB4D7 /* MFExternalOperation.m in Sources */, + 4E9B69871A42B31D001157D7 /* MFTreeBuilderController.m in Sources */, + 4E8922EC1982277900A0DC64 /* main.m in Sources */, + 4EAA0DF0199204E1007FEE79 /* MFWindowController.m in Sources */, + 4EE911C91A2EC4B4009A11D7 /* MFNeighborJoining.m in Sources */, + 4ECFCE8E1AA551F200245B86 /* MFPrefsWindowController.m in Sources */, + 4E892333198227C200A0DC64 /* MFDataType.m in Sources */, + 4E9B69821A4294EB001157D7 /* MFDistanceWindowController.m in Sources */, + 4E89233D198227C200A0DC64 /* MFSyncronizedScrollView.m in Sources */, + 4E892343198227E800A0DC64 /* MFString.m in Sources */, + 4E9274611A0C4A7300DA48F8 /* MFFASTAImporter.m in Sources */, + 4E92746A1A0C64B000DA48F8 /* MFClustalImporter.m in Sources */, + 4EE911D91A315E8C009A11D7 /* MFTextGraphic.m in Sources */, + 4E892338198227C200A0DC64 /* MFSequence.m in Sources */, + 4E89233C198227C200A0DC64 /* MFSequenceWriter.m in Sources */, + 4EE911C41A2EB6BD009A11D7 /* MFStockholmImporter.m in Sources */, + 4EAA0DF619BFF03F007FEE79 /* MFAbstractSequencesView.m in Sources */, + 4EE911D61A3156DC009A11D7 /* MFLineGraphic.m in Sources */, + 4EDDA2481A0855A7009970DB /* MFNexusImporter.m in Sources */, + 4EF0448F1A0C853B00DB9C8E /* MFDistanceMatrix.m in Sources */, + 4E892334198227C200A0DC64 /* MFNamesView.m in Sources */, + 4EF236221A6F6C0800FDB4D7 /* MFNewickExporter.m in Sources */, + 4EF044921A0C8F4200DB9C8E /* MFNode.m in Sources */, + 4ECFCE8B1AA551B500245B86 /* DBPrefsWindowController.m in Sources */, + 4EF044851A0C671B00DB9C8E /* MFGDEImporter.m in Sources */, + 4EE911D31A3153F7009A11D7 /* MFGraphic.m in Sources */, + 4E9B69701A401631001157D7 /* MFDocumentController.m in Sources */, + 4E8120381B057BE5004A3F3E /* MFK83DistanceMatrix.m in Sources */, + 4E892337198227C200A0DC64 /* MFProtein.m in Sources */, + 4E892332198227C200A0DC64 /* MFAppDelegate.m in Sources */, + 4ECFCE931AA5BED600245B86 /* MFSimpleAlignmentView.m in Sources */, + 4E9B69661A3D74AA001157D7 /* MFTreeDocument.m in Sources */, + 4E89233A198227C200A0DC64 /* MFSequenceSet.m in Sources */, + 4E9B69581A37F637001157D7 /* MFDistanceMatrixOperation.m in Sources */, + 4EF236251A6F6D9300FDB4D7 /* MFTreeExporter.m in Sources */, + 4ECFCEA71AAD473400245B86 /* MFOperationTransalign.m in Sources */, + 4E92745E1A0C429200DA48F8 /* MFFileReader.m in Sources */, + 4ECFCE991AAAABBB00245B86 /* MFAlignerController.m in Sources */, + 4E8922F31982277900A0DC64 /* MFDocument.m in Sources */, + 4E9B69611A3D72D7001157D7 /* MFTreeWindowController.m in Sources */, + 4E89233B198227C200A0DC64 /* MFSequencesView.m in Sources */, + 4EF0449A1A11B1B900DB9C8E /* MFTreeView.m in Sources */, + 4E63B7211AC2146A00643129 /* NSObject+TDBindings.m in Sources */, + 4EF044881A0C67AB00DB9C8E /* MFNBRFImporter.m in Sources */, + 4E62FC691A93107600CFD8D0 /* MFScaleBar.m in Sources */, + 4EF044821A0C665D00DB9C8E /* MFMEGAImporter.m in Sources */, + 4EF0448C1A0C68BB00DB9C8E /* MFPhylipImporter.m in Sources */, + 4EE911CD1A3133D3009A11D7 /* MFJukeCantorDistanceMatrix.m in Sources */, + 4E8120341B044F9B004A3F3E /* MFSearchController.m in Sources */, + 4E9B696A1A3E982E001157D7 /* MFNewickImporter.m in Sources */, + 4E892339198227C200A0DC64 /* MFSequenceReader.m in Sources */, + 4E9274641A0C4DE400DA48F8 /* MFReaderCluster.m in Sources */, + 4E9274671A0C525F00DA48F8 /* MFStringReader.m in Sources */, + 4EF2361F1A6F598E00FDB4D7 /* MFNexusExporter.m in Sources */, + 4E892335198227C200A0DC64 /* MFNucleotide.m in Sources */, + 4EDDA2421A00A90D009970DB /* MFSequenceUtils.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4E8922FC1982277900A0DC64 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4E89230D1982277900A0DC64 /* SeqotronTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 4E8923051982277900A0DC64 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 4E8922DB1982277900A0DC64 /* Seqotron */; + targetProxy = 4E8923041982277900A0DC64 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 4E8922E81982277900A0DC64 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 4E8922E91982277900A0DC64 /* en */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; + 4E8922EE1982277900A0DC64 /* Credits.rtf */ = { + isa = PBXVariantGroup; + children = ( + 4E8922EF1982277900A0DC64 /* en */, + ); + name = Credits.rtf; + sourceTree = ""; + }; + 4E8922F41982277900A0DC64 /* MFDocument.xib */ = { + isa = PBXVariantGroup; + children = ( + 4E8922F51982277900A0DC64 /* Base */, + ); + name = MFDocument.xib; + sourceTree = ""; + }; + 4E8922F71982277900A0DC64 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 4E8922F81982277900A0DC64 /* Base */, + ); + name = MainMenu.xib; + sourceTree = ""; + }; + 4E8923091982277900A0DC64 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 4E89230A1982277900A0DC64 /* en */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 4E89230E1982277900A0DC64 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.7; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx10.9; + }; + name = Debug; + }; + 4E89230F1982277900A0DC64 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.7; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx10.9; + }; + name = Release; + }; + 4E8923111982277900A0DC64 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_OBJC_ARC = NO; + COMBINE_HIDPI_IMAGES = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "Seqotron/Seqotron-Prefix.pch"; + INFOPLIST_FILE = "Seqotron/Seqotron-Info.plist"; + MACOSX_DEPLOYMENT_TARGET = 10.9; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 4E8923121982277900A0DC64 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_OBJC_ARC = NO; + COMBINE_HIDPI_IMAGES = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "Seqotron/Seqotron-Prefix.pch"; + INFOPLIST_FILE = "Seqotron/Seqotron-Info.plist"; + MACOSX_DEPLOYMENT_TARGET = 10.9; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; + 4E8923141982277900A0DC64 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Seqotron.app/Contents/MacOS/Seqotron"; + COMBINE_HIDPI_IMAGES = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "Seqotron/Seqotron-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = "SeqotronTests/SeqotronTests-Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUNDLE_LOADER)"; + WRAPPER_EXTENSION = xctest; + }; + name = Debug; + }; + 4E8923151982277900A0DC64 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Seqotron.app/Contents/MacOS/Seqotron"; + COMBINE_HIDPI_IMAGES = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "Seqotron/Seqotron-Prefix.pch"; + INFOPLIST_FILE = "SeqotronTests/SeqotronTests-Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUNDLE_LOADER)"; + WRAPPER_EXTENSION = xctest; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 4E8922D71982277900A0DC64 /* Build configuration list for PBXProject "Seqotron" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4E89230E1982277900A0DC64 /* Debug */, + 4E89230F1982277900A0DC64 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4E8923101982277900A0DC64 /* Build configuration list for PBXNativeTarget "Seqotron" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4E8923111982277900A0DC64 /* Debug */, + 4E8923121982277900A0DC64 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4E8923131982277900A0DC64 /* Build configuration list for PBXNativeTarget "SeqotronTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4E8923141982277900A0DC64 /* Debug */, + 4E8923151982277900A0DC64 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 4E8922D41982277900A0DC64 /* Project object */; +} diff --git a/Seqotron.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Seqotron.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100755 index 0000000..db1fcc5 --- /dev/null +++ b/Seqotron.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Seqotron.xcodeproj/project.xcworkspace/xcshareddata/Seqotron.xccheckout b/Seqotron.xcodeproj/project.xcworkspace/xcshareddata/Seqotron.xccheckout new file mode 100644 index 0000000..90a9bb9 --- /dev/null +++ b/Seqotron.xcodeproj/project.xcworkspace/xcshareddata/Seqotron.xccheckout @@ -0,0 +1,41 @@ + + + + + IDESourceControlProjectFavoriteDictionaryKey + + IDESourceControlProjectIdentifier + EE4CDE95-FA7C-4575-BE0D-F86C6A106D74 + IDESourceControlProjectName + project + IDESourceControlProjectOriginsDictionary + + 7DEF6595D8273C7A70F1ACB151468426C18DDA8A + https://bitbucket.org/4ment/Seqotron + + IDESourceControlProjectPath + Seqotron.xcodeproj/project.xcworkspace + IDESourceControlProjectRelativeInstallPathDictionary + + 7DEF6595D8273C7A70F1ACB151468426C18DDA8A + ../.. + + IDESourceControlProjectURL + https://bitbucket.org/4ment/Seqotron + IDESourceControlProjectVersion + 111 + IDESourceControlProjectWCCIdentifier + 7DEF6595D8273C7A70F1ACB151468426C18DDA8A + IDESourceControlProjectWCConfigurations + + + IDESourceControlRepositoryExtensionIdentifierKey + public.vcs.git + IDESourceControlWCCIdentifierKey + 7DEF6595D8273C7A70F1ACB151468426C18DDA8A + IDESourceControlWCCName + Seqotron + + + + diff --git a/Seqotron/Base.lproj/MFDocument.xib b/Seqotron/Base.lproj/MFDocument.xib new file mode 100755 index 0000000..07a2dcf --- /dev/null +++ b/Seqotron/Base.lproj/MFDocument.xib @@ -0,0 +1,337 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + %{value1}@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FASTA (*.fasta) + Nexus (*.nex) + Phylip (*.phy) + Clustal (*.aln) + MEGA (*.meg) + GDE (*.gde) + NBRF (*.nbrf) + Stockholm (*.stk) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Seqotron/Base.lproj/MainMenu.xib b/Seqotron/Base.lproj/MainMenu.xib new file mode 100755 index 0000000..a275134 --- /dev/null +++ b/Seqotron/Base.lproj/MainMenu.xib @@ -0,0 +1,602 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +CA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Seqotron/Coloring/Amino Acid/color-amino-acid.plist b/Seqotron/Coloring/Amino Acid/color-amino-acid.plist new file mode 100755 index 0000000..af5a514 --- /dev/null +++ b/Seqotron/Coloring/Amino Acid/color-amino-acid.plist @@ -0,0 +1,149 @@ + + + + + Description + Standard + Foreground + + - + + 0 + 0 + 0 + + * + + 0.8 + 0.8 + 0.8 + + ? + + 0.5 + 0.5 + 0.5 + + A + + 0.8 + 1 + 0 + + R + + 0 + 0 + 1 + + N + + 0.8 + 0 + 1 + + D + + 1 + 0 + 0 + + C + + 1 + 1 + 0 + + Q + + 1 + 0 + 0.8 + + E + + 1 + 0 + 0.4 + + G + + 1 + 0.6 + 0 + + H + + 0 + 0.4 + 1 + + I + + 0.4 + 1 + 0 + + L + + 0.2 + 1 + 0 + + K + + 0.4 + 0 + 1 + + M + + 0 + 1 + 0 + + F + + 0 + 1 + 0.4 + + P + + 1 + 0.8 + 0 + + S + + 1 + 0.2 + 0 + + T + + 1 + 0.4 + 0 + + W + + 0 + 0.8 + 1 + + Y + + 0 + 1 + 0.8 + + V + + 0.6 + 1 + 0 + + + + diff --git a/Seqotron/Coloring/Amino Acid/monochrome-amino-acid.plist b/Seqotron/Coloring/Amino Acid/monochrome-amino-acid.plist new file mode 100755 index 0000000..85ef880 --- /dev/null +++ b/Seqotron/Coloring/Amino Acid/monochrome-amino-acid.plist @@ -0,0 +1,149 @@ + + + + + Description + Monochrome + Foreground + + - + + 0 + 0 + 0 + + * + + 0.8 + 0.8 + 0.8 + + ? + + 0.5 + 0.5 + 0.5 + + A + + 0 + 0 + 0 + + R + + 0 + 0 + 0 + + N + + 0 + 0 + 0 + + D + + 0 + 0 + 0 + + C + + 0 + 0 + 0 + + Q + + 0 + 0 + 0 + + E + + 0 + 0 + 0 + + G + + 0 + 0 + 0 + + H + + 0 + 0 + 0 + + I + + 0 + 0 + 0 + + L + + 0 + 0 + 0 + + K + + 0 + 0 + 0 + + M + + 0 + 0 + 0 + + F + + 0 + 0 + 0 + + P + + 0 + 0 + 0 + + S + + 0 + 0 + 0 + + T + + 0 + 0 + 0 + + W + + 0 + 0 + 0 + + Y + + 0 + 0 + 0 + + V + + 0 + 0 + 0 + + + + diff --git a/Seqotron/Coloring/Nucleotide/black-nucleotide.plist b/Seqotron/Coloring/Nucleotide/black-nucleotide.plist new file mode 100755 index 0000000..8565986 --- /dev/null +++ b/Seqotron/Coloring/Nucleotide/black-nucleotide.plist @@ -0,0 +1,53 @@ + + + + + Description + Monochrome + Foreground + + ? + + 0.5 + 0.5 + 0.5 + + A + + 0 + 0 + 0 + + C + + 0 + 0 + 0 + + G + + 0 + 0 + 0 + + T + + 0 + 0 + 0 + + U + + 0 + 0 + 0 + + - + + 0 + 0 + 0 + + + + diff --git a/Seqotron/Coloring/Nucleotide/clustal-nucleotide.plist b/Seqotron/Coloring/Nucleotide/clustal-nucleotide.plist new file mode 100755 index 0000000..db03ec2 --- /dev/null +++ b/Seqotron/Coloring/Nucleotide/clustal-nucleotide.plist @@ -0,0 +1,98 @@ + + + + + Description + Clustal + Foreground + + ? + + 0 + 0 + 0 + + A + + 0 + 0 + 0 + + C + + 0 + 0 + 0 + + G + + 0 + 0 + 0 + + T + + 0 + 0 + 0 + + U + + 0 + 0 + 0 + + - + + 0 + 0 + 0 + + + Background + + ? + + 0.5 + 0.5 + 0.5 + + A + + 1 + 0 + 0 + + C + + 0 + 0 + 1 + + G + + 1 + 0.6 + 0 + + T + + 0 + 0.7 + 0 + + U + + 0 + 0.7 + 0 + + - + + 1 + 1 + 1 + + + + diff --git a/Seqotron/Coloring/Nucleotide/color-nucleotide.plist b/Seqotron/Coloring/Nucleotide/color-nucleotide.plist new file mode 100755 index 0000000..96dba73 --- /dev/null +++ b/Seqotron/Coloring/Nucleotide/color-nucleotide.plist @@ -0,0 +1,53 @@ + + + + + Description + Color + Foreground + + ? + + 0.5 + 0.5 + 0.5 + + - + + 0 + 0 + 0 + + A + + 0 + 1 + 0 + + C + + 1 + 0 + 0 + + G + + 0 + 0 + 1 + + T + + 0 + 0 + 0 + + U + + 0 + 0 + 0 + + + + diff --git a/Seqotron/Coloring/Nucleotide/geneious.plist b/Seqotron/Coloring/Nucleotide/geneious.plist new file mode 100755 index 0000000..98e6ef9 --- /dev/null +++ b/Seqotron/Coloring/Nucleotide/geneious.plist @@ -0,0 +1,112 @@ + + + + + Background + + - + + 1 + 1 + 1 + 1 + + ? + + 0.5 + 0.5 + 0.5 + 1 + + A + + 0.9098039865493774 + 0.4274510145187378 + 0.5098039507865906 + 1 + + C + + 0.4980392456054688 + 0.501960813999176 + 0.9137255549430847 + 1 + + G + + 0.9411765336990356 + 0.9568628072738647 + 0.02745098248124123 + 1 + + T + + 0.207843154668808 + 0.9725490808486938 + 0.01960784383118153 + 1 + + U + + 0.207843154668808 + 0.9725490808486938 + 0.01960784383118153 + 1 + + + Description + Geneious + Foreground + + - + + 0 + 0 + 0 + 1 + + ? + + 0 + 0 + 0 + 1 + + A + + 0 + 0 + 0 + 1 + + C + + 0 + 0 + 0 + 1 + + G + + 0 + 0 + 0 + 1 + + T + + 0 + 0 + 0 + 1 + + U + + 0 + 0 + 0 + 1 + + + + diff --git a/Seqotron/DBPrefsWindowController.h b/Seqotron/DBPrefsWindowController.h new file mode 100755 index 0000000..b892ca0 --- /dev/null +++ b/Seqotron/DBPrefsWindowController.h @@ -0,0 +1,44 @@ +// +// DBPrefsWindowController.h +// +// Created by Dave Batton +// http://www.Mere-Mortal-Software.com/blog/ +// +// Updated by David Keegan +// https://github.com/kgn/DBPrefsWindowController +// +// Copyright 2007. Some rights reserved. +// This work is licensed under a Creative Commons license: +// http://creativecommons.org/licenses/by/3.0/ + +#import + +@interface DBPrefsWindowController : NSWindowController + + +@property (nonatomic, strong) NSMutableArray *toolbarIdentifiers; +@property (nonatomic, strong) NSMutableDictionary *toolbarViews; +@property (nonatomic, strong) NSMutableDictionary *toolbarItems; +@property (nonatomic, strong) NSViewAnimation *viewAnimation; +@property (nonatomic, strong) NSView *contentSubview; + +@property (nonatomic) BOOL crossFade; +@property (nonatomic) BOOL shiftSlowsAnimation; + ++ (DBPrefsWindowController *)sharedPrefsWindowController; ++ (NSString *)nibName; + +- (void)setupToolbar; +- (void)addFlexibleSpacer; +- (void)addView:(NSView *)view label:(NSString *)label; +- (void)addView:(NSView *)view label:(NSString *)label image:(NSImage *)image; + +- (void)toggleActivePreferenceView:(NSToolbarItem *)toolbarItem; +- (void)displayViewForIdentifier:(NSString *)identifier animate:(BOOL)animate; +- (void)crossFadeView:(NSView *)oldView withView:(NSView *)newView; +- (NSRect)frameForView:(NSView *)view; + +// select both the view & toolbar item for the given identifier +- (void)loadViewForIdentifier:(NSString *)identifier animate:(BOOL)animate; + +@end diff --git a/Seqotron/DBPrefsWindowController.m b/Seqotron/DBPrefsWindowController.m new file mode 100755 index 0000000..610d529 --- /dev/null +++ b/Seqotron/DBPrefsWindowController.m @@ -0,0 +1,301 @@ +// +// DBPrefsWindowController.m +// + +#import "DBPrefsWindowController.h" + +@implementation DBPrefsWindowController + +#pragma mark - +#pragma mark Class Methods + ++ (DBPrefsWindowController *)sharedPrefsWindowController{ + static DBPrefsWindowController *_sharedPrefsWindowController = nil; + if(!_sharedPrefsWindowController){ + _sharedPrefsWindowController = [[self alloc] initWithWindowNibName:[self nibName]]; + } + return _sharedPrefsWindowController; +} + +// Subclasses can override this to use a nib with a different name. ++ (NSString *)nibName{ + return @"Preferences"; +} + + +#pragma mark - +#pragma mark Setup & Teardown + +- (id)initWithWindow:(NSWindow *)window{ + if((self = [super initWithWindow:nil])){ + // Set up an array and some dictionaries to keep track + // of the views we'll be displaying. + self.toolbarIdentifiers = [[[NSMutableArray alloc] init]autorelease]; + self.toolbarViews = [[[NSMutableDictionary alloc] init]autorelease]; + self.toolbarItems = [[[NSMutableDictionary alloc] init]autorelease]; + + // Set up an NSViewAnimation to animate the transitions. + self.viewAnimation = [[[NSViewAnimation alloc] init]autorelease]; + [self.viewAnimation setAnimationBlockingMode:NSAnimationNonblocking]; + [self.viewAnimation setAnimationCurve:NSAnimationEaseInOut]; + [self.viewAnimation setDelegate:(id)self]; + + self.crossFade = YES; + self.shiftSlowsAnimation = YES; + } + return self; +} + + +- (void)windowDidLoad{ + // Create a new window to display the preference views. + // If the developer attached a window to this controller + // in Interface Builder, it gets replaced with this one. + NSWindow *window = + [[NSWindow alloc] initWithContentRect:NSMakeRect(0,0,1000,1000) + styleMask:(NSTitledWindowMask | + NSClosableWindowMask | + NSMiniaturizableWindowMask) + backing:NSBackingStoreBuffered + defer:YES]; + [self setWindow:window]; + self.contentSubview = [[[NSView alloc] initWithFrame:[[[self window] contentView] frame]]autorelease]; + [self.contentSubview setAutoresizingMask:(NSViewMinYMargin | NSViewWidthSizable)]; + [[[self window] contentView] addSubview:self.contentSubview]; + [[self window] setShowsToolbarButton:NO]; +} + + +#pragma mark - +#pragma mark Configuration + +- (void)setupToolbar{ + // Subclasses must override this method to add items to the + // toolbar by calling -addView:label: or -addView:label:image:. +} + +- (void)addToolbarItemForIdentifier:(NSString *)identifier + label:(NSString *)label + image:(NSImage *)image + selector:(SEL)selector { + [self.toolbarIdentifiers addObject:identifier]; + + NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:identifier]; + [item setLabel:label]; + [item setImage:image]; + [item setTarget:self]; + [item setAction:selector]; + + (self.toolbarItems)[identifier] = item; + [item release]; +} + +- (void)addFlexibleSpacer { + [self addToolbarItemForIdentifier:NSToolbarFlexibleSpaceItemIdentifier label:nil image:nil selector:nil]; +} + +- (void)addView:(NSView *)view label:(NSString *)label{ + [self addView:view label:label image:[NSImage imageNamed:label]]; +} + +- (void)addView:(NSView *)view label:(NSString *)label image:(NSImage *)image{ + if(view == nil){ + return; + } + + NSString *identifier = [label copy]; + (self.toolbarViews)[identifier] = view; + [self addToolbarItemForIdentifier:identifier + label:label + image:image + selector:@selector(toggleActivePreferenceView:)]; +} + + +#pragma mark - +#pragma mark Overriding Methods + +- (IBAction)showWindow:(id)sender{ + // This forces the resources in the nib to load. + [self window]; + + // Clear the last setup and get a fresh one. + [self.toolbarIdentifiers removeAllObjects]; + [self.toolbarViews removeAllObjects]; + [self.toolbarItems removeAllObjects]; + [self setupToolbar]; + + if(![_toolbarIdentifiers count]){ + return; + } + + if([[self window] toolbar] == nil){ + NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"DBPreferencesToolbar"]; + [toolbar setAllowsUserCustomization:NO]; + [toolbar setAutosavesConfiguration:NO]; + [toolbar setSizeMode:NSToolbarSizeModeDefault]; + [toolbar setDisplayMode:NSToolbarDisplayModeIconAndLabel]; + [toolbar setDelegate:(id)self]; + [[self window] setToolbar:toolbar]; + } + + NSString *firstIdentifier = (self.toolbarIdentifiers)[0]; + [[[self window] toolbar] setSelectedItemIdentifier:firstIdentifier]; + [self displayViewForIdentifier:firstIdentifier animate:NO]; + + [[self window] center]; + + [super showWindow:sender]; +} + + +#pragma mark - +#pragma mark Toolbar + +- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar{ + return self.toolbarIdentifiers; +} + +- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)toolbar{ + return self.toolbarIdentifiers; +} + +- (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar *)toolbar{ + return self.toolbarIdentifiers; +} + +- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)identifier willBeInsertedIntoToolbar:(BOOL)willBeInserted{ + return (self.toolbarItems)[identifier]; +} + +- (void)toggleActivePreferenceView:(NSToolbarItem *)toolbarItem{ + [self displayViewForIdentifier:[toolbarItem itemIdentifier] animate:YES]; +} + +- (void)displayViewForIdentifier:(NSString *)identifier animate:(BOOL)animate{ + // Find the view we want to display. + NSView *newView = (self.toolbarViews)[identifier]; + + // See if there are any visible views. + NSView *oldView = nil; + if([[self.contentSubview subviews] count] > 0) { + // Get a list of all of the views in the window. Usually at this + // point there is just one visible view. But if the last fade + // hasn't finished, we need to get rid of it now before we move on. + NSEnumerator *subviewsEnum = [[self.contentSubview subviews] reverseObjectEnumerator]; + + // The first one (last one added) is our visible view. + oldView = [subviewsEnum nextObject]; + + // Remove any others. + NSView *reallyOldView = nil; + while((reallyOldView = [subviewsEnum nextObject]) != nil){ + [reallyOldView removeFromSuperviewWithoutNeedingDisplay]; + } + } + + if(![newView isEqualTo:oldView]){ + NSRect frame = [newView bounds]; + frame.origin.y = NSHeight([self.contentSubview frame]) - NSHeight([newView bounds]); + [newView setFrame:frame]; + [self.contentSubview addSubview:newView]; + [[self window] setInitialFirstResponder:newView]; + + if(animate && [self crossFade]){ + [self crossFadeView:oldView withView:newView]; + }else{ + [oldView removeFromSuperviewWithoutNeedingDisplay]; + [newView setHidden:NO]; + [[self window] setFrame:[self frameForView:newView] display:YES animate:animate]; + } + + [[self window] setTitle:[(self.toolbarItems)[identifier] label]]; + } +} + +- (void)loadViewForIdentifier:(NSString *)identifier animate:(BOOL)animate { + [[[self window] toolbar] setSelectedItemIdentifier:identifier]; + [self displayViewForIdentifier:identifier animate:animate]; +} + + +#pragma mark - +#pragma mark Cross-Fading Methods + +- (void)crossFadeView:(NSView *)oldView withView:(NSView *)newView{ + [self.viewAnimation stopAnimation]; + + if([self shiftSlowsAnimation] && [[[self window] currentEvent] modifierFlags] & NSShiftKeyMask){ + [self.viewAnimation setDuration:1.25]; + }else{ + [self.viewAnimation setDuration:0.25]; + } + + NSDictionary *fadeOutDictionary = + @{NSViewAnimationTargetKey: oldView, + NSViewAnimationEffectKey: NSViewAnimationFadeOutEffect}; + + NSDictionary *fadeInDictionary = + @{NSViewAnimationTargetKey: newView, + NSViewAnimationEffectKey: NSViewAnimationFadeInEffect}; + + NSDictionary *resizeDictionary = + @{NSViewAnimationTargetKey: [self window], + NSViewAnimationStartFrameKey: [NSValue valueWithRect:[[self window] frame]], + NSViewAnimationEndFrameKey: [NSValue valueWithRect:[self frameForView:newView]]}; + + NSArray *animationArray = + @[fadeOutDictionary, + fadeInDictionary, + resizeDictionary]; + + [self.viewAnimation setViewAnimations:animationArray]; + [self.viewAnimation startAnimation]; +} + +- (void)animationDidEnd:(NSAnimation *)animation{ + NSView *subview; + + // Get a list of all of the views in the window. Hopefully + // at this point there are two. One is visible and one is hidden. + NSEnumerator *subviewsEnum = [[self.contentSubview subviews] reverseObjectEnumerator]; + + // This is our visible view. Just get past it. + [subviewsEnum nextObject]; + + // Remove everything else. There should be just one, but + // if the user does a lot of fast clicking, we might have + // more than one to remove. + while((subview = [subviewsEnum nextObject]) != nil){ + [subview removeFromSuperviewWithoutNeedingDisplay]; + } + + // This is a work-around that prevents the first + // toolbar icon from becoming highlighted. + [[self window] makeFirstResponder:nil]; +} + +// Calculate the window size for the new view. +- (NSRect)frameForView:(NSView *)view{ + NSRect windowFrame = [[self window] frame]; + NSRect contentRect = [[self window] contentRectForFrameRect:windowFrame]; + float windowTitleAndToolbarHeight = NSHeight(windowFrame) - NSHeight(contentRect); + + windowFrame.size.height = NSHeight([view frame]) + windowTitleAndToolbarHeight; + windowFrame.size.width = NSWidth([view frame]); + windowFrame.origin.y = NSMaxY([[self window] frame]) - NSHeight(windowFrame); + + return windowFrame; +} + +// Close the window with cmd+w incase the app doesn't have an app menu +- (void)keyDown:(NSEvent *)theEvent{ + NSString *key = [theEvent charactersIgnoringModifiers]; + if(([theEvent modifierFlags] & NSCommandKeyMask) && [key isEqualToString:@"w"]){ + [self close]; + }else{ + [super keyDown:theEvent]; + } +} + +@end diff --git a/Seqotron/GeneticCodeTables/Alternative-Flatworm-Mitochondrial.plist b/Seqotron/GeneticCodeTables/Alternative-Flatworm-Mitochondrial.plist new file mode 100755 index 0000000..4c58d62 --- /dev/null +++ b/Seqotron/GeneticCodeTables/Alternative-Flatworm-Mitochondrial.plist @@ -0,0 +1,596 @@ + + + + + Description + Alternative Flatworm Mitochondrial + Table + + +TTT + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCT + + OLC + S + Name + Serine + TLC + Ser + +TAT + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGT + + OLC + C + Name + Cysteine + TLC + Cys + +TTC + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCC + + OLC + S + Name + Serine + TLC + Ser + +TAC + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGC + + OLC + C + Name + Cysteine + TLC + Cys + +TTA + + OLC + L + Name + Leucine + TLC + Leu + +TCA + + OLC + S + Name + Serine + TLC + Ser + +TAA + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGA + + OLC + W + Name + Tryptophan + TLC + Trp + +TTG + + OLC + L + Name + Leucine + TLC + Leu + +TCG + + OLC + S + Name + Serine + TLC + Ser + +TAG + + OLC + * + Name + termination codon + TLC + Ter + +TGG + + OLC + W + Name + Tryptophan + TLC + Trp + +CTT + + OLC + L + Name + Leucine + TLC + Leu + +CCT + + OLC + P + Name + Proline + TLC + Pro + +CAT + + OLC + H + Name + Histidine + TLC + His + +CGT + + OLC + R + Name + Arginine + TLC + Arg + +CTC + + OLC + L + Name + Leucine + TLC + Leu + +CCC + + OLC + P + Name + Proline + TLC + Pro + +CAC + + OLC + H + Name + Histidine + TLC + His + +CGC + + OLC + R + Name + Arginine + TLC + Arg + +CTA + + OLC + L + Name + Leucine + TLC + Leu + +CCA + + OLC + P + Name + Proline + TLC + Pro + +CAA + + OLC + Q + Name + Glutamine + TLC + Gln + +CGA + + OLC + R + Name + Arginine + TLC + Arg + +CTG + + OLC + L + Name + Leucine + TLC + Leu + +CCG + + OLC + P + Name + Proline + TLC + Pro + +CAG + + OLC + Q + Name + Glutamine + TLC + Gln + +CGG + + OLC + R + Name + Arginine + TLC + Arg + +ATT + + OLC + I + Name + Isoleucine + TLC + Ile + +ACT + + OLC + T + Name + Threonine + TLC + Thr + +AAT + + OLC + N + Name + Asparagine + TLC + Asn + +AGT + + OLC + S + Name + Serine + TLC + Ser + +ATC + + OLC + I + Name + Isoleucine + TLC + Ile + +ACC + + OLC + T + Name + Threonine + TLC + Thr + +AAC + + OLC + N + Name + Asparagine + TLC + Asn + +AGC + + OLC + S + Name + Serine + TLC + Ser + +ATA + + OLC + I + Name + Isoleucine + TLC + Ile + +ACA + + OLC + T + Name + Threonine + TLC + Thr + +AAA + + OLC + N + Name + Asparagine + TLC + Asn + +AGA + + OLC + S + Name + Serine + TLC + Ser + +ATG + + OLC + M + Name + Methionine + TLC + Met + +ACG + + OLC + T + Name + Threonine + TLC + Thr + +AAG + + OLC + K + Name + Lysine + TLC + Lys + +AGG + + OLC + S + Name + Serine + TLC + Ser + +GTT + + OLC + V + Name + Valine + TLC + Val + +GCT + + OLC + A + Name + Alanine + TLC + Ala + +GAT + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGT + + OLC + G + Name + Glycine + TLC + Gly + +GTC + + OLC + V + Name + Valine + TLC + Val + +GCC + + OLC + A + Name + Alanine + TLC + Ala + +GAC + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGC + + OLC + G + Name + Glycine + TLC + Gly + +GTA + + OLC + V + Name + Valine + TLC + Val + +GCA + + OLC + A + Name + Alanine + TLC + Ala + +GAA + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGA + + OLC + G + Name + Glycine + TLC + Gly + +GTG + + OLC + V + Name + Valine + TLC + Val + +GCG + + OLC + A + Name + Alanine + TLC + Ala + +GAG + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGG + + OLC + G + Name + Glycine + TLC + Gly + + + + \ No newline at end of file diff --git a/Seqotron/GeneticCodeTables/Alternative-Yeast-Nuclear.plist b/Seqotron/GeneticCodeTables/Alternative-Yeast-Nuclear.plist new file mode 100755 index 0000000..1dc4274 --- /dev/null +++ b/Seqotron/GeneticCodeTables/Alternative-Yeast-Nuclear.plist @@ -0,0 +1,596 @@ + + + + + Description + Alternative Yeast Nuclear + Table + + +TTT + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCT + + OLC + S + Name + Serine + TLC + Ser + +TAT + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGT + + OLC + C + Name + Cysteine + TLC + Cys + +TTC + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCC + + OLC + S + Name + Serine + TLC + Ser + +TAC + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGC + + OLC + C + Name + Cysteine + TLC + Cys + +TTA + + OLC + L + Name + Leucine + TLC + Leu + +TCA + + OLC + S + Name + Serine + TLC + Ser + +TAA + + OLC + * + Name + termination codon + TLC + Ter + +TGA + + OLC + * + Name + termination codon + TLC + Ter + +TTG + + OLC + L + Name + Leucine + TLC + Leu + +TCG + + OLC + S + Name + Serine + TLC + Ser + +TAG + + OLC + * + Name + termination codon + TLC + Ter + +TGG + + OLC + W + Name + Tryptophan + TLC + Trp + +CTT + + OLC + L + Name + Leucine + TLC + Leu + +CCT + + OLC + P + Name + Proline + TLC + Pro + +CAT + + OLC + H + Name + Histidine + TLC + His + +CGT + + OLC + R + Name + Arginine + TLC + Arg + +CTC + + OLC + L + Name + Leucine + TLC + Leu + +CCC + + OLC + P + Name + Proline + TLC + Pro + +CAC + + OLC + H + Name + Histidine + TLC + His + +CGC + + OLC + R + Name + Arginine + TLC + Arg + +CTA + + OLC + L + Name + Leucine + TLC + Leu + +CCA + + OLC + P + Name + Proline + TLC + Pro + +CAA + + OLC + Q + Name + Glutamine + TLC + Gln + +CGA + + OLC + R + Name + Arginine + TLC + Arg + +CTG + + OLC + S + Name + Serine + TLC + Ser + +CCG + + OLC + P + Name + Proline + TLC + Pro + +CAG + + OLC + Q + Name + Glutamine + TLC + Gln + +CGG + + OLC + R + Name + Arginine + TLC + Arg + +ATT + + OLC + I + Name + Isoleucine + TLC + Ile + +ACT + + OLC + T + Name + Threonine + TLC + Thr + +AAT + + OLC + N + Name + Asparagine + TLC + Asn + +AGT + + OLC + S + Name + Serine + TLC + Ser + +ATC + + OLC + I + Name + Isoleucine + TLC + Ile + +ACC + + OLC + T + Name + Threonine + TLC + Thr + +AAC + + OLC + N + Name + Asparagine + TLC + Asn + +AGC + + OLC + S + Name + Serine + TLC + Ser + +ATA + + OLC + I + Name + Isoleucine + TLC + Ile + +ACA + + OLC + T + Name + Threonine + TLC + Thr + +AAA + + OLC + K + Name + Lysine + TLC + Lys + +AGA + + OLC + R + Name + Arginine + TLC + Arg + +ATG + + OLC + M + Name + Methionine + TLC + Met + +ACG + + OLC + T + Name + Threonine + TLC + Thr + +AAG + + OLC + K + Name + Lysine + TLC + Lys + +AGG + + OLC + R + Name + Arginine + TLC + Arg + +GTT + + OLC + V + Name + Valine + TLC + Val + +GCT + + OLC + A + Name + Alanine + TLC + Ala + +GAT + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGT + + OLC + G + Name + Glycine + TLC + Gly + +GTC + + OLC + V + Name + Valine + TLC + Val + +GCC + + OLC + A + Name + Alanine + TLC + Ala + +GAC + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGC + + OLC + G + Name + Glycine + TLC + Gly + +GTA + + OLC + V + Name + Valine + TLC + Val + +GCA + + OLC + A + Name + Alanine + TLC + Ala + +GAA + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGA + + OLC + G + Name + Glycine + TLC + Gly + +GTG + + OLC + V + Name + Valine + TLC + Val + +GCG + + OLC + A + Name + Alanine + TLC + Ala + +GAG + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGG + + OLC + G + Name + Glycine + TLC + Gly + + + + \ No newline at end of file diff --git a/Seqotron/GeneticCodeTables/Ascidian-Mitochondrial.plist b/Seqotron/GeneticCodeTables/Ascidian-Mitochondrial.plist new file mode 100755 index 0000000..6d07010 --- /dev/null +++ b/Seqotron/GeneticCodeTables/Ascidian-Mitochondrial.plist @@ -0,0 +1,596 @@ + + + + + Description + Ascidian Mitochondrial + Table + + +TTT + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCT + + OLC + S + Name + Serine + TLC + Ser + +TAT + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGT + + OLC + C + Name + Cysteine + TLC + Cys + +TTC + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCC + + OLC + S + Name + Serine + TLC + Ser + +TAC + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGC + + OLC + C + Name + Cysteine + TLC + Cys + +TTA + + OLC + L + Name + Leucine + TLC + Leu + +TCA + + OLC + S + Name + Serine + TLC + Ser + +TAA + + OLC + * + Name + termination codon + TLC + Ter + +TGA + + OLC + W + Name + Tryptophan + TLC + Trp + +TTG + + OLC + L + Name + Leucine + TLC + Leu + +TCG + + OLC + S + Name + Serine + TLC + Ser + +TAG + + OLC + * + Name + termination codon + TLC + Ter + +TGG + + OLC + W + Name + Tryptophan + TLC + Trp + +CTT + + OLC + L + Name + Leucine + TLC + Leu + +CCT + + OLC + P + Name + Proline + TLC + Pro + +CAT + + OLC + H + Name + Histidine + TLC + His + +CGT + + OLC + R + Name + Arginine + TLC + Arg + +CTC + + OLC + L + Name + Leucine + TLC + Leu + +CCC + + OLC + P + Name + Proline + TLC + Pro + +CAC + + OLC + H + Name + Histidine + TLC + His + +CGC + + OLC + R + Name + Arginine + TLC + Arg + +CTA + + OLC + L + Name + Leucine + TLC + Leu + +CCA + + OLC + P + Name + Proline + TLC + Pro + +CAA + + OLC + Q + Name + Glutamine + TLC + Gln + +CGA + + OLC + R + Name + Arginine + TLC + Arg + +CTG + + OLC + L + Name + Leucine + TLC + Leu + +CCG + + OLC + P + Name + Proline + TLC + Pro + +CAG + + OLC + Q + Name + Glutamine + TLC + Gln + +CGG + + OLC + R + Name + Arginine + TLC + Arg + +ATT + + OLC + I + Name + Isoleucine + TLC + Ile + +ACT + + OLC + T + Name + Threonine + TLC + Thr + +AAT + + OLC + N + Name + Asparagine + TLC + Asn + +AGT + + OLC + S + Name + Serine + TLC + Ser + +ATC + + OLC + I + Name + Isoleucine + TLC + Ile + +ACC + + OLC + T + Name + Threonine + TLC + Thr + +AAC + + OLC + N + Name + Asparagine + TLC + Asn + +AGC + + OLC + S + Name + Serine + TLC + Ser + +ATA + + OLC + M + Name + Methionine + TLC + Met + +ACA + + OLC + T + Name + Threonine + TLC + Thr + +AAA + + OLC + K + Name + Lysine + TLC + Lys + +AGA + + OLC + G + Name + Glycine + TLC + Gly + +ATG + + OLC + M + Name + Methionine + TLC + Met + +ACG + + OLC + T + Name + Threonine + TLC + Thr + +AAG + + OLC + K + Name + Lysine + TLC + Lys + +AGG + + OLC + G + Name + Glycine + TLC + Gly + +GTT + + OLC + V + Name + Valine + TLC + Val + +GCT + + OLC + A + Name + Alanine + TLC + Ala + +GAT + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGT + + OLC + G + Name + Glycine + TLC + Gly + +GTC + + OLC + V + Name + Valine + TLC + Val + +GCC + + OLC + A + Name + Alanine + TLC + Ala + +GAC + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGC + + OLC + G + Name + Glycine + TLC + Gly + +GTA + + OLC + V + Name + Valine + TLC + Val + +GCA + + OLC + A + Name + Alanine + TLC + Ala + +GAA + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGA + + OLC + G + Name + Glycine + TLC + Gly + +GTG + + OLC + V + Name + Valine + TLC + Val + +GCG + + OLC + A + Name + Alanine + TLC + Ala + +GAG + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGG + + OLC + G + Name + Glycine + TLC + Gly + + + + \ No newline at end of file diff --git a/Seqotron/GeneticCodeTables/Bacterial,-Archaeal-and-Plant-Plastid.plist b/Seqotron/GeneticCodeTables/Bacterial,-Archaeal-and-Plant-Plastid.plist new file mode 100755 index 0000000..1ac8645 --- /dev/null +++ b/Seqotron/GeneticCodeTables/Bacterial,-Archaeal-and-Plant-Plastid.plist @@ -0,0 +1,596 @@ + + + + + Description + Bacterial, Archaeal and Plant Plastid + Table + + +TTT + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCT + + OLC + S + Name + Serine + TLC + Ser + +TAT + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGT + + OLC + C + Name + Cysteine + TLC + Cys + +TTC + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCC + + OLC + S + Name + Serine + TLC + Ser + +TAC + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGC + + OLC + C + Name + Cysteine + TLC + Cys + +TTA + + OLC + L + Name + Leucine + TLC + Leu + +TCA + + OLC + S + Name + Serine + TLC + Ser + +TAA + + OLC + * + Name + termination codon + TLC + Ter + +TGA + + OLC + * + Name + termination codon + TLC + Ter + +TTG + + OLC + L + Name + Leucine + TLC + Leu + +TCG + + OLC + S + Name + Serine + TLC + Ser + +TAG + + OLC + * + Name + termination codon + TLC + Ter + +TGG + + OLC + W + Name + Tryptophan + TLC + Trp + +CTT + + OLC + L + Name + Leucine + TLC + Leu + +CCT + + OLC + P + Name + Proline + TLC + Pro + +CAT + + OLC + H + Name + Histidine + TLC + His + +CGT + + OLC + R + Name + Arginine + TLC + Arg + +CTC + + OLC + L + Name + Leucine + TLC + Leu + +CCC + + OLC + P + Name + Proline + TLC + Pro + +CAC + + OLC + H + Name + Histidine + TLC + His + +CGC + + OLC + R + Name + Arginine + TLC + Arg + +CTA + + OLC + L + Name + Leucine + TLC + Leu + +CCA + + OLC + P + Name + Proline + TLC + Pro + +CAA + + OLC + Q + Name + Glutamine + TLC + Gln + +CGA + + OLC + R + Name + Arginine + TLC + Arg + +CTG + + OLC + L + Name + Leucine + TLC + Leu + +CCG + + OLC + P + Name + Proline + TLC + Pro + +CAG + + OLC + Q + Name + Glutamine + TLC + Gln + +CGG + + OLC + R + Name + Arginine + TLC + Arg + +ATT + + OLC + I + Name + Isoleucine + TLC + Ile + +ACT + + OLC + T + Name + Threonine + TLC + Thr + +AAT + + OLC + N + Name + Asparagine + TLC + Asn + +AGT + + OLC + S + Name + Serine + TLC + Ser + +ATC + + OLC + I + Name + Isoleucine + TLC + Ile + +ACC + + OLC + T + Name + Threonine + TLC + Thr + +AAC + + OLC + N + Name + Asparagine + TLC + Asn + +AGC + + OLC + S + Name + Serine + TLC + Ser + +ATA + + OLC + I + Name + Isoleucine + TLC + Ile + +ACA + + OLC + T + Name + Threonine + TLC + Thr + +AAA + + OLC + K + Name + Lysine + TLC + Lys + +AGA + + OLC + R + Name + Arginine + TLC + Arg + +ATG + + OLC + M + Name + Methionine + TLC + Met + +ACG + + OLC + T + Name + Threonine + TLC + Thr + +AAG + + OLC + K + Name + Lysine + TLC + Lys + +AGG + + OLC + R + Name + Arginine + TLC + Arg + +GTT + + OLC + V + Name + Valine + TLC + Val + +GCT + + OLC + A + Name + Alanine + TLC + Ala + +GAT + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGT + + OLC + G + Name + Glycine + TLC + Gly + +GTC + + OLC + V + Name + Valine + TLC + Val + +GCC + + OLC + A + Name + Alanine + TLC + Ala + +GAC + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGC + + OLC + G + Name + Glycine + TLC + Gly + +GTA + + OLC + V + Name + Valine + TLC + Val + +GCA + + OLC + A + Name + Alanine + TLC + Ala + +GAA + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGA + + OLC + G + Name + Glycine + TLC + Gly + +GTG + + OLC + V + Name + Valine + TLC + Val + +GCG + + OLC + A + Name + Alanine + TLC + Ala + +GAG + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGG + + OLC + G + Name + Glycine + TLC + Gly + + + + \ No newline at end of file diff --git a/Seqotron/GeneticCodeTables/Candidate-Division-SR1-and-Gracilibacteria.plist b/Seqotron/GeneticCodeTables/Candidate-Division-SR1-and-Gracilibacteria.plist new file mode 100755 index 0000000..a04ab95 --- /dev/null +++ b/Seqotron/GeneticCodeTables/Candidate-Division-SR1-and-Gracilibacteria.plist @@ -0,0 +1,595 @@ + + + + + Description + Candidate Division SR1 and Gracilibacteria + Table + + TTT + + OLC + F + Name + Phenylalanine + TLC + Phe + + TCT + + OLC + S + Name + Serine + TLC + Ser + + TAT + + OLC + Y + Name + Tyrosine + TLC + Tyr + + TGT + + OLC + C + Name + Cysteine + TLC + Cys + + TTC + + OLC + F + Name + Phenylalanine + TLC + Phe + + TCC + + OLC + S + Name + Serine + TLC + Ser + + TAC + + OLC + Y + Name + Tyrosine + TLC + Tyr + + TGC + + OLC + C + Name + Cysteine + TLC + Cys + + TTA + + OLC + L + Name + Leucine + TLC + Leu + + TCA + + OLC + S + Name + Serine + TLC + Ser + + TAA + + OLC + * + Name + termination codon + TLC + Ter + + TGA + + OLC + G + Name + Glycine + TLC + Gly + + TTG + + OLC + L + Name + Leucine + TLC + Leu + + TCG + + OLC + S + Name + Serine + TLC + Ser + + TAG + + OLC + * + Name + termination codon + TLC + Ter + + TGG + + OLC + W + Name + Tryptophan + TLC + Trp + + CTT + + OLC + L + Name + Leucine + TLC + Leu + + CCT + + OLC + P + Name + Proline + TLC + Pro + + CAT + + OLC + H + Name + Histidine + TLC + His + + CGT + + OLC + R + Name + Arginine + TLC + Arg + + CTC + + OLC + L + Name + Leucine + TLC + Leu + + CCC + + OLC + P + Name + Proline + TLC + Pro + + CAC + + OLC + H + Name + Histidine + TLC + His + + CGC + + OLC + R + Name + Arginine + TLC + Arg + + CTA + + OLC + L + Name + Leucine + TLC + Leu + + CCA + + OLC + P + Name + Proline + TLC + Pro + + CAA + + OLC + Q + Name + Glutamine + TLC + Gln + + CGA + + OLC + R + Name + Arginine + TLC + Arg + + CTG + + OLC + L + Name + Leucine + TLC + Leu + + CCG + + OLC + P + Name + Proline + TLC + Pro + + CAG + + OLC + Q + Name + Glutamine + TLC + Gln + + CGG + + OLC + R + Name + Arginine + TLC + Arg + + ATT + + OLC + I + Name + Isoleucine + TLC + Ile + + ACT + + OLC + T + Name + Threonine + TLC + Thr + + AAT + + OLC + N + Name + Asparagine + TLC + Asn + + AGT + + OLC + S + Name + Serine + TLC + Ser + + ATC + + OLC + I + Name + Isoleucine + TLC + Ile + + ACC + + OLC + T + Name + Threonine + TLC + Thr + + AAC + + OLC + N + Name + Asparagine + TLC + Asn + + AGC + + OLC + S + Name + Serine + TLC + Ser + + ATA + + OLC + I + Name + Isoleucine + TLC + Ile + + ACA + + OLC + T + Name + Threonine + TLC + Thr + + AAA + + OLC + K + Name + Lysine + TLC + Lys + + AGA + + OLC + R + Name + Arginine + TLC + Arg + + ATG + + OLC + M + Name + Methionine + TLC + Met + + ACG + + OLC + T + Name + Threonine + TLC + Thr + + AAG + + OLC + K + Name + Lysine + TLC + Lys + + AGG + + OLC + R + Name + Arginine + TLC + Arg + + GTT + + OLC + V + Name + Valine + TLC + Val + + GCT + + OLC + A + Name + Alanine + TLC + Ala + + GAT + + OLC + D + Name + Aspartate + TLC + Asp + Alt + Aspartic acid + + GGT + + OLC + G + Name + Glycine + TLC + Gly + + GTC + + OLC + V + Name + Valine + TLC + Val + + GCC + + OLC + A + Name + Alanine + TLC + Ala + + GAC + + OLC + D + Name + Aspartate + TLC + Asp + Alt + Aspartic acid + + GGC + + OLC + G + Name + Glycine + TLC + Gly + + GTA + + OLC + V + Name + Valine + TLC + Val + + GCA + + OLC + A + Name + Alanine + TLC + Ala + + GAA + + OLC + E + Name + Glutamate + TLC + Glu + Alt + Glutamic acid + + GGA + + OLC + G + Name + Glycine + TLC + Gly + + GTG + + OLC + V + Name + Valine + TLC + Val + + GCG + + OLC + A + Name + Alanine + TLC + Ala + + GAG + + OLC + E + Name + Glutamate + TLC + Glu + Alt + Glutamic acid + + GGG + + OLC + G + Name + Glycine + TLC + Gly + + + + diff --git a/Seqotron/GeneticCodeTables/Chlorophycean-Mitochondrial.plist b/Seqotron/GeneticCodeTables/Chlorophycean-Mitochondrial.plist new file mode 100755 index 0000000..90cfab3 --- /dev/null +++ b/Seqotron/GeneticCodeTables/Chlorophycean-Mitochondrial.plist @@ -0,0 +1,596 @@ + + + + + Description + Chlorophycean Mitochondrial + Table + + +TTT + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCT + + OLC + S + Name + Serine + TLC + Ser + +TAT + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGT + + OLC + C + Name + Cysteine + TLC + Cys + +TTC + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCC + + OLC + S + Name + Serine + TLC + Ser + +TAC + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGC + + OLC + C + Name + Cysteine + TLC + Cys + +TTA + + OLC + L + Name + Leucine + TLC + Leu + +TCA + + OLC + S + Name + Serine + TLC + Ser + +TAA + + OLC + * + Name + termination codon + TLC + Ter + +TGA + + OLC + * + Name + termination codon + TLC + Ter + +TTG + + OLC + L + Name + Leucine + TLC + Leu + +TCG + + OLC + S + Name + Serine + TLC + Ser + +TAG + + OLC + L + Name + Leucine + TLC + Leu + +TGG + + OLC + W + Name + Tryptophan + TLC + Trp + +CTT + + OLC + L + Name + Leucine + TLC + Leu + +CCT + + OLC + P + Name + Proline + TLC + Pro + +CAT + + OLC + H + Name + Histidine + TLC + His + +CGT + + OLC + R + Name + Arginine + TLC + Arg + +CTC + + OLC + L + Name + Leucine + TLC + Leu + +CCC + + OLC + P + Name + Proline + TLC + Pro + +CAC + + OLC + H + Name + Histidine + TLC + His + +CGC + + OLC + R + Name + Arginine + TLC + Arg + +CTA + + OLC + L + Name + Leucine + TLC + Leu + +CCA + + OLC + P + Name + Proline + TLC + Pro + +CAA + + OLC + Q + Name + Glutamine + TLC + Gln + +CGA + + OLC + R + Name + Arginine + TLC + Arg + +CTG + + OLC + L + Name + Leucine + TLC + Leu + +CCG + + OLC + P + Name + Proline + TLC + Pro + +CAG + + OLC + Q + Name + Glutamine + TLC + Gln + +CGG + + OLC + R + Name + Arginine + TLC + Arg + +ATT + + OLC + I + Name + Isoleucine + TLC + Ile + +ACT + + OLC + T + Name + Threonine + TLC + Thr + +AAT + + OLC + N + Name + Asparagine + TLC + Asn + +AGT + + OLC + S + Name + Serine + TLC + Ser + +ATC + + OLC + I + Name + Isoleucine + TLC + Ile + +ACC + + OLC + T + Name + Threonine + TLC + Thr + +AAC + + OLC + N + Name + Asparagine + TLC + Asn + +AGC + + OLC + S + Name + Serine + TLC + Ser + +ATA + + OLC + I + Name + Isoleucine + TLC + Ile + +ACA + + OLC + T + Name + Threonine + TLC + Thr + +AAA + + OLC + K + Name + Lysine + TLC + Lys + +AGA + + OLC + R + Name + Arginine + TLC + Arg + +ATG + + OLC + M + Name + Methionine + TLC + Met + +ACG + + OLC + T + Name + Threonine + TLC + Thr + +AAG + + OLC + K + Name + Lysine + TLC + Lys + +AGG + + OLC + R + Name + Arginine + TLC + Arg + +GTT + + OLC + V + Name + Valine + TLC + Val + +GCT + + OLC + A + Name + Alanine + TLC + Ala + +GAT + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGT + + OLC + G + Name + Glycine + TLC + Gly + +GTC + + OLC + V + Name + Valine + TLC + Val + +GCC + + OLC + A + Name + Alanine + TLC + Ala + +GAC + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGC + + OLC + G + Name + Glycine + TLC + Gly + +GTA + + OLC + V + Name + Valine + TLC + Val + +GCA + + OLC + A + Name + Alanine + TLC + Ala + +GAA + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGA + + OLC + G + Name + Glycine + TLC + Gly + +GTG + + OLC + V + Name + Valine + TLC + Val + +GCG + + OLC + A + Name + Alanine + TLC + Ala + +GAG + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGG + + OLC + G + Name + Glycine + TLC + Gly + + + + \ No newline at end of file diff --git a/Seqotron/GeneticCodeTables/Ciliate,-Dasycladacean-and-Hexamita-Nuclear.plist b/Seqotron/GeneticCodeTables/Ciliate,-Dasycladacean-and-Hexamita-Nuclear.plist new file mode 100755 index 0000000..042476f --- /dev/null +++ b/Seqotron/GeneticCodeTables/Ciliate,-Dasycladacean-and-Hexamita-Nuclear.plist @@ -0,0 +1,596 @@ + + + + + Description + Ciliate, Dasycladacean and Hexamita Nuclear + Table + + +TTT + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCT + + OLC + S + Name + Serine + TLC + Ser + +TAT + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGT + + OLC + C + Name + Cysteine + TLC + Cys + +TTC + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCC + + OLC + S + Name + Serine + TLC + Ser + +TAC + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGC + + OLC + C + Name + Cysteine + TLC + Cys + +TTA + + OLC + L + Name + Leucine + TLC + Leu + +TCA + + OLC + S + Name + Serine + TLC + Ser + +TAA + + OLC + Q + Name + Glutamine + TLC + Gln + +TGA + + OLC + * + Name + termination codon + TLC + Ter + +TTG + + OLC + L + Name + Leucine + TLC + Leu + +TCG + + OLC + S + Name + Serine + TLC + Ser + +TAG + + OLC + Q + Name + Glutamine + TLC + Gln + +TGG + + OLC + W + Name + Tryptophan + TLC + Trp + +CTT + + OLC + L + Name + Leucine + TLC + Leu + +CCT + + OLC + P + Name + Proline + TLC + Pro + +CAT + + OLC + H + Name + Histidine + TLC + His + +CGT + + OLC + R + Name + Arginine + TLC + Arg + +CTC + + OLC + L + Name + Leucine + TLC + Leu + +CCC + + OLC + P + Name + Proline + TLC + Pro + +CAC + + OLC + H + Name + Histidine + TLC + His + +CGC + + OLC + R + Name + Arginine + TLC + Arg + +CTA + + OLC + L + Name + Leucine + TLC + Leu + +CCA + + OLC + P + Name + Proline + TLC + Pro + +CAA + + OLC + Q + Name + Glutamine + TLC + Gln + +CGA + + OLC + R + Name + Arginine + TLC + Arg + +CTG + + OLC + L + Name + Leucine + TLC + Leu + +CCG + + OLC + P + Name + Proline + TLC + Pro + +CAG + + OLC + Q + Name + Glutamine + TLC + Gln + +CGG + + OLC + R + Name + Arginine + TLC + Arg + +ATT + + OLC + I + Name + Isoleucine + TLC + Ile + +ACT + + OLC + T + Name + Threonine + TLC + Thr + +AAT + + OLC + N + Name + Asparagine + TLC + Asn + +AGT + + OLC + S + Name + Serine + TLC + Ser + +ATC + + OLC + I + Name + Isoleucine + TLC + Ile + +ACC + + OLC + T + Name + Threonine + TLC + Thr + +AAC + + OLC + N + Name + Asparagine + TLC + Asn + +AGC + + OLC + S + Name + Serine + TLC + Ser + +ATA + + OLC + I + Name + Isoleucine + TLC + Ile + +ACA + + OLC + T + Name + Threonine + TLC + Thr + +AAA + + OLC + K + Name + Lysine + TLC + Lys + +AGA + + OLC + R + Name + Arginine + TLC + Arg + +ATG + + OLC + M + Name + Methionine + TLC + Met + +ACG + + OLC + T + Name + Threonine + TLC + Thr + +AAG + + OLC + K + Name + Lysine + TLC + Lys + +AGG + + OLC + R + Name + Arginine + TLC + Arg + +GTT + + OLC + V + Name + Valine + TLC + Val + +GCT + + OLC + A + Name + Alanine + TLC + Ala + +GAT + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGT + + OLC + G + Name + Glycine + TLC + Gly + +GTC + + OLC + V + Name + Valine + TLC + Val + +GCC + + OLC + A + Name + Alanine + TLC + Ala + +GAC + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGC + + OLC + G + Name + Glycine + TLC + Gly + +GTA + + OLC + V + Name + Valine + TLC + Val + +GCA + + OLC + A + Name + Alanine + TLC + Ala + +GAA + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGA + + OLC + G + Name + Glycine + TLC + Gly + +GTG + + OLC + V + Name + Valine + TLC + Val + +GCG + + OLC + A + Name + Alanine + TLC + Ala + +GAG + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGG + + OLC + G + Name + Glycine + TLC + Gly + + + + \ No newline at end of file diff --git a/Seqotron/GeneticCodeTables/Echinoderm-and-Flatworm-Mitochondrial.plist b/Seqotron/GeneticCodeTables/Echinoderm-and-Flatworm-Mitochondrial.plist new file mode 100755 index 0000000..42ec86b --- /dev/null +++ b/Seqotron/GeneticCodeTables/Echinoderm-and-Flatworm-Mitochondrial.plist @@ -0,0 +1,596 @@ + + + + + Description + Echinoderm and Flatworm Mitochondrial + Table + + +TTT + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCT + + OLC + S + Name + Serine + TLC + Ser + +TAT + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGT + + OLC + C + Name + Cysteine + TLC + Cys + +TTC + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCC + + OLC + S + Name + Serine + TLC + Ser + +TAC + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGC + + OLC + C + Name + Cysteine + TLC + Cys + +TTA + + OLC + L + Name + Leucine + TLC + Leu + +TCA + + OLC + S + Name + Serine + TLC + Ser + +TAA + + OLC + * + Name + termination codon + TLC + Ter + +TGA + + OLC + W + Name + Tryptophan + TLC + Trp + +TTG + + OLC + L + Name + Leucine + TLC + Leu + +TCG + + OLC + S + Name + Serine + TLC + Ser + +TAG + + OLC + * + Name + termination codon + TLC + Ter + +TGG + + OLC + W + Name + Tryptophan + TLC + Trp + +CTT + + OLC + L + Name + Leucine + TLC + Leu + +CCT + + OLC + P + Name + Proline + TLC + Pro + +CAT + + OLC + H + Name + Histidine + TLC + His + +CGT + + OLC + R + Name + Arginine + TLC + Arg + +CTC + + OLC + L + Name + Leucine + TLC + Leu + +CCC + + OLC + P + Name + Proline + TLC + Pro + +CAC + + OLC + H + Name + Histidine + TLC + His + +CGC + + OLC + R + Name + Arginine + TLC + Arg + +CTA + + OLC + L + Name + Leucine + TLC + Leu + +CCA + + OLC + P + Name + Proline + TLC + Pro + +CAA + + OLC + Q + Name + Glutamine + TLC + Gln + +CGA + + OLC + R + Name + Arginine + TLC + Arg + +CTG + + OLC + L + Name + Leucine + TLC + Leu + +CCG + + OLC + P + Name + Proline + TLC + Pro + +CAG + + OLC + Q + Name + Glutamine + TLC + Gln + +CGG + + OLC + R + Name + Arginine + TLC + Arg + +ATT + + OLC + I + Name + Isoleucine + TLC + Ile + +ACT + + OLC + T + Name + Threonine + TLC + Thr + +AAT + + OLC + N + Name + Asparagine + TLC + Asn + +AGT + + OLC + S + Name + Serine + TLC + Ser + +ATC + + OLC + I + Name + Isoleucine + TLC + Ile + +ACC + + OLC + T + Name + Threonine + TLC + Thr + +AAC + + OLC + N + Name + Asparagine + TLC + Asn + +AGC + + OLC + S + Name + Serine + TLC + Ser + +ATA + + OLC + I + Name + Isoleucine + TLC + Ile + +ACA + + OLC + T + Name + Threonine + TLC + Thr + +AAA + + OLC + N + Name + Asparagine + TLC + Asn + +AGA + + OLC + S + Name + Serine + TLC + Ser + +ATG + + OLC + M + Name + Methionine + TLC + Met + +ACG + + OLC + T + Name + Threonine + TLC + Thr + +AAG + + OLC + K + Name + Lysine + TLC + Lys + +AGG + + OLC + S + Name + Serine + TLC + Ser + +GTT + + OLC + V + Name + Valine + TLC + Val + +GCT + + OLC + A + Name + Alanine + TLC + Ala + +GAT + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGT + + OLC + G + Name + Glycine + TLC + Gly + +GTC + + OLC + V + Name + Valine + TLC + Val + +GCC + + OLC + A + Name + Alanine + TLC + Ala + +GAC + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGC + + OLC + G + Name + Glycine + TLC + Gly + +GTA + + OLC + V + Name + Valine + TLC + Val + +GCA + + OLC + A + Name + Alanine + TLC + Ala + +GAA + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGA + + OLC + G + Name + Glycine + TLC + Gly + +GTG + + OLC + V + Name + Valine + TLC + Val + +GCG + + OLC + A + Name + Alanine + TLC + Ala + +GAG + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGG + + OLC + G + Name + Glycine + TLC + Gly + + + + \ No newline at end of file diff --git a/Seqotron/GeneticCodeTables/Euplotid-Nuclear.plist b/Seqotron/GeneticCodeTables/Euplotid-Nuclear.plist new file mode 100755 index 0000000..46bb2c9 --- /dev/null +++ b/Seqotron/GeneticCodeTables/Euplotid-Nuclear.plist @@ -0,0 +1,596 @@ + + + + + Description + Euplotid Nuclear + Table + + +TTT + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCT + + OLC + S + Name + Serine + TLC + Ser + +TAT + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGT + + OLC + C + Name + Cysteine + TLC + Cys + +TTC + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCC + + OLC + S + Name + Serine + TLC + Ser + +TAC + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGC + + OLC + C + Name + Cysteine + TLC + Cys + +TTA + + OLC + L + Name + Leucine + TLC + Leu + +TCA + + OLC + S + Name + Serine + TLC + Ser + +TAA + + OLC + * + Name + termination codon + TLC + Ter + +TGA + + OLC + C + Name + Cysteine + TLC + Cys + +TTG + + OLC + L + Name + Leucine + TLC + Leu + +TCG + + OLC + S + Name + Serine + TLC + Ser + +TAG + + OLC + * + Name + termination codon + TLC + Ter + +TGG + + OLC + W + Name + Tryptophan + TLC + Trp + +CTT + + OLC + L + Name + Leucine + TLC + Leu + +CCT + + OLC + P + Name + Proline + TLC + Pro + +CAT + + OLC + H + Name + Histidine + TLC + His + +CGT + + OLC + R + Name + Arginine + TLC + Arg + +CTC + + OLC + L + Name + Leucine + TLC + Leu + +CCC + + OLC + P + Name + Proline + TLC + Pro + +CAC + + OLC + H + Name + Histidine + TLC + His + +CGC + + OLC + R + Name + Arginine + TLC + Arg + +CTA + + OLC + L + Name + Leucine + TLC + Leu + +CCA + + OLC + P + Name + Proline + TLC + Pro + +CAA + + OLC + Q + Name + Glutamine + TLC + Gln + +CGA + + OLC + R + Name + Arginine + TLC + Arg + +CTG + + OLC + L + Name + Leucine + TLC + Leu + +CCG + + OLC + P + Name + Proline + TLC + Pro + +CAG + + OLC + Q + Name + Glutamine + TLC + Gln + +CGG + + OLC + R + Name + Arginine + TLC + Arg + +ATT + + OLC + I + Name + Isoleucine + TLC + Ile + +ACT + + OLC + T + Name + Threonine + TLC + Thr + +AAT + + OLC + N + Name + Asparagine + TLC + Asn + +AGT + + OLC + S + Name + Serine + TLC + Ser + +ATC + + OLC + I + Name + Isoleucine + TLC + Ile + +ACC + + OLC + T + Name + Threonine + TLC + Thr + +AAC + + OLC + N + Name + Asparagine + TLC + Asn + +AGC + + OLC + S + Name + Serine + TLC + Ser + +ATA + + OLC + I + Name + Isoleucine + TLC + Ile + +ACA + + OLC + T + Name + Threonine + TLC + Thr + +AAA + + OLC + K + Name + Lysine + TLC + Lys + +AGA + + OLC + R + Name + Arginine + TLC + Arg + +ATG + + OLC + M + Name + Methionine + TLC + Met + +ACG + + OLC + T + Name + Threonine + TLC + Thr + +AAG + + OLC + K + Name + Lysine + TLC + Lys + +AGG + + OLC + R + Name + Arginine + TLC + Arg + +GTT + + OLC + V + Name + Valine + TLC + Val + +GCT + + OLC + A + Name + Alanine + TLC + Ala + +GAT + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGT + + OLC + G + Name + Glycine + TLC + Gly + +GTC + + OLC + V + Name + Valine + TLC + Val + +GCC + + OLC + A + Name + Alanine + TLC + Ala + +GAC + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGC + + OLC + G + Name + Glycine + TLC + Gly + +GTA + + OLC + V + Name + Valine + TLC + Val + +GCA + + OLC + A + Name + Alanine + TLC + Ala + +GAA + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGA + + OLC + G + Name + Glycine + TLC + Gly + +GTG + + OLC + V + Name + Valine + TLC + Val + +GCG + + OLC + A + Name + Alanine + TLC + Ala + +GAG + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGG + + OLC + G + Name + Glycine + TLC + Gly + + + + \ No newline at end of file diff --git a/Seqotron/GeneticCodeTables/Invertebrate-Mitochondrial.plist b/Seqotron/GeneticCodeTables/Invertebrate-Mitochondrial.plist new file mode 100755 index 0000000..82c6986 --- /dev/null +++ b/Seqotron/GeneticCodeTables/Invertebrate-Mitochondrial.plist @@ -0,0 +1,596 @@ + + + + + Description + Invertebrate Mitochondrial + Table + + +TTT + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCT + + OLC + S + Name + Serine + TLC + Ser + +TAT + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGT + + OLC + C + Name + Cysteine + TLC + Cys + +TTC + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCC + + OLC + S + Name + Serine + TLC + Ser + +TAC + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGC + + OLC + C + Name + Cysteine + TLC + Cys + +TTA + + OLC + L + Name + Leucine + TLC + Leu + +TCA + + OLC + S + Name + Serine + TLC + Ser + +TAA + + OLC + * + Name + termination codon + TLC + Ter + +TGA + + OLC + W + Name + Tryptophan + TLC + Trp + +TTG + + OLC + L + Name + Leucine + TLC + Leu + +TCG + + OLC + S + Name + Serine + TLC + Ser + +TAG + + OLC + * + Name + termination codon + TLC + Ter + +TGG + + OLC + W + Name + Tryptophan + TLC + Trp + +CTT + + OLC + L + Name + Leucine + TLC + Leu + +CCT + + OLC + P + Name + Proline + TLC + Pro + +CAT + + OLC + H + Name + Histidine + TLC + His + +CGT + + OLC + R + Name + Arginine + TLC + Arg + +CTC + + OLC + L + Name + Leucine + TLC + Leu + +CCC + + OLC + P + Name + Proline + TLC + Pro + +CAC + + OLC + H + Name + Histidine + TLC + His + +CGC + + OLC + R + Name + Arginine + TLC + Arg + +CTA + + OLC + L + Name + Leucine + TLC + Leu + +CCA + + OLC + P + Name + Proline + TLC + Pro + +CAA + + OLC + Q + Name + Glutamine + TLC + Gln + +CGA + + OLC + R + Name + Arginine + TLC + Arg + +CTG + + OLC + L + Name + Leucine + TLC + Leu + +CCG + + OLC + P + Name + Proline + TLC + Pro + +CAG + + OLC + Q + Name + Glutamine + TLC + Gln + +CGG + + OLC + R + Name + Arginine + TLC + Arg + +ATT + + OLC + I + Name + Isoleucine + TLC + Ile + +ACT + + OLC + T + Name + Threonine + TLC + Thr + +AAT + + OLC + N + Name + Asparagine + TLC + Asn + +AGT + + OLC + S + Name + Serine + TLC + Ser + +ATC + + OLC + I + Name + Isoleucine + TLC + Ile + +ACC + + OLC + T + Name + Threonine + TLC + Thr + +AAC + + OLC + N + Name + Asparagine + TLC + Asn + +AGC + + OLC + S + Name + Serine + TLC + Ser + +ATA + + OLC + M + Name + Methionine + TLC + Met + +ACA + + OLC + T + Name + Threonine + TLC + Thr + +AAA + + OLC + K + Name + Lysine + TLC + Lys + +AGA + + OLC + S + Name + Serine + TLC + Ser + +ATG + + OLC + M + Name + Methionine + TLC + Met + +ACG + + OLC + T + Name + Threonine + TLC + Thr + +AAG + + OLC + K + Name + Lysine + TLC + Lys + +AGG + + OLC + S + Name + Serine + TLC + Ser + +GTT + + OLC + V + Name + Valine + TLC + Val + +GCT + + OLC + A + Name + Alanine + TLC + Ala + +GAT + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGT + + OLC + G + Name + Glycine + TLC + Gly + +GTC + + OLC + V + Name + Valine + TLC + Val + +GCC + + OLC + A + Name + Alanine + TLC + Ala + +GAC + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGC + + OLC + G + Name + Glycine + TLC + Gly + +GTA + + OLC + V + Name + Valine + TLC + Val + +GCA + + OLC + A + Name + Alanine + TLC + Ala + +GAA + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGA + + OLC + G + Name + Glycine + TLC + Gly + +GTG + + OLC + V + Name + Valine + TLC + Val + +GCG + + OLC + A + Name + Alanine + TLC + Ala + +GAG + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGG + + OLC + G + Name + Glycine + TLC + Gly + + + + \ No newline at end of file diff --git a/Seqotron/GeneticCodeTables/Mold,-Protozoan,-and-Coelenterate-Mitochondrial-Code-and-the-Mycoplasma-Spiroplasma.plist b/Seqotron/GeneticCodeTables/Mold,-Protozoan,-and-Coelenterate-Mitochondrial-Code-and-the-Mycoplasma-Spiroplasma.plist new file mode 100755 index 0000000..e8293f1 --- /dev/null +++ b/Seqotron/GeneticCodeTables/Mold,-Protozoan,-and-Coelenterate-Mitochondrial-Code-and-the-Mycoplasma-Spiroplasma.plist @@ -0,0 +1,595 @@ + + + + + Description + Mold, Protozoan, Coelenterate Mitochondrial and Mycoplasma-Spiroplasma + Table + + TTT + + OLC + F + Name + Phenylalanine + TLC + Phe + + TCT + + OLC + S + Name + Serine + TLC + Ser + + TAT + + OLC + Y + Name + Tyrosine + TLC + Tyr + + TGT + + OLC + C + Name + Cysteine + TLC + Cys + + TTC + + OLC + F + Name + Phenylalanine + TLC + Phe + + TCC + + OLC + S + Name + Serine + TLC + Ser + + TAC + + OLC + Y + Name + Tyrosine + TLC + Tyr + + TGC + + OLC + C + Name + Cysteine + TLC + Cys + + TTA + + OLC + L + Name + Leucine + TLC + Leu + + TCA + + OLC + S + Name + Serine + TLC + Ser + + TAA + + OLC + * + Name + termination codon + TLC + Ter + + TGA + + OLC + W + Name + Tryptophan + TLC + Trp + + TTG + + OLC + L + Name + Leucine + TLC + Leu + + TCG + + OLC + S + Name + Serine + TLC + Ser + + TAG + + OLC + * + Name + termination codon + TLC + Ter + + TGG + + OLC + W + Name + Tryptophan + TLC + Trp + + CTT + + OLC + L + Name + Leucine + TLC + Leu + + CCT + + OLC + P + Name + Proline + TLC + Pro + + CAT + + OLC + H + Name + Histidine + TLC + His + + CGT + + OLC + R + Name + Arginine + TLC + Arg + + CTC + + OLC + L + Name + Leucine + TLC + Leu + + CCC + + OLC + P + Name + Proline + TLC + Pro + + CAC + + OLC + H + Name + Histidine + TLC + His + + CGC + + OLC + R + Name + Arginine + TLC + Arg + + CTA + + OLC + L + Name + Leucine + TLC + Leu + + CCA + + OLC + P + Name + Proline + TLC + Pro + + CAA + + OLC + Q + Name + Glutamine + TLC + Gln + + CGA + + OLC + R + Name + Arginine + TLC + Arg + + CTG + + OLC + L + Name + Leucine + TLC + Leu + + CCG + + OLC + P + Name + Proline + TLC + Pro + + CAG + + OLC + Q + Name + Glutamine + TLC + Gln + + CGG + + OLC + R + Name + Arginine + TLC + Arg + + ATT + + OLC + I + Name + Isoleucine + TLC + Ile + + ACT + + OLC + T + Name + Threonine + TLC + Thr + + AAT + + OLC + N + Name + Asparagine + TLC + Asn + + AGT + + OLC + S + Name + Serine + TLC + Ser + + ATC + + OLC + I + Name + Isoleucine + TLC + Ile + + ACC + + OLC + T + Name + Threonine + TLC + Thr + + AAC + + OLC + N + Name + Asparagine + TLC + Asn + + AGC + + OLC + S + Name + Serine + TLC + Ser + + ATA + + OLC + I + Name + Isoleucine + TLC + Ile + + ACA + + OLC + T + Name + Threonine + TLC + Thr + + AAA + + OLC + K + Name + Lysine + TLC + Lys + + AGA + + OLC + R + Name + Arginine + TLC + Arg + + ATG + + OLC + M + Name + Methionine + TLC + Met + + ACG + + OLC + T + Name + Threonine + TLC + Thr + + AAG + + OLC + K + Name + Lysine + TLC + Lys + + AGG + + OLC + R + Name + Arginine + TLC + Arg + + GTT + + OLC + V + Name + Valine + TLC + Val + + GCT + + OLC + A + Name + Alanine + TLC + Ala + + GAT + + OLC + D + Name + Aspartate + TLC + Asp + Alt + Aspartic acid + + GGT + + OLC + G + Name + Glycine + TLC + Gly + + GTC + + OLC + V + Name + Valine + TLC + Val + + GCC + + OLC + A + Name + Alanine + TLC + Ala + + GAC + + OLC + D + Name + Aspartate + TLC + Asp + Alt + Aspartic acid + + GGC + + OLC + G + Name + Glycine + TLC + Gly + + GTA + + OLC + V + Name + Valine + TLC + Val + + GCA + + OLC + A + Name + Alanine + TLC + Ala + + GAA + + OLC + E + Name + Glutamate + TLC + Glu + Alt + Glutamic acid + + GGA + + OLC + G + Name + Glycine + TLC + Gly + + GTG + + OLC + V + Name + Valine + TLC + Val + + GCG + + OLC + A + Name + Alanine + TLC + Ala + + GAG + + OLC + E + Name + Glutamate + TLC + Glu + Alt + Glutamic acid + + GGG + + OLC + G + Name + Glycine + TLC + Gly + + + + diff --git a/Seqotron/GeneticCodeTables/Pterobranchia-Mitochondrial.plist b/Seqotron/GeneticCodeTables/Pterobranchia-Mitochondrial.plist new file mode 100755 index 0000000..f37d2c9 --- /dev/null +++ b/Seqotron/GeneticCodeTables/Pterobranchia-Mitochondrial.plist @@ -0,0 +1,595 @@ + + + + + Description + Pterobranchia Mitochondrial + Table + + TTT + + OLC + F + Name + Phenylalanine + TLC + Phe + + TCT + + OLC + S + Name + Serine + TLC + Ser + + TAT + + OLC + Y + Name + Tyrosine + TLC + Tyr + + TGT + + OLC + C + Name + Cysteine + TLC + Cys + + TTC + + OLC + F + Name + Phenylalanine + TLC + Phe + + TCC + + OLC + S + Name + Serine + TLC + Ser + + TAC + + OLC + Y + Name + Tyrosine + TLC + Tyr + + TGC + + OLC + C + Name + Cysteine + TLC + Cys + + TTA + + OLC + L + Name + Leucine + TLC + Leu + + TCA + + OLC + S + Name + Serine + TLC + Ser + + TAA + + OLC + * + Name + termination codon + TLC + Ter + + TGA + + OLC + W + Name + Tryptophan + TLC + Trp + + TTG + + OLC + L + Name + Leucine + TLC + Leu + + TCG + + OLC + S + Name + Serine + TLC + Ser + + TAG + + OLC + * + Name + termination codon + TLC + Ter + + TGG + + OLC + W + Name + Tryptophan + TLC + Trp + + CTT + + OLC + L + Name + Leucine + TLC + Leu + + CCT + + OLC + P + Name + Proline + TLC + Pro + + CAT + + OLC + H + Name + Histidine + TLC + His + + CGT + + OLC + R + Name + Arginine + TLC + Arg + + CTC + + OLC + L + Name + Leucine + TLC + Leu + + CCC + + OLC + P + Name + Proline + TLC + Pro + + CAC + + OLC + H + Name + Histidine + TLC + His + + CGC + + OLC + R + Name + Arginine + TLC + Arg + + CTA + + OLC + L + Name + Leucine + TLC + Leu + + CCA + + OLC + P + Name + Proline + TLC + Pro + + CAA + + OLC + Q + Name + Glutamine + TLC + Gln + + CGA + + OLC + R + Name + Arginine + TLC + Arg + + CTG + + OLC + L + Name + Leucine + TLC + Leu + + CCG + + OLC + P + Name + Proline + TLC + Pro + + CAG + + OLC + Q + Name + Glutamine + TLC + Gln + + CGG + + OLC + R + Name + Arginine + TLC + Arg + + ATT + + OLC + I + Name + Isoleucine + TLC + Ile + + ACT + + OLC + T + Name + Threonine + TLC + Thr + + AAT + + OLC + N + Name + Asparagine + TLC + Asn + + AGT + + OLC + S + Name + Serine + TLC + Ser + + ATC + + OLC + I + Name + Isoleucine + TLC + Ile + + ACC + + OLC + T + Name + Threonine + TLC + Thr + + AAC + + OLC + N + Name + Asparagine + TLC + Asn + + AGC + + OLC + S + Name + Serine + TLC + Ser + + ATA + + OLC + I + Name + Isoleucine + TLC + Ile + + ACA + + OLC + T + Name + Threonine + TLC + Thr + + AAA + + OLC + K + Name + Lysine + TLC + Lys + + AGA + + OLC + S + Name + Serine + TLC + Ser + + ATG + + OLC + M + Name + Methionine + TLC + Met + + ACG + + OLC + T + Name + Threonine + TLC + Thr + + AAG + + OLC + K + Name + Lysine + TLC + Lys + + AGG + + OLC + K + Name + Lysine + TLC + Lys + + GTT + + OLC + V + Name + Valine + TLC + Val + + GCT + + OLC + A + Name + Alanine + TLC + Ala + + GAT + + OLC + D + Name + Aspartate + TLC + Asp + Alt + Aspartic acid + + GGT + + OLC + G + Name + Glycine + TLC + Gly + + GTC + + OLC + V + Name + Valine + TLC + Val + + GCC + + OLC + A + Name + Alanine + TLC + Ala + + GAC + + OLC + D + Name + Aspartate + TLC + Asp + Alt + Aspartic acid + + GGC + + OLC + G + Name + Glycine + TLC + Gly + + GTA + + OLC + V + Name + Valine + TLC + Val + + GCA + + OLC + A + Name + Alanine + TLC + Ala + + GAA + + OLC + E + Name + Glutamate + TLC + Glu + Alt + Glutamic acid + + GGA + + OLC + G + Name + Glycine + TLC + Gly + + GTG + + OLC + V + Name + Valine + TLC + Val + + GCG + + OLC + A + Name + Alanine + TLC + Ala + + GAG + + OLC + E + Name + Glutamate + TLC + Glu + Alt + Glutamic acid + + GGG + + OLC + G + Name + Glycine + TLC + Gly + + + + diff --git a/Seqotron/GeneticCodeTables/Scenedesmus-obliquus-Mitochondrial.plist b/Seqotron/GeneticCodeTables/Scenedesmus-obliquus-Mitochondrial.plist new file mode 100755 index 0000000..43f4583 --- /dev/null +++ b/Seqotron/GeneticCodeTables/Scenedesmus-obliquus-Mitochondrial.plist @@ -0,0 +1,596 @@ + + + + + Description + Scenedesmus obliquus Mitochondrial + Table + + +TTT + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCT + + OLC + S + Name + Serine + TLC + Ser + +TAT + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGT + + OLC + C + Name + Cysteine + TLC + Cys + +TTC + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCC + + OLC + S + Name + Serine + TLC + Ser + +TAC + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGC + + OLC + C + Name + Cysteine + TLC + Cys + +TTA + + OLC + L + Name + Leucine + TLC + Leu + +TCA + + OLC + * + Name + termination codon + TLC + Ter + +TAA + + OLC + * + Name + termination codon + TLC + Ter + +TGA + + OLC + * + Name + termination codon + TLC + Ter + +TTG + + OLC + L + Name + Leucine + TLC + Leu + +TCG + + OLC + S + Name + Serine + TLC + Ser + +TAG + + OLC + L + Name + Leucine + TLC + Leu + +TGG + + OLC + W + Name + Tryptophan + TLC + Trp + +CTT + + OLC + L + Name + Leucine + TLC + Leu + +CCT + + OLC + P + Name + Proline + TLC + Pro + +CAT + + OLC + H + Name + Histidine + TLC + His + +CGT + + OLC + R + Name + Arginine + TLC + Arg + +CTC + + OLC + L + Name + Leucine + TLC + Leu + +CCC + + OLC + P + Name + Proline + TLC + Pro + +CAC + + OLC + H + Name + Histidine + TLC + His + +CGC + + OLC + R + Name + Arginine + TLC + Arg + +CTA + + OLC + L + Name + Leucine + TLC + Leu + +CCA + + OLC + P + Name + Proline + TLC + Pro + +CAA + + OLC + Q + Name + Glutamine + TLC + Gln + +CGA + + OLC + R + Name + Arginine + TLC + Arg + +CTG + + OLC + L + Name + Leucine + TLC + Leu + +CCG + + OLC + P + Name + Proline + TLC + Pro + +CAG + + OLC + Q + Name + Glutamine + TLC + Gln + +CGG + + OLC + R + Name + Arginine + TLC + Arg + +ATT + + OLC + I + Name + Isoleucine + TLC + Ile + +ACT + + OLC + T + Name + Threonine + TLC + Thr + +AAT + + OLC + N + Name + Asparagine + TLC + Asn + +AGT + + OLC + S + Name + Serine + TLC + Ser + +ATC + + OLC + I + Name + Isoleucine + TLC + Ile + +ACC + + OLC + T + Name + Threonine + TLC + Thr + +AAC + + OLC + N + Name + Asparagine + TLC + Asn + +AGC + + OLC + S + Name + Serine + TLC + Ser + +ATA + + OLC + I + Name + Isoleucine + TLC + Ile + +ACA + + OLC + T + Name + Threonine + TLC + Thr + +AAA + + OLC + K + Name + Lysine + TLC + Lys + +AGA + + OLC + R + Name + Arginine + TLC + Arg + +ATG + + OLC + M + Name + Methionine + TLC + Met + +ACG + + OLC + T + Name + Threonine + TLC + Thr + +AAG + + OLC + K + Name + Lysine + TLC + Lys + +AGG + + OLC + R + Name + Arginine + TLC + Arg + +GTT + + OLC + V + Name + Valine + TLC + Val + +GCT + + OLC + A + Name + Alanine + TLC + Ala + +GAT + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGT + + OLC + G + Name + Glycine + TLC + Gly + +GTC + + OLC + V + Name + Valine + TLC + Val + +GCC + + OLC + A + Name + Alanine + TLC + Ala + +GAC + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGC + + OLC + G + Name + Glycine + TLC + Gly + +GTA + + OLC + V + Name + Valine + TLC + Val + +GCA + + OLC + A + Name + Alanine + TLC + Ala + +GAA + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGA + + OLC + G + Name + Glycine + TLC + Gly + +GTG + + OLC + V + Name + Valine + TLC + Val + +GCG + + OLC + A + Name + Alanine + TLC + Ala + +GAG + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGG + + OLC + G + Name + Glycine + TLC + Gly + + + + \ No newline at end of file diff --git a/Seqotron/GeneticCodeTables/Standard.plist b/Seqotron/GeneticCodeTables/Standard.plist new file mode 100755 index 0000000..289bdab --- /dev/null +++ b/Seqotron/GeneticCodeTables/Standard.plist @@ -0,0 +1,596 @@ + + + + + Description + Standard + Table + + +TTT + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCT + + OLC + S + Name + Serine + TLC + Ser + +TAT + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGT + + OLC + C + Name + Cysteine + TLC + Cys + +TTC + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCC + + OLC + S + Name + Serine + TLC + Ser + +TAC + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGC + + OLC + C + Name + Cysteine + TLC + Cys + +TTA + + OLC + L + Name + Leucine + TLC + Leu + +TCA + + OLC + S + Name + Serine + TLC + Ser + +TAA + + OLC + * + Name + termination codon + TLC + Ter + +TGA + + OLC + * + Name + termination codon + TLC + Ter + +TTG + + OLC + L + Name + Leucine + TLC + Leu + +TCG + + OLC + S + Name + Serine + TLC + Ser + +TAG + + OLC + * + Name + termination codon + TLC + Ter + +TGG + + OLC + W + Name + Tryptophan + TLC + Trp + +CTT + + OLC + L + Name + Leucine + TLC + Leu + +CCT + + OLC + P + Name + Proline + TLC + Pro + +CAT + + OLC + H + Name + Histidine + TLC + His + +CGT + + OLC + R + Name + Arginine + TLC + Arg + +CTC + + OLC + L + Name + Leucine + TLC + Leu + +CCC + + OLC + P + Name + Proline + TLC + Pro + +CAC + + OLC + H + Name + Histidine + TLC + His + +CGC + + OLC + R + Name + Arginine + TLC + Arg + +CTA + + OLC + L + Name + Leucine + TLC + Leu + +CCA + + OLC + P + Name + Proline + TLC + Pro + +CAA + + OLC + Q + Name + Glutamine + TLC + Gln + +CGA + + OLC + R + Name + Arginine + TLC + Arg + +CTG + + OLC + L + Name + Leucine + TLC + Leu + +CCG + + OLC + P + Name + Proline + TLC + Pro + +CAG + + OLC + Q + Name + Glutamine + TLC + Gln + +CGG + + OLC + R + Name + Arginine + TLC + Arg + +ATT + + OLC + I + Name + Isoleucine + TLC + Ile + +ACT + + OLC + T + Name + Threonine + TLC + Thr + +AAT + + OLC + N + Name + Asparagine + TLC + Asn + +AGT + + OLC + S + Name + Serine + TLC + Ser + +ATC + + OLC + I + Name + Isoleucine + TLC + Ile + +ACC + + OLC + T + Name + Threonine + TLC + Thr + +AAC + + OLC + N + Name + Asparagine + TLC + Asn + +AGC + + OLC + S + Name + Serine + TLC + Ser + +ATA + + OLC + I + Name + Isoleucine + TLC + Ile + +ACA + + OLC + T + Name + Threonine + TLC + Thr + +AAA + + OLC + K + Name + Lysine + TLC + Lys + +AGA + + OLC + R + Name + Arginine + TLC + Arg + +ATG + + OLC + M + Name + Methionine + TLC + Met + +ACG + + OLC + T + Name + Threonine + TLC + Thr + +AAG + + OLC + K + Name + Lysine + TLC + Lys + +AGG + + OLC + R + Name + Arginine + TLC + Arg + +GTT + + OLC + V + Name + Valine + TLC + Val + +GCT + + OLC + A + Name + Alanine + TLC + Ala + +GAT + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGT + + OLC + G + Name + Glycine + TLC + Gly + +GTC + + OLC + V + Name + Valine + TLC + Val + +GCC + + OLC + A + Name + Alanine + TLC + Ala + +GAC + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGC + + OLC + G + Name + Glycine + TLC + Gly + +GTA + + OLC + V + Name + Valine + TLC + Val + +GCA + + OLC + A + Name + Alanine + TLC + Ala + +GAA + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGA + + OLC + G + Name + Glycine + TLC + Gly + +GTG + + OLC + V + Name + Valine + TLC + Val + +GCG + + OLC + A + Name + Alanine + TLC + Ala + +GAG + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGG + + OLC + G + Name + Glycine + TLC + Gly + + + + \ No newline at end of file diff --git a/Seqotron/GeneticCodeTables/Thraustochytrium-Mitochondrial.plist b/Seqotron/GeneticCodeTables/Thraustochytrium-Mitochondrial.plist new file mode 100755 index 0000000..4ecbd68 --- /dev/null +++ b/Seqotron/GeneticCodeTables/Thraustochytrium-Mitochondrial.plist @@ -0,0 +1,596 @@ + + + + + Description + Thraustochytrium Mitochondrial + Table + + +TTT + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCT + + OLC + S + Name + Serine + TLC + Ser + +TAT + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGT + + OLC + C + Name + Cysteine + TLC + Cys + +TTC + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCC + + OLC + S + Name + Serine + TLC + Ser + +TAC + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGC + + OLC + C + Name + Cysteine + TLC + Cys + +TTA + + OLC + * + Name + termination codon + TLC + Ter + +TCA + + OLC + S + Name + Serine + TLC + Ser + +TAA + + OLC + * + Name + termination codon + TLC + Ter + +TGA + + OLC + * + Name + termination codon + TLC + Ter + +TTG + + OLC + L + Name + Leucine + TLC + Leu + +TCG + + OLC + S + Name + Serine + TLC + Ser + +TAG + + OLC + * + Name + termination codon + TLC + Ter + +TGG + + OLC + W + Name + Tryptophan + TLC + Trp + +CTT + + OLC + L + Name + Leucine + TLC + Leu + +CCT + + OLC + P + Name + Proline + TLC + Pro + +CAT + + OLC + H + Name + Histidine + TLC + His + +CGT + + OLC + R + Name + Arginine + TLC + Arg + +CTC + + OLC + L + Name + Leucine + TLC + Leu + +CCC + + OLC + P + Name + Proline + TLC + Pro + +CAC + + OLC + H + Name + Histidine + TLC + His + +CGC + + OLC + R + Name + Arginine + TLC + Arg + +CTA + + OLC + L + Name + Leucine + TLC + Leu + +CCA + + OLC + P + Name + Proline + TLC + Pro + +CAA + + OLC + Q + Name + Glutamine + TLC + Gln + +CGA + + OLC + R + Name + Arginine + TLC + Arg + +CTG + + OLC + L + Name + Leucine + TLC + Leu + +CCG + + OLC + P + Name + Proline + TLC + Pro + +CAG + + OLC + Q + Name + Glutamine + TLC + Gln + +CGG + + OLC + R + Name + Arginine + TLC + Arg + +ATT + + OLC + I + Name + Isoleucine + TLC + Ile + +ACT + + OLC + T + Name + Threonine + TLC + Thr + +AAT + + OLC + N + Name + Asparagine + TLC + Asn + +AGT + + OLC + S + Name + Serine + TLC + Ser + +ATC + + OLC + I + Name + Isoleucine + TLC + Ile + +ACC + + OLC + T + Name + Threonine + TLC + Thr + +AAC + + OLC + N + Name + Asparagine + TLC + Asn + +AGC + + OLC + S + Name + Serine + TLC + Ser + +ATA + + OLC + I + Name + Isoleucine + TLC + Ile + +ACA + + OLC + T + Name + Threonine + TLC + Thr + +AAA + + OLC + K + Name + Lysine + TLC + Lys + +AGA + + OLC + R + Name + Arginine + TLC + Arg + +ATG + + OLC + M + Name + Methionine + TLC + Met + +ACG + + OLC + T + Name + Threonine + TLC + Thr + +AAG + + OLC + K + Name + Lysine + TLC + Lys + +AGG + + OLC + R + Name + Arginine + TLC + Arg + +GTT + + OLC + V + Name + Valine + TLC + Val + +GCT + + OLC + A + Name + Alanine + TLC + Ala + +GAT + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGT + + OLC + G + Name + Glycine + TLC + Gly + +GTC + + OLC + V + Name + Valine + TLC + Val + +GCC + + OLC + A + Name + Alanine + TLC + Ala + +GAC + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGC + + OLC + G + Name + Glycine + TLC + Gly + +GTA + + OLC + V + Name + Valine + TLC + Val + +GCA + + OLC + A + Name + Alanine + TLC + Ala + +GAA + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGA + + OLC + G + Name + Glycine + TLC + Gly + +GTG + + OLC + V + Name + Valine + TLC + Val + +GCG + + OLC + A + Name + Alanine + TLC + Ala + +GAG + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGG + + OLC + G + Name + Glycine + TLC + Gly + + + + \ No newline at end of file diff --git a/Seqotron/GeneticCodeTables/Trematode-Mitochondrial.plist b/Seqotron/GeneticCodeTables/Trematode-Mitochondrial.plist new file mode 100755 index 0000000..89f1f90 --- /dev/null +++ b/Seqotron/GeneticCodeTables/Trematode-Mitochondrial.plist @@ -0,0 +1,596 @@ + + + + + Description + Trematode Mitochondrial + Table + + +TTT + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCT + + OLC + S + Name + Serine + TLC + Ser + +TAT + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGT + + OLC + C + Name + Cysteine + TLC + Cys + +TTC + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCC + + OLC + S + Name + Serine + TLC + Ser + +TAC + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGC + + OLC + C + Name + Cysteine + TLC + Cys + +TTA + + OLC + L + Name + Leucine + TLC + Leu + +TCA + + OLC + S + Name + Serine + TLC + Ser + +TAA + + OLC + * + Name + termination codon + TLC + Ter + +TGA + + OLC + W + Name + Tryptophan + TLC + Trp + +TTG + + OLC + L + Name + Leucine + TLC + Leu + +TCG + + OLC + S + Name + Serine + TLC + Ser + +TAG + + OLC + * + Name + termination codon + TLC + Ter + +TGG + + OLC + W + Name + Tryptophan + TLC + Trp + +CTT + + OLC + L + Name + Leucine + TLC + Leu + +CCT + + OLC + P + Name + Proline + TLC + Pro + +CAT + + OLC + H + Name + Histidine + TLC + His + +CGT + + OLC + R + Name + Arginine + TLC + Arg + +CTC + + OLC + L + Name + Leucine + TLC + Leu + +CCC + + OLC + P + Name + Proline + TLC + Pro + +CAC + + OLC + H + Name + Histidine + TLC + His + +CGC + + OLC + R + Name + Arginine + TLC + Arg + +CTA + + OLC + L + Name + Leucine + TLC + Leu + +CCA + + OLC + P + Name + Proline + TLC + Pro + +CAA + + OLC + Q + Name + Glutamine + TLC + Gln + +CGA + + OLC + R + Name + Arginine + TLC + Arg + +CTG + + OLC + L + Name + Leucine + TLC + Leu + +CCG + + OLC + P + Name + Proline + TLC + Pro + +CAG + + OLC + Q + Name + Glutamine + TLC + Gln + +CGG + + OLC + R + Name + Arginine + TLC + Arg + +ATT + + OLC + I + Name + Isoleucine + TLC + Ile + +ACT + + OLC + T + Name + Threonine + TLC + Thr + +AAT + + OLC + N + Name + Asparagine + TLC + Asn + +AGT + + OLC + S + Name + Serine + TLC + Ser + +ATC + + OLC + I + Name + Isoleucine + TLC + Ile + +ACC + + OLC + T + Name + Threonine + TLC + Thr + +AAC + + OLC + N + Name + Asparagine + TLC + Asn + +AGC + + OLC + S + Name + Serine + TLC + Ser + +ATA + + OLC + M + Name + Methionine + TLC + Met + +ACA + + OLC + T + Name + Threonine + TLC + Thr + +AAA + + OLC + N + Name + Asparagine + TLC + Asn + +AGA + + OLC + S + Name + Serine + TLC + Ser + +ATG + + OLC + M + Name + Methionine + TLC + Met + +ACG + + OLC + T + Name + Threonine + TLC + Thr + +AAG + + OLC + K + Name + Lysine + TLC + Lys + +AGG + + OLC + S + Name + Serine + TLC + Ser + +GTT + + OLC + V + Name + Valine + TLC + Val + +GCT + + OLC + A + Name + Alanine + TLC + Ala + +GAT + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGT + + OLC + G + Name + Glycine + TLC + Gly + +GTC + + OLC + V + Name + Valine + TLC + Val + +GCC + + OLC + A + Name + Alanine + TLC + Ala + +GAC + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGC + + OLC + G + Name + Glycine + TLC + Gly + +GTA + + OLC + V + Name + Valine + TLC + Val + +GCA + + OLC + A + Name + Alanine + TLC + Ala + +GAA + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGA + + OLC + G + Name + Glycine + TLC + Gly + +GTG + + OLC + V + Name + Valine + TLC + Val + +GCG + + OLC + A + Name + Alanine + TLC + Ala + +GAG + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGG + + OLC + G + Name + Glycine + TLC + Gly + + + + \ No newline at end of file diff --git a/Seqotron/GeneticCodeTables/Yeast-Mitochondrial.plist b/Seqotron/GeneticCodeTables/Yeast-Mitochondrial.plist new file mode 100755 index 0000000..fb42c9f --- /dev/null +++ b/Seqotron/GeneticCodeTables/Yeast-Mitochondrial.plist @@ -0,0 +1,596 @@ + + + + + Description + Yeast Mitochondrial + Table + + +TTT + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCT + + OLC + S + Name + Serine + TLC + Ser + +TAT + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGT + + OLC + C + Name + Cysteine + TLC + Cys + +TTC + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCC + + OLC + S + Name + Serine + TLC + Ser + +TAC + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGC + + OLC + C + Name + Cysteine + TLC + Cys + +TTA + + OLC + L + Name + Leucine + TLC + Leu + +TCA + + OLC + S + Name + Serine + TLC + Ser + +TAA + + OLC + * + Name + termination codon + TLC + Ter + +TGA + + OLC + W + Name + Tryptophan + TLC + Trp + +TTG + + OLC + L + Name + Leucine + TLC + Leu + +TCG + + OLC + S + Name + Serine + TLC + Ser + +TAG + + OLC + * + Name + termination codon + TLC + Ter + +TGG + + OLC + W + Name + Tryptophan + TLC + Trp + +CTT + + OLC + T + Name + Threonine + TLC + Thr + +CCT + + OLC + P + Name + Proline + TLC + Pro + +CAT + + OLC + H + Name + Histidine + TLC + His + +CGT + + OLC + R + Name + Arginine + TLC + Arg + +CTC + + OLC + T + Name + Threonine + TLC + Thr + +CCC + + OLC + P + Name + Proline + TLC + Pro + +CAC + + OLC + H + Name + Histidine + TLC + His + +CGC + + OLC + R + Name + Arginine + TLC + Arg + +CTA + + OLC + T + Name + Threonine + TLC + Thr + +CCA + + OLC + P + Name + Proline + TLC + Pro + +CAA + + OLC + Q + Name + Glutamine + TLC + Gln + +CGA + + OLC + R + Name + Arginine + TLC + Arg + +CTG + + OLC + T + Name + Threonine + TLC + Thr + +CCG + + OLC + P + Name + Proline + TLC + Pro + +CAG + + OLC + Q + Name + Glutamine + TLC + Gln + +CGG + + OLC + R + Name + Arginine + TLC + Arg + +ATT + + OLC + I + Name + Isoleucine + TLC + Ile + +ACT + + OLC + T + Name + Threonine + TLC + Thr + +AAT + + OLC + N + Name + Asparagine + TLC + Asn + +AGT + + OLC + S + Name + Serine + TLC + Ser + +ATC + + OLC + I + Name + Isoleucine + TLC + Ile + +ACC + + OLC + T + Name + Threonine + TLC + Thr + +AAC + + OLC + N + Name + Asparagine + TLC + Asn + +AGC + + OLC + S + Name + Serine + TLC + Ser + +ATA + + OLC + M + Name + Methionine + TLC + Met + +ACA + + OLC + T + Name + Threonine + TLC + Thr + +AAA + + OLC + K + Name + Lysine + TLC + Lys + +AGA + + OLC + R + Name + Arginine + TLC + Arg + +ATG + + OLC + M + Name + Methionine + TLC + Met + +ACG + + OLC + T + Name + Threonine + TLC + Thr + +AAG + + OLC + K + Name + Lysine + TLC + Lys + +AGG + + OLC + R + Name + Arginine + TLC + Arg + +GTT + + OLC + V + Name + Valine + TLC + Val + +GCT + + OLC + A + Name + Alanine + TLC + Ala + +GAT + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGT + + OLC + G + Name + Glycine + TLC + Gly + +GTC + + OLC + V + Name + Valine + TLC + Val + +GCC + + OLC + A + Name + Alanine + TLC + Ala + +GAC + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGC + + OLC + G + Name + Glycine + TLC + Gly + +GTA + + OLC + V + Name + Valine + TLC + Val + +GCA + + OLC + A + Name + Alanine + TLC + Ala + +GAA + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGA + + OLC + G + Name + Glycine + TLC + Gly + +GTG + + OLC + V + Name + Valine + TLC + Val + +GCG + + OLC + A + Name + Alanine + TLC + Ala + +GAG + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGG + + OLC + G + Name + Glycine + TLC + Gly + + + + \ No newline at end of file diff --git a/Seqotron/GeneticCodeTables/vertebrate-mitochondrial.plist b/Seqotron/GeneticCodeTables/vertebrate-mitochondrial.plist new file mode 100755 index 0000000..6d52ed9 --- /dev/null +++ b/Seqotron/GeneticCodeTables/vertebrate-mitochondrial.plist @@ -0,0 +1,596 @@ + + + + + Description + Vertebrate Mitochondrial + Table + + +TTT + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCT + + OLC + S + Name + Serine + TLC + Ser + +TAT + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGT + + OLC + C + Name + Cysteine + TLC + Cys + +TTC + + OLC + F + Name + Phenylalanine + TLC + Phe + +TCC + + OLC + S + Name + Serine + TLC + Ser + +TAC + + OLC + Y + Name + Tyrosine + TLC + Tyr + +TGC + + OLC + C + Name + Cysteine + TLC + Cys + +TTA + + OLC + L + Name + Leucine + TLC + Leu + +TCA + + OLC + S + Name + Serine + TLC + Ser + +TAA + + OLC + * + Name + termination codon + TLC + Ter + +TGA + + OLC + W + Name + Tryptophan + TLC + Trp + +TTG + + OLC + L + Name + Leucine + TLC + Leu + +TCG + + OLC + S + Name + Serine + TLC + Ser + +TAG + + OLC + * + Name + termination codon + TLC + Ter + +TGG + + OLC + W + Name + Tryptophan + TLC + Trp + +CTT + + OLC + L + Name + Leucine + TLC + Leu + +CCT + + OLC + P + Name + Proline + TLC + Pro + +CAT + + OLC + H + Name + Histidine + TLC + His + +CGT + + OLC + R + Name + Arginine + TLC + Arg + +CTC + + OLC + L + Name + Leucine + TLC + Leu + +CCC + + OLC + P + Name + Proline + TLC + Pro + +CAC + + OLC + H + Name + Histidine + TLC + His + +CGC + + OLC + R + Name + Arginine + TLC + Arg + +CTA + + OLC + L + Name + Leucine + TLC + Leu + +CCA + + OLC + P + Name + Proline + TLC + Pro + +CAA + + OLC + Q + Name + Glutamine + TLC + Gln + +CGA + + OLC + R + Name + Arginine + TLC + Arg + +CTG + + OLC + L + Name + Leucine + TLC + Leu + +CCG + + OLC + P + Name + Proline + TLC + Pro + +CAG + + OLC + Q + Name + Glutamine + TLC + Gln + +CGG + + OLC + R + Name + Arginine + TLC + Arg + +ATT + + OLC + I + Name + Isoleucine + TLC + Ile + +ACT + + OLC + T + Name + Threonine + TLC + Thr + +AAT + + OLC + N + Name + Asparagine + TLC + Asn + +AGT + + OLC + S + Name + Serine + TLC + Ser + +ATC + + OLC + I + Name + Isoleucine + TLC + Ile + +ACC + + OLC + T + Name + Threonine + TLC + Thr + +AAC + + OLC + N + Name + Asparagine + TLC + Asn + +AGC + + OLC + S + Name + Serine + TLC + Ser + +ATA + + OLC + M + Name + Methionine + TLC + Met + +ACA + + OLC + T + Name + Threonine + TLC + Thr + +AAA + + OLC + K + Name + Lysine + TLC + Lys + +AGA + + OLC + * + Name + termination codon + TLC + Ter + +ATG + + OLC + M + Name + Methionine + TLC + Met + +ACG + + OLC + T + Name + Threonine + TLC + Thr + +AAG + + OLC + K + Name + Lysine + TLC + Lys + +AGG + + OLC + * + Name + termination codon + TLC + Ter + +GTT + + OLC + V + Name + Valine + TLC + Val + +GCT + + OLC + A + Name + Alanine + TLC + Ala + +GAT + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGT + + OLC + G + Name + Glycine + TLC + Gly + +GTC + + OLC + V + Name + Valine + TLC + Val + +GCC + + OLC + A + Name + Alanine + TLC + Ala + +GAC + + OLC + D + Name + Aspartate + TLC + Asp +Alt +Aspartic acid + +GGC + + OLC + G + Name + Glycine + TLC + Gly + +GTA + + OLC + V + Name + Valine + TLC + Val + +GCA + + OLC + A + Name + Alanine + TLC + Ala + +GAA + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGA + + OLC + G + Name + Glycine + TLC + Gly + +GTG + + OLC + V + Name + Valine + TLC + Val + +GCG + + OLC + A + Name + Alanine + TLC + Ala + +GAG + + OLC + E + Name + Glutamate + TLC + Glu +Alt +Glutamic acid + +GGG + + OLC + G + Name + Glycine + TLC + Gly + + + + \ No newline at end of file diff --git a/Seqotron/Images.xcassets/AppIcon.appiconset/Contents.json b/Seqotron/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100755 index 0000000..7cd4f8e --- /dev/null +++ b/Seqotron/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "icon_16x16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "icon_16x16@2x.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "icon_32x32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "icon_32x32@2x.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "icon_128x128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "icon_128x128@2x.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "icon_256x256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "icon_256x256@2x.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "icon_512x512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "icon_512x512@2x.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Seqotron/Images.xcassets/AppIcon.appiconset/icon_128x128.png b/Seqotron/Images.xcassets/AppIcon.appiconset/icon_128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..35347a0aefda048efc393913bc5be37b10fae4cf GIT binary patch literal 21253 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

ZaG40ZmJ4QD00Z&u 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 O00009uDwTa%S(&Fz1;#`4*Vi|r zRPaL%1OkC3hts`laA?RFi%mcxkwB}}Kq{3%Bos``CAcKU$HU<0E|G{)TvX&Xn~b}& zva$~RC-9kIWRF1hppVQ z(NbD^^g9kXovz|`vL1=XB>4tC!r=(KK@DDdy&3zSZNz{xh?c7!h{Ius#{3xZ1W;@> zptX4bc?H=_M_EG^`a?-HF0r8B9>n3lUdB_8*2AC;BQsOW;|fVG$<3%JFE^PA25)fS z{C_TV4%wYHYncV3W25kfGT|NLV9d+FQQCD@Jc4+i1C=Z4aN;W;TQhfdJ}!|{-(IWc z=+X8`3*;6vZ|xQLjL1gYg?1k<*^wIc^DlX zMN(wIi*H?pL?(eQO99oe7cSp8y7fhHrleSTM>)>@?Exs&a@@aiCLUT>3uA$nJ>z9~ z*>GzwL?RJ-93iGFMX4P2dMLFy7#SI>Ht2uDzIkeFTvS4xhgBIX)YV!>i0fW|09`!a|J7GSKfChxc+P#JM^q`~2ezVUG(i zcCi(U?yAM&`U13c`_V8fhY=RfsvO4&gj5>K`CJKzRY#&iy!WLYMFkqDljQjwby!eM zL=@v#$sIrV0h*D8y1AuPo+65$h}}&_4K}W=M~fgEAH8u3rhE-FY8e_A6rpz6EI3>N z1R@EXYW83>Dh3aHnN*6&_~ddS)u)9IiGVz4wMU`JRKOF6o+|DDhK@oqoqI`#!BinG|7 zR(s@|=B45tu?Uk%jx`qEfk+~a`BetQlT&PWYi=vUo%4(E+6O(jbgm0=r5cU%%=oz3 zjWx?GOlC?fMu;Lbvs6b>k~0}zwz)ruw|=t(85#wXyEYVphQ9MQ_y~vz$UI)e{0~YA z#H1)eMX|Ix4`(QcRn>)fa77tLLUCNRhltygFy!cQ@}djTNn%Q^92G^Gq+E!Xj(4E4 zP>VK_n>SO>WPP+D$7qc0+@vmq)uhF}6fOUFjM?InN!s+(bq56WlF*+He;m5(IFjU0 zPp1d#UOmsk#bcn*>4x2;L}QHs7Lx|&yT=GK30xcuqSCCT(iE}p{PnHu=0lg*+Mv^e z657a#7P{-CWJ?3S?1={<N|n>UIU%H8eN1}t4s4OuDyM>v6SJc)2JeZ3NB=X~U}Dk&jf%4Fs&o`LQ6 z+OWu$12gdkuS+f$_+5uW7m$8*D|FC?ImagN&Z*AW2>8X1;Yvk=P8MQuhJ zfva68RiqK`w?iBDW7#YN<}R;8p-zF}2!{{a$Kbfo3Q>gx4b(rL`*a9vsx+|WD&P+U z_+CIWcZK28e5lp77@+NsKh z-BtykxrE*MciL0{6LrpVy$Z)W{Fq;$WB{z2X~gk1KMYxN+-1`;8Yb%JrzAITn}INq zNbHV8A{ZF3Lnf19M}rRk=nk^b-+K2Poci<{ygg3rIqShdh(Jh0^=xA?!@$Gyjd)|N z4Hw-}ob2-BnSVJT9T~ydGo5I9cqz=;a$NMruyb)Pqu*hl5Bx|UjYhUlx8sK%eBeNQ zVxrAQ3;ls0781ccR;NXT49zyG@YD4R8GyKpmtjoDv2unE3yL(1jM^yI=UfrECQ{JG zqfCYYb@Qzlr%K^S|IS4^%%_w_V^OBx?RK}3&7(I=MEei^sW>A;(`_{7OR8s8K_-{s z@0|hs(~*QqD#Di99Gs$Qxb4~yq@H26rkDoP*1i!cUnzCU1PW`dxPNgOdI@~TIsyn$ z%C=Uiu)17Jtv-dmz5&=B4$kB8ly80P(ZTNwr3VhZz9BO+^GJSvK4w+TWa&8P2;=!H zQDl*kHI&12dI|%?1$BA_qTCcTG9jGf984J!_^4#V#P#p@hN#3+_-$K~?^wR?wS!GLIXV0J0fnYl;`JQr-7&n~8-qI`Mh7vVE0TnkqEePA zg>Qn4PNvz~5KTx64RTnOX{@)Zp`ghp7!2|QFFVlm_YZ7+;_=50{NTal)qVRn(2?p* znh_;=bilA!N+6{pSv)?8b_#C`4XY6%s{l2PK`Oc0OcB�qV2Gn2{}moFbQ`2soTh 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 literal 0 HcmV?d00001 diff --git a/Seqotron/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png b/Seqotron/Images.xcassets/AppIcon.appiconset/icon_256x256@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fda56368afd368c05dc4a91e7df8b7df53a81f1f GIT binary patch literal 89576 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 KALX_?E$e?stZ$G2 literal 0 HcmV?d00001 diff --git a/Seqotron/Images.xcassets/AppIcon.appiconset/icon_32x32.png b/Seqotron/Images.xcassets/AppIcon.appiconset/icon_32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..7e03d7df60f8522ed832d103874e28cd63504b76 GIT binary patch literal 2945 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 literal 0 HcmV?d00001 diff --git a/Seqotron/Images.xcassets/AppIcon.appiconset/icon_512x512.png b/Seqotron/Images.xcassets/AppIcon.appiconset/icon_512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..d6068b38808e1bc0732917376fddcb72db2ede68 GIT binary patch literal 89121 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�Nc^W?t_fct`V2>l;I=kbf>uLPrX!%D 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

w3tEThy*mTf-1oGl<^wD@#xYm6x%oS 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 literal 0 HcmV?d00001 diff --git a/Seqotron/MFAbstractSequencesView.h b/Seqotron/MFAbstractSequencesView.h new file mode 100755 index 0000000..6450df1 --- /dev/null +++ b/Seqotron/MFAbstractSequencesView.h @@ -0,0 +1,78 @@ +// +// MFAbstractSequencesView.h +// Seqotron +// +// Created by Mathieu Fourment on 10/09/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +extern NSString *MFSequenceViewSequencesBindingName; +extern NSString *MFSequenceViewSelectionIndexesBindingName; +extern NSString *MFSequenceViewFontSizeBindingName; + +extern NSString *MFSequenceViewSequencesObservationContext; +extern NSString *MFSequenceViewSelectionIndexesObservationContext; + +@interface MFAbstractSequencesView : NSView{ + // Information that is recorded when the "graphics" and "selectionIndexes" bindings are established. Notice that we don't keep around copies of the actual graphics array and selection indexes. Those would just be unnecessary (as far as we know, so far, without having ever done any relevant performance measurement) caches of values that really live in the bound-to objects. + NSObject *_sequencesContainer; + NSString *_sequencesKeyPath; + NSObject *_selectionIndexesContainer; + NSString *_selectionIndexesKeyPath; + + + CGFloat _fontSize; + NSString *_fontName; + CGFloat _rowSpacing; + CGFloat _rowHeight; + CGFloat _residueHeight; + CGFloat _residueWidth; + CGFloat _lineGap; + CGFloat _colSpacing; +} + + +@property (readwrite) CGFloat rowHeight; +@property (readwrite) CGFloat residueWidth; +@property (readwrite) CGFloat residueHeight; + +-(void)setFontSize:(NSNumber*)fontSize; + +-(void)setFontName:(NSString *)fontName; + +- (NSArray *)sequences; + +- (NSMutableArray *)mutableSequences; + +- (NSIndexSet *)selectionIndexes; + +- (void)changeSelectionIndexes:(NSIndexSet *)indexes; + + +// An override of the NSObject(NSKeyValueBindingCreation) method. +- (void)bind:(NSString *)bindingName toObject:(id)observableObject withKeyPath:(NSString *)observableKeyPath options:(NSDictionary *)options; + +// An override of the NSObject(NSKeyValueBindingCreation) method. +- (void)unbind:(NSString *)bindingName; + + +- (NSArray *)selectedSequences; + +@end diff --git a/Seqotron/MFAbstractSequencesView.m b/Seqotron/MFAbstractSequencesView.m new file mode 100755 index 0000000..89b265b --- /dev/null +++ b/Seqotron/MFAbstractSequencesView.m @@ -0,0 +1,269 @@ +// +// MFAbstractSequencesView.m +// Seqotron +// +// Created by Mathieu Fourment on 10/09/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFAbstractSequencesView.h" + +// The names of the bindings supported by this class, in addition to the ones whose support is inherited from NSView. +NSString *MFSequenceViewSequencesBindingName = @"sequences"; +NSString *MFSequenceViewSelectionIndexesBindingName = @"selectionIndexes"; +NSString *MFSequenceViewFontNameBindingName = @"fontName"; +NSString *MFSequenceViewFontSizeBindingName = @"fontSize"; + +NSString *MFSequenceViewSequencesObservationContext = @"tk.phylogenetics.MFSequenceView.sequences"; +NSString *MFSequenceViewSelectionIndexesObservationContext = @"tk.phylogenetics.MFSequenceView.selectionIndexes"; + +@implementation MFAbstractSequencesView + + +@synthesize rowHeight = _rowHeight; +@synthesize residueWidth = _residueWidth; +@synthesize residueHeight = _residueHeight; + +- (id)initWithFrame:(NSRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + NSLog(@"MFAbstractSequencesView initWithFrame"); + _fontSize = [[[NSUserDefaults standardUserDefaults] objectForKey:@"MFDefaultSequenceFontSize"]floatValue]; + _fontName = [[[NSUserDefaults standardUserDefaults] objectForKey:@"MFDefaultSequenceFontName"]copy]; + _rowSpacing = [[[NSUserDefaults standardUserDefaults] objectForKey:@"MFDefaultSequenceRowSpacing"]floatValue]; + _colSpacing = [[[NSUserDefaults standardUserDefaults] objectForKey:@"MFDefaultSequenceColumnSpacing"]floatValue]; + [self initSize]; + } + return self; +} + +-(void)awakeFromNib { + NSLog(@"MFAbstractSequencesView awakeFromNib"); + _fontSize = [[[NSUserDefaults standardUserDefaults] objectForKey:@"MFDefaultSequenceFontSize"]floatValue]; + _fontName = [[[NSUserDefaults standardUserDefaults] objectForKey:@"MFDefaultSequenceFontName"]copy]; + _rowSpacing = [[[NSUserDefaults standardUserDefaults] objectForKey:@"MFDefaultSequenceRowSpacing"]floatValue]; + _colSpacing = [[[NSUserDefaults standardUserDefaults] objectForKey:@"MFDefaultSequenceColumnSpacing"]floatValue]; + [self initSize]; +} + +-(void)dealloc{ + // Stop observing objects for the bindings whose support isn't implemented using NSObject's default implementations. + [self unbind:MFSequenceViewSequencesBindingName]; + [self unbind:MFSequenceViewSelectionIndexesBindingName]; + [_fontName release]; + [super dealloc]; +} + +- (void)drawRect:(NSRect)dirtyRect +{ + [super drawRect:dirtyRect]; + + // Drawing code here. +} + +-(void)setFontSize:(NSNumber*)fontSize{ + if( _fontSize!= [fontSize floatValue] ){ + _fontSize = [fontSize floatValue]; + [self initSize]; + [self setNeedsDisplay:YES]; + } +} + +-(NSNumber*)fontSize{ + return [NSNumber numberWithFloat:_fontSize]; +} + +-(void)setFontName:(NSString *)fontName{ + if( ![fontName isEqualToString:_fontName] ){ + [_fontName release]; + _fontName = [fontName copy]; + [self initSize]; + [self setNeedsDisplay:YES]; + } +} + +-(void)initSize{ + NSMutableDictionary *attsDict = [[NSMutableDictionary alloc] init]; + NSFont *font = [NSFont fontWithName:_fontName size:_fontSize]; + [attsDict setObject:font forKey:NSFontAttributeName]; + + NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:@"A" attributes:attsDict]; + //_rowHeight = [font capHeight]+_rowSpacing;//[string size].height - 4; + + _lineGap = [string size].height+[font descender] - [font capHeight]; + _rowHeight = [string size].height - _lineGap; + _residueHeight = [font capHeight]; + _residueWidth = [string size].width; + [string release]; + [attsDict release]; + +} + +- (NSArray *)sequences { + + // A graphic view doesn't hold onto an array of the graphics it's presenting. That would be a cache that hasn't been justified by performance measurement (not yet anyway). Get the array of graphics from the bound-to object (an array controller, in Sketch's case). It's poor practice for a method that returns a collection to return nil, so never return nil. + NSArray *sequences = [_sequencesContainer valueForKeyPath:_sequencesKeyPath]; + if (!sequences) { + sequences = [NSArray array]; + } + return sequences; + +} + +- (NSMutableArray *)mutableSequences { + + // Get a mutable array of graphics from the bound-to object (an array controller, in Sketch's case). The bound-to object is responsible for being KVO-compliant enough that all observers of the bound-to property get notified of whatever mutation we perform on the returned array. Trying to mutate the graphics of a graphic view whose graphics aren't bound to anything is a programming error. + NSAssert((_sequencesContainer && _sequencesKeyPath), @"An MFAbstractSequencesView's 'sequence' property is not bound to anything."); + NSMutableArray *mutableSequences = [_sequencesContainer mutableArrayValueForKeyPath:_sequencesKeyPath]; + return mutableSequences; + +} + +- (NSIndexSet *)selectionIndexes { + + // A graphic view doesn't hold onto the selection indexes. That would be a cache that hasn't been justified by performance measurement (not yet anyway). + // Get the selection indexes from the bound-to object (an array controller, in Sketch's case). It's poor practice for a method that returns a collection + // (and an index set is a collection) to return nil, so never return nil. + NSIndexSet *selectionIndexes = [_selectionIndexesContainer valueForKeyPath:_selectionIndexesKeyPath]; + if (!selectionIndexes) { + selectionIndexes = [NSIndexSet indexSet]; + } + return selectionIndexes; + +} + +/* Why isn't this method called -setSelectionIndexes:? Mostly to encourage a naming convention that's useful for a few reasons: + + NSObject's default implementation of key-value binding (KVB) uses key-value coding (KVC) to invoke methods like -set: on the bound object when the bound-to property changes, to make it simple to support binding in the simple case of a view property that affects the way a view is drawn but whose value isn't directly manipulated by the user. If NSObject's default implementation of KVB were good enough to use for this "selectionIndexes" property maybe we _would_ implement a -setSelectionIndexes: method instead of stuffing so much code in -observeValueForKeyPath:ofObject:change:context: down below (but it's not, because it doesn't provide a way to get at the old and new selection indexes when they change). So, this method isn't here to take advantage of NSObject's default implementation of KVB. It's here to centralize the bindings work that must be done when the user changes the selection (check out all of the places it's invoked down below). Hopefully the different verb in this method name is a good reminder of the distinction. + + A person who assumes that a -set... method always succeeds, and always sets the exact value that was passed in (or throws an exception for invalid values to signal the need for some debugging), isn't assuming anything unreasonable. Setters that invalidate that assumption make a class' interface unnecessarily unpredictable and hard to program against. Sometimes they require people to write code that sets a value and then gets it right back again to keep multiple copies of the value synchronized, in case the setting didn't "take." So, avoid that. When validation is appropriate don't put it in your setter. Instead, implement a separate validation method. Follow the naming pattern established by KVC's -validateValue:forKey:error: when applicable. Now, _this_ method can't guarantee that, when it's invoked, an immediately subsequent invocation of -selectionIndexes will return the passed-in value. It's supposed to set the value of a property in the bound-to object using KVC, but only after asking the bound-to object to validate the value. So, again, -setSelectionIndexes: wouldn't be a very good name for it. + + */ +- (void)changeSelectionIndexes:(NSIndexSet *)indexes { + + // After all of that talk, this method isn't invoking -validateValue:forKeyPath:error:. It will, once we come up with an example of invalid selection indexes for this case. + + // It will also someday take any value transformer specified as a binding option into account, so you have an example of how to do that. + + // Set the selection index set in the bound-to object (an array controller, in Sketch's case). The bound-to object is responsible for being KVO-compliant enough that all observers of the bound-to property get notified of the setting. Trying to set the selection indexes of a graphic view whose selection indexes aren't bound to anything is a programming error. + NSAssert((_selectionIndexesContainer && _selectionIndexesKeyPath), @"An MFNamesView's 'selectionIndexes' property is not bound to anything."); + [_selectionIndexesContainer setValue:indexes forKeyPath:_selectionIndexesKeyPath]; + +} + + +// An override of the NSObject(NSKeyValueBindingCreation) method. +- (void)bind:(NSString *)bindingName toObject:(id)observableObject withKeyPath:(NSString *)observableKeyPath options:(NSDictionary *)options { + + // SKTGraphicView supports several different bindings. + if ([bindingName isEqualToString:MFSequenceViewSequencesBindingName]) { + + // We don't have any options to support for our custom "graphics" binding. + NSAssert(([options count]==0), @"MFSequenceView doesn't support any options for the 'sequences' binding."); + + // Rebinding is just as valid as resetting. + if (_sequencesContainer || _sequencesKeyPath) { + [self unbind:MFSequenceViewSequencesBindingName]; + } + + // Record the information about the binding. + _sequencesContainer = [observableObject retain]; + _sequencesKeyPath = [observableKeyPath copy]; + + // Start observing changes to the array of graphics to which we're bound, and also start observing properties of the graphics themselves that might require redrawing. + [_sequencesContainer addObserver:self forKeyPath:_sequencesKeyPath options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:MFSequenceViewSequencesObservationContext]; + //[self startObservingSequences:[_sequencesContainer valueForKeyPath:_sequencesKeyPath]]; + + // Redraw the whole view to make the binding take immediate visual effect. We could be much cleverer about this and just redraw the part of the view that needs it, but in typical usage the view isn't even visible yet, so that would probably be a waste of time (the programmer's and the computer's). If this view ever gets reused in some wildly dynamic situation where the bindings come and go we can reconsider optimization decisions like this then. + [self setNeedsDisplay:YES]; + //NSLog(@"ABstrctView binding %@ keypath %@",bindingName, _sequencesKeyPath); + + } else if ([bindingName isEqualToString:MFSequenceViewSelectionIndexesBindingName]) { + + // We don't have any options to support for our custom "selectionIndexes" binding either. Maybe in the future someone will imagine a use for a value transformer on this, and we'll add support for it then. + NSAssert(([options count]==0), @"MFSequenceView doesn't support any options for the 'selectionIndexes' binding."); + + // Rebinding is just as valid as resetting. + if (_selectionIndexesContainer || _selectionIndexesKeyPath) { + [self unbind:MFSequenceViewSelectionIndexesBindingName]; + } + + // Record the information about the binding. + _selectionIndexesContainer = [observableObject retain]; + _selectionIndexesKeyPath = [observableKeyPath copy]; + + // Start observing changes to the selection indexes to which we're bound. + [_selectionIndexesContainer addObserver:self forKeyPath:_selectionIndexesKeyPath options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:MFSequenceViewSelectionIndexesObservationContext]; + + // Same comment as above. + [self setNeedsDisplay:YES]; + //NSLog(@"ABstrctView binding %@",bindingName); + + } else { + + // For every binding except "graphics" and "selectionIndexes" just use NSObject's default implementation. It will start observing the bound-to property. When a KVO notification is sent for the bound-to property, this object will be sent a [self setValue:theNewValue forKey:theBindingName] message, so this class just has to be KVC-compliant for a key that is the same as the binding name, like "grid." That's why this class has a -setGrid: method. Also, NSView supports a few simple bindings of its own, and there's no reason to get in the way of those. + [super bind:bindingName toObject:observableObject withKeyPath:observableKeyPath options:options]; + + } + +} + +// An override of the NSObject(NSKeyValueBindingCreation) method. +- (void)unbind:(NSString *)bindingName { + + // SKTGraphicView supports several different bindings. For the ones that don't use NSObject's default implementation of key-value binding, undo what we do in -bind:toObject:withKeyPath:options:, and then redraw the whole view to make the unbinding take immediate visual effect. + if ([bindingName isEqualToString:MFSequenceViewSequencesBindingName]) { + //[self stopObservingSequences:[self sequences]]; + [_sequencesContainer removeObserver:self forKeyPath:_sequencesKeyPath]; + [_sequencesContainer release]; + _sequencesContainer = nil; + [_sequencesKeyPath release]; + _sequencesKeyPath = nil; + [self setNeedsDisplay:YES]; + } else if ([bindingName isEqualToString:MFSequenceViewSelectionIndexesBindingName]) { + [_selectionIndexesContainer removeObserver:self forKeyPath:_selectionIndexesKeyPath]; + [_selectionIndexesContainer release]; + _selectionIndexesContainer = nil; + [_selectionIndexesKeyPath release]; + _selectionIndexesKeyPath = nil; + [self setNeedsDisplay:YES]; + } else { + + // // For every binding except "graphics" and "selectionIndexes" just use NSObject's default implementation. Also, NSView supports a few simple bindings of its own, and there's no reason to get in the way of those. + [super unbind:bindingName]; + + } + +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(NSObject *)observedObject change:(NSDictionary *)change context:(void *)context { + + //NSLog(@"AbstactSequencesView observeValueForKeyPath %@",context); + [super observeValueForKeyPath:keyPath ofObject:observedObject change:change context:context]; + +} +// This doesn't contribute to any KVC or KVO compliance. It's just a convenience method that's invoked down below. +- (NSArray *)selectedSequences { + + // Simple, because we made sure -graphics and -selectionIndexes never return nil. + return [[self sequences] objectsAtIndexes:[self selectionIndexes]]; + +} + +@end diff --git a/Seqotron/MFAligner.xib b/Seqotron/MFAligner.xib new file mode 100644 index 0000000..4dd5f1a --- /dev/null +++ b/Seqotron/MFAligner.xib @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Seqotron/MFAlignerController.h b/Seqotron/MFAlignerController.h new file mode 100644 index 0000000..15d69a5 --- /dev/null +++ b/Seqotron/MFAlignerController.h @@ -0,0 +1,53 @@ +// +// MFAlignerController.h +// Seqotron +// +// Created by Mathieu Fourment on 7/03/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFDefines.h" +#import "MFOperationBuilder.h" + +@interface MFAlignerController : NSWindowController { + + NSString *_alignmentName; + NSArray *_sequences; + + NSString *_musclePath; + NSString *_mafftPath; + NSString *_tempPath; + MF2DRange _rangeSelection; +} + +@property NSUInteger indexTabView; +@property BOOL transalign; +@property BOOL transalignEnabled; + +@property (readwrite,copy) NSString *additionalCommands; + +// Muscle +@property NSInteger refine; + +- (id)initWithSequences:(NSArray *)sequences withName:(NSString*)name; + +- (void)setSelection:(MF2DRange)selection; + +@end diff --git a/Seqotron/MFAlignerController.m b/Seqotron/MFAlignerController.m new file mode 100644 index 0000000..fd88ebb --- /dev/null +++ b/Seqotron/MFAlignerController.m @@ -0,0 +1,227 @@ +// +// MFAlignerController.m +// Seqotron +// +// Created by Mathieu Fourment on 7/03/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFAlignerController.h" + +#import "MFSequenceWriter.h" +#import "MFSequenceSet.h" +#import "MFString.h" +#import "MFExternalOperation.h" +#import "MFOperationTransalign.h" + +@implementation MFAlignerController + +@synthesize indexTabView,refine,additionalCommands,transalign,transalignEnabled; + +- (id)initWithSequences:(NSArray *)sequences withName:(NSString*)name{ + if(self = [super initWithWindowNibName:@"MFAligner"]){ + + _sequences = [sequences retain]; + _musclePath = [[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"bin/muscle"] copy]; + _mafftPath = [[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"bin/mafft.bat"] copy];; + NSError *error = nil; + NSURL *cacheDir = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&error]; + + NSString *executableName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleExecutable"]; + _tempPath = [[NSString alloc]initWithString:[[[cacheDir path] stringByAppendingPathComponent:executableName]stringByAppendingPathComponent:@"temp"]]; + transalignEnabled = YES; + transalign = NO; + } + return self; +} + +-(void)dealloc{ + NSLog(@"MFAlignerController delloc"); + [_sequences release]; + + [_musclePath release]; + [_tempPath release]; + [_mafftPath release]; + + [super dealloc]; +} + +-(NSArray*)operations{ + NSMutableDictionary *options = [[NSMutableDictionary alloc]init]; + NSMutableArray *arguments = [[NSMutableArray alloc]init]; + + [options setObject:arguments forKey:MFExternalOperationArgumentsKey]; + [options setObject:@"MFDocument" forKey:MFOperationDocumentClassKey]; + + NSString *outputDirPath = [_tempPath stringByAppendingPathComponent:[NSString stringWithFormat: @"%.0f", [NSDate timeIntervalSinceReferenceDate] * 1000.0]]; + BOOL isDir = YES; + while ( [[NSFileManager defaultManager]fileExistsAtPath:outputDirPath isDirectory:&isDir]) { + outputDirPath = [_tempPath stringByAppendingPathComponent:[NSString stringWithFormat: @"%.0f", [NSDate timeIntervalSinceReferenceDate] * 1000.0]]; + } + + NSError *error = nil; + [[NSFileManager defaultManager] createDirectoryAtPath:outputDirPath withIntermediateDirectories:YES attributes:nil error:&error]; + + if( !error ){ + + NSString *inputFile = [[outputDirPath stringByAppendingPathComponent:@"input.muscle.fa"] stringByAddingQuotesIfSpaces]; + NSString *outputFile = [[outputDirPath stringByAppendingPathComponent:@"output.muscle.fa"] stringByAddingQuotesIfSpaces]; + NSString *transalignNuc = [[outputDirPath stringByAppendingPathComponent:@"input.nuc.fa"] stringByAddingQuotesIfSpaces]; + + NSURL *url = [NSURL URLWithString:[outputFile stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; + [options setObject:url forKey:MFOperationOutputKey]; + + // Input + NSURL *inputURL = [[NSURL URLWithString:[inputFile stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]copy]; + MFSequenceSet *sequenceSet; + if ( !MFIsEmpty2DRange(_rangeSelection) ) { + NSArray *sub = [_sequences objectsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:_rangeSelection.y]]; + sequenceSet = [[MFSequenceSet alloc]initWithSequences:sub]; + [MFSequenceWriter writeFasta:sequenceSet toFile:inputFile attributes:nil]; + } + else if( transalign ){ + + // write DNA for transalign + sequenceSet = [[MFSequenceSet alloc]initWithSequences:_sequences]; + NSUInteger index = [sequenceSet indexFirstNonGap]; + [MFSequenceWriter writeFasta:sequenceSet toFile:transalignNuc attributes:nil]; + + // write AA for muscle + MFSequenceSet *aaSet = [[MFSequenceSet alloc] init]; + for ( MFSequence *seq in _sequences ) { + MFSequence *copySeq = [seq copy]; + // there are gaps at the beginning + if( index != 0 ){ + [copySeq deleteResiduesInRange:NSMakeRange(0, 3)]; + } + [copySeq translateFinal]; + [aaSet addSequence:copySeq]; + + [copySeq release]; + } + [MFSequenceWriter writeFasta:aaSet toFile:inputFile attributes:nil]; + [aaSet release]; + } + else{ + sequenceSet = [[MFSequenceSet alloc]initWithSequences:_sequences]; + NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:YES], MFSequenceWriterIgnoreLeadingGaps, nil]; + [MFSequenceWriter writeFasta:sequenceSet toFile:inputFile attributes:attrs]; + } + + [options setObject:inputURL forKey:MFExternalOperationInputKey]; + [inputURL release]; + + // Muscle + if( self.indexTabView == 0 ){ + + [arguments addObject:@"-in"]; + [arguments addObject:inputFile]; + + [arguments addObject:@"-out"]; + [arguments addObject:outputFile]; + + if ( self.refine ) { + [arguments addObject:@"-refine"]; + } + + //[arguments addObject:@"-stable"]; // keep the same order but not supported in this version + + [options setObject:[@"MUSCLE: " stringByAppendingString:[inputFile lastPathComponent]] forKey:MFOperationDescriptionKey]; + + [options setObject:_musclePath forKey:MFExternalOperationLaunchPathKey]; + } + // MAFFT + else if( self.indexTabView == 1 ){ + + [arguments addObject:inputFile]; + + [arguments addObject:@">"]; + [arguments addObject:outputFile]; + + [options setObject:[@"MAFFT: " stringByAppendingString:[inputFile lastPathComponent]] forKey:MFOperationDescriptionKey]; + + [options setObject:_mafftPath forKey:MFExternalOperationLaunchPathKey]; + } + + if ( [additionalCommands length] > 0 ) { + NSArray *array = [self.additionalCommands componentsSeparatedByString:@"-"]; + for (NSString *arg in array) { + arg = [arg stringByTrimmingPaddingWhitespace]; + + NSRange range = [arg rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]]; + if (range.location == NSNotFound ) { + if (![arg isEmpty])[arguments addObject:[@"-" stringByAppendingString:arg] ]; + } + else { + NSString *key = [[arg substringToIndex:range.location] stringByTrimmingPaddingWhitespace]; + NSString *value = [[arg substringFromIndex:range.location]stringByTrimmingPaddingWhitespace]; + + [arguments addObject:[@"-" stringByAppendingString:key]]; + if([value length])[arguments addObject:value]; + } + } + } + + [arguments release]; + + NSLog(@"%@", options); + + MFExternalOperation *op = [[MFExternalOperation alloc]initWithOptions:options]; + op.description = [options objectForKey:MFOperationDescriptionKey]; + + [options release]; + NSArray *ops; + if( transalign ){ + NSString *transalignNucOutput = [[outputDirPath stringByAppendingPathComponent:@"output.nuc.fa"] stringByAddingQuotesIfSpaces]; + NSURL *outputURL = [NSURL URLWithString:[transalignNucOutput stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; + MFOperationTransalign *transalignOp = [[MFOperationTransalign alloc]initWithNucleotideFile:transalignNuc AminoacidFile:outputFile outputULR:outputURL]; + transalignOp.position = [sequenceSet indexFirstNonGap]; + transalignOp.description = [@"Transalign: " stringByAppendingString:[inputFile lastPathComponent]]; + [transalignOp addDependency:op]; + + ops = [NSArray arrayWithObjects:op,transalignOp, nil]; + [transalignOp release]; + } + else { + ops = [NSArray arrayWithObject:op]; + } + [sequenceSet release]; + [op release]; + return ops; + } + + [options release]; + [arguments release]; + return nil; +} + +- (void)setSelection:(MF2DRange)selection{ + _rangeSelection = selection; +} + +-(IBAction)closeAction:(id)sender{ + [NSApp endSheet:[self window] returnCode:NSCancelButton]; + [[self window] orderOut:nil]; +} + +-(IBAction)alignAction:(id)sender{ + [NSApp endSheet:[self window] returnCode:NSOKButton]; + [[self window] orderOut:nil]; +} + +@end diff --git a/Seqotron/MFAppDelegate.h b/Seqotron/MFAppDelegate.h new file mode 100755 index 0000000..5dc1ce5 --- /dev/null +++ b/Seqotron/MFAppDelegate.h @@ -0,0 +1,44 @@ +// +// MFAppDelegate.h +// Seqotron +// +// Created by Mathieu Fourment on 25/01/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFPrefsWindowController.h" + +@interface MFAppDelegate : NSObject { + + IBOutlet NSMenu *geneticCodeMenu; + IBOutlet NSMenu *coloringMenu; + + @private + MFPrefsWindowController *preferenceController; + +} +@property(assign) IBOutlet NSMenu *geneticCodeMenu; +@property(assign) IBOutlet NSMenu *coloringMenu; + +@property (retain,readwrite) NSOperationQueue *sharedOperationQueue; + +- (IBAction)showPreferencePanel:(id)sender; + +@end diff --git a/Seqotron/MFAppDelegate.m b/Seqotron/MFAppDelegate.m new file mode 100755 index 0000000..0e26e85 --- /dev/null +++ b/Seqotron/MFAppDelegate.m @@ -0,0 +1,226 @@ +// +// MFAppDelegate.m +// Seqotron +// +// Created by Mathieu Fourment on 25/01/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFAppDelegate.h" + + +#import "MFColorManager.h" +#import "MFDocument.h" +#import "MFTreeWindowController.h" + +#pragma mark *** NSWindowController Conveniences *** + + +@interface NSWindowController(MFConvenience) +- (BOOL)isWindowShown; +- (void)showOrHideWindow; +@end + +@implementation NSWindowController(MFConvenience) + + +- (BOOL)isWindowShown { + + // Simple. + return [[self window] isVisible]; + +} + + +- (void)showOrHideWindow { + + // Simple. + NSWindow *window = [self window]; + if ([window isVisible]) { + [window orderOut:self]; + } else { + [self showWindow:self]; + } + +} + + +@end + +@implementation MFAppDelegate + +@synthesize sharedOperationQueue; +@synthesize coloringMenu,geneticCodeMenu; + +- (id) init { + if ( self = [super init] ) { + sharedOperationQueue = [[NSOperationQueue alloc] init]; + preferenceController = nil; + } + return self; +} + ++ (void)initialize { + NSMutableDictionary *defaults = [NSMutableDictionary dictionary]; + // Sequences + [defaults setObject:[NSNumber numberWithFloat:15] forKey:@"MFDefaultSequenceFontSize"]; + [defaults setObject:@"Courier" forKey:@"MFDefaultSequenceFontName"]; + [defaults setObject:[NSNumber numberWithFloat:4] forKey:@"MFDefaultSequenceColumnSpacing"]; + [defaults setObject:[NSNumber numberWithFloat:2] forKey:@"MFDefaultSequenceRowSpacing"]; + + // Trees + [defaults setObject:[NSNumber numberWithFloat:12] forKey:@"MFDefaultTreeFontSize"]; + [defaults setObject:@"Courier" forKey:@"MFDefaultTreeFontName"]; + + [defaults setObject:@"Coloring" forKey:@"MFColoringDirectory"]; + + // Not used + [defaults setObject:[NSNumber numberWithBool:YES] forKey:@"NSDisabledDictationMenuItem"]; + [defaults setObject:[NSNumber numberWithBool:YES] forKey:@"NSDisabledCharacterPaletteMenuItem"]; + + [[NSUserDefaults standardUserDefaults] registerDefaults:defaults]; +} + +- (void)applicationDidFinishLaunching:(NSNotification *)notification { + NSLog(@"applicationDidFinishLaunching"); + NSError *error = nil; + NSURL *cacheDir = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&error]; + + NSString *executableName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleExecutable"]; + NSString *tempPath = [[[cacheDir path] stringByAppendingPathComponent:executableName]stringByAppendingPathComponent:@"temp"]; + [[NSFileManager defaultManager] removeItemAtPath:tempPath error:nil]; + NSLog(@"Color user directory %@", [MFColorManager userColorDirectory]); + + [[NSColorPanel sharedColorPanel]setRestorable:NO]; + [[NSFontPanel sharedFontPanel]setRestorable:NO]; + + [self setUpColoringMenu]; + [self setUpGeneticCodeMenu]; + + [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(coloringNotification:) name:@"MFColoringDidChange" object:nil]; +} + +- (void)dealloc{ + [[NSNotificationCenter defaultCenter]removeObserver:self]; + [sharedOperationQueue release]; + [preferenceController release]; + [super dealloc]; +} + +// Conformance to the NSObject(NSMenuValidation) informal protocol. +- (BOOL)validateMenuItem:(NSMenuItem *)menuItem { + + BOOL enabled = NO; + SEL action = [menuItem action]; + + if (action==@selector(showPreferencePanel:) ) { + enabled = YES; + } + else { + enabled = [super validateMenuItem:menuItem]; + } + return enabled; + +} + +- (IBAction)showPreferencePanel:(id)sender{ + if ( !preferenceController ){ + preferenceController = [[MFPrefsWindowController alloc]init]; + } + [preferenceController showOrHideWindow]; +} + +- (void)setUpGeneticCodeMenu{ + // Set up the genetic code menu + NSArray *geneticCodes = [self loadGeneticCodes]; + NSInteger tag = 0; + for (NSString *gc in geneticCodes) { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:gc action:@selector(geneticCodeAction:) keyEquivalent:@""]; + [item setTarget:nil]; + [item setTag:tag]; + [geneticCodeMenu addItem:item]; + [item release]; + tag++; + } +} + +- (void)setUpColoringMenu{ + // Set up the color scheme menu + NSInteger tag = 0; + NSMenu *nucMenu = [[coloringMenu itemAtIndex:0]submenu]; + for (NSString *scheme in [self loadColorSchemes:@"Nucleotide"]) { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:scheme action:@selector(colorSchemeAction:) keyEquivalent:@""]; + [item setTarget:nil]; + [item setTag:tag]; + [nucMenu addItem:item]; + [item release]; + tag++; + } + tag = 0; + + NSMenu *protMenu = [[coloringMenu itemAtIndex:1]submenu]; + for (NSString *scheme in [self loadColorSchemes:@"Amino acid"]) { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:scheme action:@selector(colorSchemeAction:) keyEquivalent:@""]; + [item setTarget:nil]; + [item setTag:tag]; + [protMenu addItem:item]; + [item release]; + tag++; + } +} + +- (void)coloringNotification:(NSNotification *)notification { + [[[coloringMenu itemAtIndex:0]submenu]removeAllItems]; + [[[coloringMenu itemAtIndex:1]submenu]removeAllItems]; + [self setUpColoringMenu]; +} + +-(NSArray*)loadGeneticCodes{ + NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"GeneticCodeTables"]; + NSArray *dirFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil]; + NSMutableArray *mutableArray = [[NSMutableArray alloc]init]; + + for ( NSString *file in dirFiles) { + if ( [file hasSuffix:@"plist"] ) { + NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:[path stringByAppendingPathComponent:file]]; + [mutableArray addObject:[dict objectForKey:@"Description"]]; + [dict release]; + } + } + return [mutableArray autorelease]; +} + +-(NSMutableArray*)loadColorSchemes:(NSString*)type{ + NSMutableArray *colorSchemes = [[NSMutableArray alloc]init]; + NSString *coloringFolder = [[NSUserDefaults standardUserDefaults] objectForKey:@"MFColoringDirectory"]; + NSString *userPath = [[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:coloringFolder]stringByAppendingPathComponent:type]; + NSArray *schemes = [MFColorManager colorSchemesAtPath:userPath]; + [colorSchemes addObjectsFromArray:schemes]; + + NSError *error = nil; + NSURL *appSupportDir = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&error]; + if(!error){ + NSString *executableName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleExecutable"]; + NSString *appPath = [[[appSupportDir path] stringByAppendingPathComponent:[executableName stringByAppendingPathComponent:coloringFolder]]stringByAppendingPathComponent:type]; + NSArray *schemes = [MFColorManager colorSchemesAtPath:appPath]; + [colorSchemes addObjectsFromArray:schemes]; + } + return [colorSchemes autorelease]; +} + +@end diff --git a/Seqotron/MFClustalImporter.h b/Seqotron/MFClustalImporter.h new file mode 100755 index 0000000..5e6ff62 --- /dev/null +++ b/Seqotron/MFClustalImporter.h @@ -0,0 +1,31 @@ +// +// MFClustalImporter.h +// Seqotron +// +// Created by Mathieu Fourment on 7/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFSequenceImporter.h" + +@interface MFClustalImporter : NSObject { + +} +@end diff --git a/Seqotron/MFClustalImporter.m b/Seqotron/MFClustalImporter.m new file mode 100755 index 0000000..a2cd611 --- /dev/null +++ b/Seqotron/MFClustalImporter.m @@ -0,0 +1,135 @@ +// +// MFClustalImporter.m +// Seqotron +// +// Created by Mathieu Fourment on 7/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFClustalImporter.h" + +#import "MFSequence.h" +#import "MFReaderCluster.h" +#import "MFString.h" + +@implementation MFClustalImporter + + +- (MFSequenceSet *)readSequencesFromFile:(NSString *)path{ + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithFile:path]; + + MFSequenceSet *sequences = [self readSequences:reader]; + + [reader release]; + + return sequences; +} + +-(MFSequenceSet*)readSequencesFromURL:(NSURL*)url{ + NSString *content = [ NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil]; + MFSequenceSet *sequences = [self readSequencesFromString:content]; + + return sequences; +} + +-(MFSequenceSet*)readSequencesFromData:(NSData*)data{ + NSString *content = [[NSString alloc] initWithData: data encoding: NSASCIIStringEncoding]; + + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithString:content]; + + MFSequenceSet *sequences = [self readSequences:reader]; + + [reader release]; + [content release]; + + return sequences; +} + +-(MFSequenceSet*)readSequencesFromString:(NSString*)content{ + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithString:content]; + + MFSequenceSet *sequences = [self readSequences:reader]; + + [reader release]; + + return sequences; +} + +- (MFSequenceSet *)readSequences:(MFReaderCluster *)reader{ + + + NSRegularExpression *conservationRegex = [NSRegularExpression regularExpressionWithPattern:@"^\\s*[\\*\\.: ]+$" options:NSRegularExpressionCaseInsensitive error:nil]; + NSRegularExpression *positionRegex = [NSRegularExpression regularExpressionWithPattern:@"\\d+$" options:NSRegularExpressionCaseInsensitive error:nil]; + + + NSString *line; + // discard first line (CLUSTAL...) + [reader readLine]; + + MFSequenceSet *sequences = nil; + NSMutableArray *array = [[NSMutableArray alloc] init]; + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; + + while ( (line = [ reader readLine]) ) { + + if ( ![line isEmpty] ){ + NSArray* block = [conservationRegex matchesInString:line options:0 range: NSMakeRange(0, [line length])]; + + // Not the conservation sequence + if( [block count] == 0 ){ + line = [line stringByTrimmingPaddingWhitespace]; + NSUInteger i = 0; + while ( i != [line length] ) { + if ( [[NSCharacterSet whitespaceCharacterSet] characterIsMember:[line characterAtIndex:i]]) break; + i++; + } + NSString *name = [line substringToIndex:i]; + + NSArray* temp = [[[line substringFromIndex:i+1] uppercaseString] componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + NSString *seq = [temp componentsJoinedByString:@""]; + + // remove position at the end of each sequence if available + seq = [positionRegex stringByReplacingMatchesInString:seq options:0 range:NSMakeRange(0, [seq length]) withTemplate:@""]; + seq = [seq uppercaseString]; + + if( [dict objectForKey:name] ){ + MFSequence *sequence = [dict objectForKey:name]; + [sequence concatenateString:seq]; + } + else { + MFSequence *sequence = [[MFSequence alloc] initWithString:seq name:name]; + [dict setObject:sequence forKey:name]; + [sequence release]; + [array addObject:name]; + } + } + } + } + if( [array count] > 0 ){ + sequences = [[MFSequenceSet alloc] initWithCapacity:[array count]]; + for (NSString *name in array) { + [sequences addSequence: [dict objectForKey:name]]; + } + } + [dict release]; + [array release]; + + return [sequences autorelease]; +} + +@end diff --git a/Seqotron/MFColorManager.h b/Seqotron/MFColorManager.h new file mode 100644 index 0000000..31163f4 --- /dev/null +++ b/Seqotron/MFColorManager.h @@ -0,0 +1,39 @@ +// +// MFColorManager.h +// Seqotron +// +// Created by Mathieu Fourment on 4/03/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +@interface MFColorManager : NSObject + ++ (NSArray*)colorSchemesAtPath:(NSString*)path; + ++ (NSArray*)colorUserSchemes:(NSString*)type; + ++ (NSArray*)colorSchemesWithInfoAtPath:(NSString*)path; + ++ (NSString*)applicationColorDirectory; ++ (NSString*)userColorDirectory; + ++ (NSDictionary*)coloring:(NSString*)desc fromPath:(NSString*)path; + +@end diff --git a/Seqotron/MFColorManager.m b/Seqotron/MFColorManager.m new file mode 100644 index 0000000..7c245a1 --- /dev/null +++ b/Seqotron/MFColorManager.m @@ -0,0 +1,162 @@ +// +// MFColorManager.m +// Seqotron +// +// Created by Mathieu Fourment on 4/03/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFColorManager.h" + +@implementation MFColorManager + + ++ (NSArray*)colorSchemesAtPath:(NSString*)path{ + + NSError *error = nil; + NSMutableArray *schemes = [[NSMutableArray alloc]init]; + NSArray *dirFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error]; + if( !error ){ + for ( NSString *file in dirFiles) { + if ( [file hasSuffix:@"plist"] ) { + NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:[path stringByAppendingPathComponent:file]]; + [schemes addObject:[dict objectForKey:@"Description"]]; + [dict release]; + } + } + } + return [schemes autorelease]; +} + ++ (NSArray*)colorUserSchemes:(NSString*)type{ + NSString *userColorDir = [MFColorManager userColorDirectory]; + if( userColorDir ){ + NSString *path = [userColorDir stringByAppendingPathComponent:type]; + + NSMutableArray *schemes = [[NSMutableArray alloc]init]; + NSError *error = nil; + NSArray *dirFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error]; + if( !error ){ + for ( NSString *file in dirFiles) { + if ( [file hasSuffix:@"plist"] ) { + NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:[path stringByAppendingPathComponent:file]]; + [schemes addObject:[dict objectForKey:@"Description"]]; + [dict release]; + } + } + return [schemes autorelease]; + } + [schemes release]; + } + return nil; +} + ++ (NSArray*)colorSchemesWithInfoAtPath:(NSString*)path{ + + NSError *error = nil; + NSMutableArray *schemes = [[NSMutableArray alloc]init]; + NSArray *dirFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error]; + if( !error ){ + for ( NSString *file in dirFiles) { + if ( [file hasSuffix:@"plist"] ) { + NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:[path stringByAppendingPathComponent:file]]; + NSMutableDictionary *scheme = [NSMutableDictionary dictionary]; + [scheme setObject:[dict objectForKey:@"Description"] forKey:@"desc"]; + [scheme setObject:[file stringByDeletingPathExtension] forKey:@"file"]; + [schemes addObject:scheme]; + [dict release]; + } + } + } + return [schemes autorelease]; +} + ++ (NSString*)userColorDirectory{ + NSString *rootPath = nil; + NSError *err = nil; + NSURL *appSupportDir = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&err]; + + if( !err ){ + NSString *executableName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleExecutable"]; + rootPath = [[appSupportDir path] stringByAppendingPathComponent:[executableName stringByAppendingPathComponent:[[NSUserDefaults standardUserDefaults] objectForKey:@"MFColoringDirectory"]]]; + } + return rootPath; +} + ++ (NSString*)applicationColorDirectory{ + NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:[[NSUserDefaults standardUserDefaults] objectForKey:@"MFColoringDirectory"]]; + return path; +} + ++ (NSDictionary*)coloring:(NSString*)desc fromPath:(NSString*)path{ + NSMutableDictionary *colors = [[NSMutableDictionary alloc]init]; + NSError *error = nil; + NSArray *dirFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error]; + + if(!error){ + for ( NSString *file in dirFiles) { + if ( [file hasSuffix:@"plist"] ) { + NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:[path stringByAppendingPathComponent:file]]; + if ( [[dict objectForKey:@"Description"] isEqualToString:desc] ) { + + NSDictionary *temp = [dict objectForKey:@"Foreground"]; + if ( temp != nil ) { + NSMutableDictionary *foreground = [[NSMutableDictionary alloc]init]; + for (NSString *key in [temp keyEnumerator]) { + NSArray *colors = [temp valueForKey:key]; + CGFloat red = [[colors objectAtIndex:0] floatValue]; + CGFloat green = [[colors objectAtIndex:1] floatValue]; + CGFloat blue = [[colors objectAtIndex:2] floatValue]; + CGFloat alpha = 1; + if( [colors count] == 4 ) [[colors objectAtIndex:3] floatValue]; + + NSColor *color = [NSColor colorWithDeviceRed:red green:green blue:blue alpha:alpha]; + [foreground setValue: color forKey:key]; + } + [colors setObject:foreground forKey:NSForegroundColorAttributeName]; + [foreground release]; + } + + temp = [dict objectForKey:@"Background"]; + if ( temp != nil ) { + NSMutableDictionary *background = [[NSMutableDictionary alloc]init]; + for (NSString *key in [temp keyEnumerator]) { + NSArray *colors = [temp valueForKey:key]; + CGFloat red = [[colors objectAtIndex:0] floatValue]; + CGFloat green = [[colors objectAtIndex:1] floatValue]; + CGFloat blue = [[colors objectAtIndex:2] floatValue]; + CGFloat alpha = 1; + if( [colors count] == 4 ) [[colors objectAtIndex:3] floatValue]; + + NSColor *color = [NSColor colorWithDeviceRed:red green:green blue:blue alpha:alpha]; + [background setValue: color forKey:key]; + } + [colors setObject:background forKey:NSBackgroundColorAttributeName]; + [background release]; + } + [dict release]; + break; + } + [dict release]; + } + } + } + return [colors autorelease]; +} + +@end diff --git a/Seqotron/MFDataType.h b/Seqotron/MFDataType.h new file mode 100755 index 0000000..13493ea --- /dev/null +++ b/Seqotron/MFDataType.h @@ -0,0 +1,52 @@ +// +// MFDataType.h +// Seqotron +// +// Created by Mathieu Fourment on 6/02/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +@protocol MFDataTypeProtocol + +@property (readwrite,copy) NSString *gap; +@property (readwrite,copy) NSString *unknown; + +- (NSUInteger)stateCount; + +- (BOOL)isGap:(NSString *)character; + +- (BOOL)isUnknown:(NSString *)character; + +- (BOOL)isKnown:(NSString *)character; + +- (BOOL)isKnownChar:(char)character; + +-(BOOL)isValid:(NSString *)character; + +-(BOOL)isAmbiguous:(NSString *)character; + + + +@end + +@interface MFDataType : NSObject + + +@end diff --git a/Seqotron/MFDataType.m b/Seqotron/MFDataType.m new file mode 100755 index 0000000..9481990 --- /dev/null +++ b/Seqotron/MFDataType.m @@ -0,0 +1,78 @@ +// +// MFDataType.m +// Seqotron +// +// Created by Mathieu Fourment on 8/02/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFDataType.h" + +@implementation MFDataType + +@synthesize gap = _gap; +@synthesize unknown = _unknown; + +- (id)init{ + if( (self = [super init]) ){ + _gap = @"-"; + _unknown = @"?"; + } + return [super init]; +} + +- (NSUInteger)stateCount{ + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)] + userInfo:nil]; +} + +-(BOOL)isGap:(NSString *)character{ + return [character isEqualToString:_gap]; +} + +-(BOOL)isUnknown:(NSString *)character{ + return [character isEqualToString:_unknown]; +} + +-(BOOL)isKnown:(NSString *)character{ + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)] + userInfo:nil]; +} + +-(BOOL)isKnownChar:(char)character{ + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)] + userInfo:nil]; +} + +-(BOOL)isAmbiguous:(NSString *)character{ + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)] + userInfo:nil]; +} + +-(BOOL)isValid:(NSString *)character{ + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)] + userInfo:nil]; +} + + +@end diff --git a/Seqotron/MFDefines.h b/Seqotron/MFDefines.h new file mode 100755 index 0000000..8d07664 --- /dev/null +++ b/Seqotron/MFDefines.h @@ -0,0 +1,79 @@ +// +// MFDefines.h +// Seqotron +// +// Created by Mathieu Fourment on 29/01/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +typedef struct MF2DRange { + NSRange x; + NSRange y; +} MF2DRange; + +NS_INLINE MF2DRange MFMake2DRange(NSUInteger xo, NSUInteger xl, NSUInteger yo, NSUInteger yl) { + MF2DRange r; + r.x = NSMakeRange(xo, xl); + r.y = NSMakeRange(yo, yl); + return r; +} + +NS_INLINE BOOL MFLocationsin2DRange(NSUInteger x, NSUInteger y, MF2DRange range) { + return NSLocationInRange(x, range.x) && NSLocationInRange(y, range.y); +} + +NS_INLINE MF2DRange MFMakeEmpty2DRange() { + return MFMake2DRange(0, 0, 0, 0); +} + +NS_INLINE BOOL MFIsEmpty2DRange(MF2DRange range){ + if( range.x.length == 0 && range.y.length == 0 ){ + return YES; + } + return NO; +} + +typedef enum MFSequenceFormat: NSUInteger { + MFSequenceFormatFASTA = 0, + MFSequenceFormatNEXUS = 1, + MFSequenceFormatPHYLIP = 2, + MFSequenceFormatCLUSTAL = 3, + MFSequenceFormatMEGA = 4, + MFSequenceFormatGDE = 5, + MFSequenceFormatNBRF = 6, + MFSequenceFormatSTOCKHOLM = 7 + +} MFSequenceFormat; + +#define MFSequenceFormatArray @"FASTA", @"Nexus", @"Phylip", @"Clustal", @"MEGA", @"GDE", @"NBRF", @"Stockholm", nil + + +typedef enum MFTreeFormat: NSUInteger { + MFTreeFormatNEWICK = 0, + MFTreeFormatNEXUS = 1 + +} MFTreeFormat; + +#define MFTreeFormatArray @"Newick", @"Nexus", nil + + +extern NSString * const MFSequenceFileFormat; + +extern NSString * const MFTreeFileFormat; diff --git a/Seqotron/MFDistanceMatrix.h b/Seqotron/MFDistanceMatrix.h new file mode 100755 index 0000000..d31edea --- /dev/null +++ b/Seqotron/MFDistanceMatrix.h @@ -0,0 +1,58 @@ +// +// MFDistanceMatrix.h +// Seqotron +// +// Created by Mathieu Fourment on 7/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFSequence.h" + +typedef enum MFDistanceMatrixModel: NSUInteger { + MFDistanceMatrixModelRAW = 0, + MFDistanceMatrixModelJC69 = 1, + MFDistanceMatrixModelK2P = 2, + +} MFDistanceMatrixModel; + +@interface MFDistanceMatrix : NSObject{ + float **_matrix; + NSUInteger _dimension; + NSArray *_sequences; +} + +@property (readonly)NSUInteger dimension; + +-(id)initWithSequencesFromArray:(NSArray*)sequences; + +-(float)valueForRow:(NSUInteger)aRow column:(NSUInteger)aColumn; + +- (void)calculateDistances; + +- (float)calculatePairwiseDistanceBetween:(MFSequence*)sequence1 and:(MFSequence*)sequence2; + +- (NSString*)nameAtIndex:(NSUInteger)index; + +- (float**)floatMatrix; + +- (void)print; + + +@end diff --git a/Seqotron/MFDistanceMatrix.m b/Seqotron/MFDistanceMatrix.m new file mode 100755 index 0000000..e0a16ad --- /dev/null +++ b/Seqotron/MFDistanceMatrix.m @@ -0,0 +1,131 @@ +// +// MFDistanceMatrix.m +// Seqotron +// +// Created by Mathieu Fourment on 7/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFDistanceMatrix.h" + +#import "MFSequence.h" + +@implementation MFDistanceMatrix +@synthesize dimension = _dimension; + + +-(id)initWithSequencesFromArray:(NSArray*)sequences{ + if( self = [super init] ){ + _dimension = [sequences count]; + _matrix = malloc(_dimension*sizeof(float*)); + for ( NSUInteger i = 0; i < _dimension; i++ ) { + _matrix[i] = calloc(_dimension, sizeof(float)); + } + _sequences = [sequences retain]; + } + return self; +} + +-(void)dealloc{ + NSLog(@"MFDistanceMatrix dealloc"); + for ( NSUInteger i = 0; i < _dimension; i++ ) { + free(_matrix[i]); + } + free(_matrix); + [_sequences release]; + [super dealloc]; +} + +- (float)calculatePairwiseDistanceBetween:(MFSequence*)sequence1 and:(MFSequence*)sequence2{ + NSUInteger d = 0; + NSUInteger n = 0; + const char *c1 = [[sequence1 sequence]UTF8String]; + const char *c2 = [[sequence2 sequence]UTF8String]; + MFDataType *dataType = sequence1.dataType; + NSUInteger dimension = [sequence1 length]; + + for ( NSUInteger k = 0; k < dimension; k++ ) { + if( [dataType isKnownChar:c1[k]] && [dataType isKnownChar:c2[k]] ){ + if(c1[k] != c2[k] ){ + d++; + } + n++; + } + } + return (float)d/n; +} + + +-(void)calculateDistances{ + for ( NSUInteger i = 0; i < _dimension; i++ ) { + MFSequence *seq1 = [_sequences objectAtIndex:i]; + + for ( NSUInteger j = i+1; j < _dimension; j++ ) { + MFSequence *seq2 = [_sequences objectAtIndex:j]; + + _matrix[i][j] = _matrix[j][i] = [self calculatePairwiseDistanceBetween:seq1 and:seq2]; + } + } +} + +- (float**)floatMatrix{ + return _matrix; +} + +- (NSString*)nameAtIndex:(NSUInteger)index{ + return [[_sequences objectAtIndex:index]name]; +} + +- (void)print{ + for ( NSUInteger i = 0; i < _dimension; i++ ) { + for ( NSUInteger j = 0; j < _dimension; j++ ) { + printf("%f ", _matrix[i][j]); + } + printf("\n"); + } +} + +-(float)valueForRow:(NSUInteger)aRow column:(NSUInteger)aColumn{ + return _matrix[aRow][aColumn]; +} + +#pragma mark *** C functions *** + +float calculatePairwiseDistance(const char *c1, const char *c2, NSUInteger dimension){ + NSUInteger d = 0; + NSUInteger n = 0; + + for ( NSUInteger k = 0; k < dimension; k++ ) { + + if( (*c1 == 'A' || *c1 == 'C' || *c1 == 'G' || *c1 == 'T' || *c1 == 'U') + && (*c2 == 'A' || *c2 == 'C' || *c2 == 'G' || *c2 == 'T' || *c2 == 'U') ){ + if( *c1 != *c2 ){ + d++; + } + n++; + } + + c1++; + c2++; + } + return (float)d/n; +} + + + +@end diff --git a/Seqotron/MFDistanceMatrixOperation.h b/Seqotron/MFDistanceMatrixOperation.h new file mode 100644 index 0000000..a6f286b --- /dev/null +++ b/Seqotron/MFDistanceMatrixOperation.h @@ -0,0 +1,41 @@ +// +// MFDistanceMatrixOperation.h +// Seqotron +// +// Created by Mathieu Fourment on 10/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFDistanceMatrix.h" + +#import "MFOperation.h" + + +@interface MFDistanceMatrixOperation : MFOperation{ + NSArray *_sequences; + MFDistanceMatrixModel _model; + MFDistanceMatrix *_matrix; +} + +@property (readonly) MFDistanceMatrix *matrix; + +-(id)initWithSequenceArray:(NSArray*)sequences model:(MFDistanceMatrixModel)model; + +@end diff --git a/Seqotron/MFDistanceMatrixOperation.m b/Seqotron/MFDistanceMatrixOperation.m new file mode 100644 index 0000000..4089aff --- /dev/null +++ b/Seqotron/MFDistanceMatrixOperation.m @@ -0,0 +1,94 @@ +// +// MFDistanceMatrixOperation.m +// Seqotron +// +// Created by Mathieu Fourment on 10/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFDistanceMatrixOperation.h" + +#import "MFK2PDistanceMatrix.h" +#import "MFJukeCantorDistanceMatrix.h" +#import "MFProtein.h" + +@implementation MFDistanceMatrixOperation + +@synthesize matrix = _matrix; + +-(id)initWithSequenceArray:(NSArray*)sequences model:(MFDistanceMatrixModel)model{ + if ( self = [super initWithOptions:[NSDictionary dictionary]] ) { + _sequences = [sequences retain]; + _model = model; + _matrix = nil; + + if ( [[[_sequences objectAtIndex:0]dataType] isKindOfClass:[MFProtein class]] ) { + _matrix = [[MFDistanceMatrix alloc]initWithSequencesFromArray: _sequences]; + self.description = @"Caculating distance Matrix"; + } + else { + + switch (_model) { + case MFDistanceMatrixModelRAW:{ + _matrix = [[MFDistanceMatrix alloc]initWithSequencesFromArray: _sequences]; + self.description = @"Caculating distance matrix"; + break; + } + case MFDistanceMatrixModelJC69:{ + _matrix = [[MFJukeCantorDistanceMatrix alloc]initWithSequencesFromArray: _sequences]; + self.description = @"Caculating JC69 distance matrix"; + break; + } + case MFDistanceMatrixModelK2P:{ + _matrix = [[MFK2PDistanceMatrix alloc]initWithSequencesFromArray: _sequences]; + self.description = @"Caculating K2P distance matrix"; + break; + } + default:{ + NSLog(@"MFDistanceMatrixOperation error"); + break; + } + } + } + } + return self; +} + +-(void)dealloc{ + [_matrix release]; + [_sequences release]; + [super dealloc]; +} + +-(void)main{ + NSLog(@"MFDistanceMatrixOperation running %lu", _model); + +// if( [self.delegate respondsToSelector:@selector(operation:setDescription:)] ){ +// dispatch_sync(dispatch_get_main_queue(), ^{ +// [self.delegate operation:self setDescription:self.description]; +// }); +// } + + [_matrix calculateDistances]; + +// [(NSObject *)self.delegate performSelectorOnMainThread: @selector(distanceMatrixDidFinish:) +// withObject: self +// waitUntilDone: YES]; +} + +@end diff --git a/Seqotron/MFDistanceMatrixWindow.xib b/Seqotron/MFDistanceMatrixWindow.xib new file mode 100644 index 0000000..55c4b16 --- /dev/null +++ b/Seqotron/MFDistanceMatrixWindow.xib @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Seqotron/MFDistanceWindowController.h b/Seqotron/MFDistanceWindowController.h new file mode 100644 index 0000000..4031d88 --- /dev/null +++ b/Seqotron/MFDistanceWindowController.h @@ -0,0 +1,48 @@ +// +// MFDistanceWindow.h +// Seqotron +// +// Created by Mathieu Fourment on 18/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFDistanceMatrix.h" +#import "MFSyncronizedScrollView.h" + + +@interface MFDistanceWindowController : NSWindowController{ + IBOutlet NSTableView *_matrixTable; + IBOutlet NSTableView *_columnTable; + + IBOutlet MFSyncronizedScrollView *_matrixScrollView; + IBOutlet MFSyncronizedScrollView *_columnScrollView; + + IBOutlet NSView *_sigDigitsView; + + MFDistanceMatrix *_distanceMatrix; + NSMutableDictionary *_columnMap; + NSNumberFormatter *_numberFormatter; +} + +@property NSInteger sigDigits; + +- (id)initWithDistanceMatrix:(MFDistanceMatrix*)distanceMatrix; + +@end diff --git a/Seqotron/MFDistanceWindowController.m b/Seqotron/MFDistanceWindowController.m new file mode 100644 index 0000000..c18d074 --- /dev/null +++ b/Seqotron/MFDistanceWindowController.m @@ -0,0 +1,278 @@ +// +// MFDistanceWindow.m +// Seqotron +// +// Created by Mathieu Fourment on 18/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFDistanceWindowController.h" + +#define kSigDigitsToolbarItemID @"Sig. Digits" + +@implementation MFDistanceWindowController + +@synthesize sigDigits; + +- (id)initWithDistanceMatrix:(MFDistanceMatrix*)distanceMatrix{ + if (self = [super initWithWindowNibName:@"MFDistanceMatrixWindow"]) { + NSLog(@"MFDistanceWindowController initWithWindowNibName"); + _distanceMatrix = [distanceMatrix retain]; + _columnMap = [[NSMutableDictionary alloc]init]; + sigDigits = 3; + _numberFormatter = [[NSNumberFormatter alloc] init]; + [_numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle]; + [_numberFormatter setMaximumSignificantDigits:sigDigits]; + } + return self; +} + +- (void)dealloc { + NSLog(@"MFDistanceWindowController dealloc"); + [_columnScrollView stopSynchronizing]; + [_matrixScrollView stopSynchronizing]; + [_distanceMatrix release]; + [_columnMap release]; + [_numberFormatter release]; + [super dealloc]; +} + +- (void)windowDidLoad { + [super windowDidLoad]; + + [[_columnScrollView verticalScroller] setControlSize:1]; + + [_columnScrollView setSynchronizedScrollView:_matrixScrollView onVertical:YES]; + [_matrixScrollView setSynchronizedScrollView:_columnScrollView onVertical:YES]; //_sequenceView listens to _namesView + + for ( NSUInteger i = 0; i < [_distanceMatrix dimension]; i++ ) { + [_columnMap setObject:[NSNumber numberWithUnsignedInteger:i] forKey:[_distanceMatrix nameAtIndex:i]]; + NSTableColumn * column = [[NSTableColumn alloc] initWithIdentifier:[_distanceMatrix nameAtIndex:i]]; + [[column headerCell]setStringValue:[_distanceMatrix nameAtIndex:i]]; + [column setHeaderToolTip:[_distanceMatrix nameAtIndex:i]]; + [_matrixTable addTableColumn:column]; + [column setWidth:80]; + [column release]; + } + [_matrixTable reloadData]; +} + +// NSStepper +-(IBAction)stepperAction:(id)sender{ + [_numberFormatter setMaximumSignificantDigits:self.sigDigits]; + [_matrixTable reloadData]; +} + +- (IBAction)showSavePanel:(id)sender{ + NSSavePanel * savePanel = [NSSavePanel savePanel]; + // Restrict the file type to whatever you like + [savePanel setAllowedFileTypes:@[@"csv"]]; + // Set the starting directory + //[savePanel setDirectoryURL:someURL]; + // Perform other setup + // Use a completion handler -- this is a block which takes one argument + // which corresponds to the button that was clicked + [savePanel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result){ + if (result == NSFileHandlingPanelOKButton) { + // Close panel before handling errors + [savePanel orderOut:self]; + + NSMutableString *string = [[NSMutableString alloc]init]; + for ( NSUInteger i = 0; i < _distanceMatrix.dimension ; i++ ) { + [string appendFormat:@",%@",[ _distanceMatrix nameAtIndex:i]]; + } + [string appendString:@"\r"]; + + for ( NSUInteger i = 0; i < _distanceMatrix.dimension ; i++ ) { + [string appendFormat:@"%@",[ _distanceMatrix nameAtIndex:i]]; + for ( NSUInteger j = 0; j < _distanceMatrix.dimension ; j++ ) { + [string appendFormat:@",%@", [_numberFormatter stringFromNumber:[NSNumber numberWithDouble:[_distanceMatrix valueForRow:i column: j]]]]; + } + [string appendString:@"\r"]; + } + NSString *path = [savePanel.URL path]; + [string writeToFile:path atomically:NO encoding:NSUTF8StringEncoding error:nil]; + [string release]; + } + }]; +} + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView { + return [_distanceMatrix dimension]; +} + +- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex{ + if ( aTableView == _columnTable ) { + return [_distanceMatrix nameAtIndex:rowIndex]; + } + + NSUInteger index = [[_columnMap objectForKey: [aTableColumn identifier]] unsignedIntegerValue]; + //return [NSString stringWithFormat:@"%f", [_distanceMatrix valueForRow:rowIndex column: index]]; + return [_numberFormatter stringFromNumber:[NSNumber numberWithDouble:[_distanceMatrix valueForRow:rowIndex column: index]]]; +} + +- (void)tableViewSelectionDidChange:(NSNotification *)aNotification{ + NSTableView *table = [aNotification object]; + NSUInteger row = [table selectedRow]; + + if( [_matrixTable selectedRow] != row ){ + [_matrixTable selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO]; + } + if( [_columnTable selectedRow] != row ){ + [_columnTable selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO]; + } +} + + + +//-------------------------------------------------------------------------------------------------- +// Factory method to create autoreleased NSToolbarItems. +// +// All NSToolbarItems have a unique identifer associated with them, used to tell your delegate/controller +// what toolbar items to initialize and return at various points. Typically, for a given identifier, +// you need to generate a copy of your "master" toolbar item, and return it autoreleased. The function +// creates an NSToolbarItem with a bunch of NSToolbarItem paramenters. +// +// It's easy to call this function repeatedly to generate lots of NSToolbarItems for your toolbar. +// +// The label, palettelabel, toolTip, action, and menu can all be nil, depending upon what you want +// the item to do. +//-------------------------------------------------------------------------------------------------- +- (NSToolbarItem *)toolbarItemWithIdentifier:(NSString *)identifier + label:(NSString *)label + paleteLabel:(NSString *)paletteLabel + toolTip:(NSString *)toolTip + target:(id)target + itemContent:(id)imageOrView + action:(SEL)action + menu:(NSMenu *)menu +{ + // here we create the NSToolbarItem and setup its attributes in line with the parameters + NSToolbarItem *item = [[[NSToolbarItem alloc] initWithItemIdentifier:identifier] autorelease]; + + [item setLabel:label]; + [item setPaletteLabel:paletteLabel]; + [item setToolTip:toolTip]; + [item setTarget:target]; + [item setAction:action]; + + // Set the right attribute, depending on if we were given an image or a view + if([imageOrView isKindOfClass:[NSImage class]]){ + [item setImage:imageOrView]; + } else if ([imageOrView isKindOfClass:[NSView class]]){ + [item setView:imageOrView]; + }else { + assert(!"Invalid itemContent: object"); + } + + + // If this NSToolbarItem is supposed to have a menu "form representation" associated with it + // (for text-only mode), we set it up here. Actually, you have to hand an NSMenuItem + // (not a complete NSMenu) to the toolbar item, so we create a dummy NSMenuItem that has our real + // menu as a submenu. + // + if (menu != nil) + { + // we actually need an NSMenuItem here, so we construct one + NSMenuItem *mItem = [[[NSMenuItem alloc] init] autorelease]; + [mItem setSubmenu:menu]; + [mItem setTitle:label]; + [item setMenuFormRepresentation:mItem]; + } + + return item; +} + +#pragma mark - +#pragma mark NSToolbarDelegate + +//-------------------------------------------------------------------------------------------------- +// This is an optional delegate method, called when a new item is about to be added to the toolbar. +// This is a good spot to set up initial state information for toolbar items, particularly ones +// that you don't directly control yourself (like with NSToolbarPrintItemIdentifier here). +// The notification's object is the toolbar, and the @"item" key in the userInfo is the toolbar item +// being added. +//-------------------------------------------------------------------------------------------------- +- (void)toolbarWillAddItem:(NSNotification *)notif { + NSToolbarItem *addedItem = [[notif userInfo] objectForKey:@"item"]; + + // Is this the printing toolbar item? If so, then we want to redirect it's action to ourselves + // so we can handle the printing properly; hence, we give it a new target. + // + if ([[addedItem itemIdentifier] isEqual: NSToolbarPrintItemIdentifier]) { + [addedItem setToolTip:@"Print your document"]; + [addedItem setTarget:self]; + } +} + +//-------------------------------------------------------------------------------------------------- +// This method is required of NSToolbar delegates. +// It takes an identifier, and returns the matching NSToolbarItem. It also takes a parameter telling +// whether this toolbar item is going into an actual toolbar, or whether it's going to be displayed +// in a customization palette. +//-------------------------------------------------------------------------------------------------- +- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag { + NSToolbarItem *toolbarItem = nil; + + // We create and autorelease a new NSToolbarItem, and then go through the process of setting up its + // attributes from the master toolbar item matching that identifier in our dictionary of items. + if ([itemIdentifier isEqualToString:kSigDigitsToolbarItemID]) + { + // 1) Navigation toolbar item + toolbarItem = [self toolbarItemWithIdentifier:kSigDigitsToolbarItemID + label:kSigDigitsToolbarItemID + paleteLabel:kSigDigitsToolbarItemID + toolTip:@"Significant digits" + target:self + itemContent:_sigDigitsView + action:nil + menu:nil]; + } + + + return toolbarItem; +} + +//-------------------------------------------------------------------------------------------------- +// This method is required of NSToolbar delegates. It returns an array holding identifiers for the default +// set of toolbar items. It can also be called by the customization palette to display the default toolbar. +//-------------------------------------------------------------------------------------------------- +- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar { + return [NSArray arrayWithObjects: kSigDigitsToolbarItemID, + nil]; + // note: + // that since our toolbar is defined from Interface Builder, an additional separator and customize + // toolbar items will be automatically added to the "default" list of items. +} + +//-------------------------------------------------------------------------------------------------- +// This method is required of NSToolbar delegates. It returns an array holding identifiers for all allowed +// toolbar items in this toolbar. Any not listed here will not be available in the customization palette. +//-------------------------------------------------------------------------------------------------- +- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar +{ + return [NSArray arrayWithObjects: kSigDigitsToolbarItemID, + nil]; + // note: + // that since our toolbar is defined from Interface Builder, an additional separator and customize + // toolbar items will be automatically added to the "default" list of items. +} + + + +@end diff --git a/Seqotron/MFDocument.h b/Seqotron/MFDocument.h new file mode 100755 index 0000000..f985915 --- /dev/null +++ b/Seqotron/MFDocument.h @@ -0,0 +1,49 @@ +// +// MFDocument.h +// Seqotron +// +// Created by Mathieu Fourment on 25/07/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFDefines.h" + +@class MFSequenceSet; + +extern NSString *MFDocumentSequencesKey; + + +@interface MFDocument : NSDocument { + + NSMutableArray *_sequences; + + MFSequenceFormat _currentFileFormat;// set when a file is read or when when we save the alignment + MFSequenceFormat _indexFormat; // bound to combobox (avoid changing selection in combobox after canceling) +} + +- (void)setFileFormat:(NSString*)format; + +@property (assign) IBOutlet NSWindow *window; + +@property (readonly) NSArray *fileFormats; + + + +@end diff --git a/Seqotron/MFDocument.m b/Seqotron/MFDocument.m new file mode 100755 index 0000000..2841cdf --- /dev/null +++ b/Seqotron/MFDocument.m @@ -0,0 +1,209 @@ +// +// MFDocument.m +// Seqotron +// +// Created by Mathieu Fourment on 25/07/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFDocument.h" + +#import "MFSequenceReader.h" +#import "MFSequenceWriter.h" +#import "MFSequenceSet.h" +#import "MFSequence.h" +#import "MFSequenceUtils.h" +#import "MFNucleotide.h" +#import "MFProtein.h" +#import "MFWindowController.h" + + +NSString *MFDocumentSequencesKey = @"sequences"; + +@implementation MFDocument + +@synthesize window,fileFormats; + +- (id)init +{ + self = [super init]; + if (self) { + _currentFileFormat = MFSequenceFormatFASTA; // default is FASTA + _indexFormat = MFSequenceFormatFASTA; + _sequences = nil; + fileFormats = [[NSArray alloc] initWithObjects:MFSequenceFormatArray]; + } + return self; +} + + +- (void)dealloc { + NSLog(@"MFDocument dealloc"); + [_sequences release]; + [fileFormats release]; + [super dealloc]; +} + + ++ (BOOL)autosavesInPlace{ + return NO; +} + +- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError{ + NSData * data = nil; + if( [_sequences count] > 0 ){ + MFSequenceSet *set = [[MFSequenceSet alloc] initWithSequences:_sequences]; + + NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:YES], MFSequenceWriterIgnoreLeadingGaps, nil]; + data = [MFSequenceWriter data:set withFormat:_indexFormat attributes:attrs]; + [set release]; + _currentFileFormat = _indexFormat; + NSLog(@"Writing to file %lu", _indexFormat); + } + return data; +} + +#pragma mark - Save panel + +// https://developer.apple.com/library/mac/samplecode/CustomSave/Introduction/Intro.html#//apple_ref/doc/uid/DTS10004201 + +// ------------------------------------------------------------------------------- +// prepareSavePanel:inSavePanel: +// ------------------------------------------------------------------------------- +// Invoked by runModalSavePanel to do any customization of the Save panel savePanel. +// +- (BOOL)prepareSavePanel:(NSSavePanel *)inSavePanel { + [inSavePanel setDelegate:self]; // allows us to be notified of save panel events + + MFWindowController *windowController = [[self windowControllers] objectAtIndex:0]; + [inSavePanel setMessage:@"Save alignment as:"]; + [inSavePanel setAccessoryView: windowController.saveDialogCustomView]; // add our custom view + //[inSavePanel setAllowedFileTypes:@[@"txt"]]; // save files with 'txt' extension only + //[inSavePanel setNameFieldLabel:@"FILE NAME:"]; // override the file name label + + //_savePanel = inSavePanel; // keep track of the save panel for later + [windowController.saveFileFormat removeAllItems]; +// NSArray *formats = [[NSArray alloc] initWithObjects:MFSequenceFormatArray]; + [windowController.saveFileFormat addItemsWithObjectValues:fileFormats]; +// [formats release]; + + [windowController.saveFileFormat setAction:@selector(formatComboBoxChanged:)]; + [windowController.saveFileFormat setTarget:self]; + [windowController.saveFileFormat setEditable:NO]; + [windowController.saveFileFormat selectItemAtIndex: _currentFileFormat]; + + _indexFormat = _currentFileFormat; + + return YES; +} + +- (void)formatComboBoxChanged:(NSComboBox *)comboBox { + _indexFormat = [comboBox indexOfSelectedItem]; +} + +-(BOOL)readFromURL:(NSURL*) url ofType:(NSString*)type error:(NSError**) outError { + BOOL readSuccessfully = NO; + + MFSequenceSet *sequenceSet = [MFSequenceReader readSequencesFromFile:url.path]; + NSArray *sequenceArray = [sequenceSet sequences]; + + if( sequenceArray != nil ){ + [MFSequenceUtils deleteFrontEmptyColumns:sequenceArray]; + [MFSequenceUtils pad:sequenceArray]; + + MFDataType *dataType = nil; + for (MFSequence *sequence in sequenceArray) { + if( [sequence dataType] != nil ){ + dataType = [sequence dataType]; + break; + } + } + if( dataType == nil ){ + [sequenceSet guessDataType]; + } + + _currentFileFormat = [self formatTypeStringToEnum: [sequenceSet annotationForKey:MFSequenceFileFormat]]; + + // without it when we close the alignment without editing it, undos will be generated + [[self undoManager] disableUndoRegistration]; + + [self removeSequencesAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [[self sequences] count])]]; + [self insertSequences:sequenceArray atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [sequenceArray count])]]; + + [[self undoManager] enableUndoRegistration]; + + readSuccessfully = YES; + } +// else { +// NSArray *objArray = [NSArray arrayWithObjects:@"Description", @"FailureReason", @"RecoverySuggestion", nil]; +// NSArray *keyArray = [NSArray arrayWithObjects:NSLocalizedDescriptionKey, NSLocalizedFailureReasonErrorKey, NSLocalizedRecoverySuggestionErrorKey, nil]; +// NSDictionary *userInfo = [NSDictionary dictionaryWithObjects:objArray forKeys:keyArray]; +// +// *outError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileReadUnknownError userInfo:userInfo]; +// } + NSLog(@"successful %@ %d",[url path], readSuccessfully); + return readSuccessfully; +} + +// A method to retrieve the int value from the NSArray of NSStrings +-(MFSequenceFormat) formatTypeStringToEnum:(NSString*)strVal{ + NSArray *typeArray = [[NSArray alloc] initWithObjects:MFSequenceFormatArray]; + NSUInteger n = [typeArray indexOfObject:strVal]; + [typeArray release]; + return (MFSequenceFormat) n; +} + +- (void)makeWindowControllers { + MFWindowController *windowController = [[MFWindowController alloc] init]; + [self addWindowController:windowController]; + [windowController release]; +} + +- (NSArray *)sequences { + return _sequences ? _sequences : [NSArray array]; + +} + +- (void)insertSequences:(NSArray *)sequences atIndexes:(NSIndexSet *)indexes{ + if (!_sequences) { + _sequences = [[NSMutableArray alloc] init]; + } + [_sequences insertObjects:sequences atIndexes:indexes]; + // Register an action that will undo the insertion. + NSUndoManager *undoManager = [self undoManager]; + [undoManager registerUndoWithTarget:self selector:@selector(removeSequencesAtIndexes:) object:indexes]; +} + +- (void)removeSequencesAtIndexes:(NSIndexSet *)indexes { + + if (!_sequences) { + _sequences = [[NSMutableArray alloc] init]; + } + NSArray *sequences = [_sequences objectsAtIndexes:indexes]; + + [[[self undoManager] prepareWithInvocationTarget:self] insertSequences:sequences atIndexes:indexes]; + + [_sequences removeObjectsAtIndexes:indexes]; + +} + +-(void)setFileFormat:(NSString *)format{ + _indexFormat = _currentFileFormat = [self formatTypeStringToEnum: format]; +} + +@end diff --git a/Seqotron/MFDocumentController.h b/Seqotron/MFDocumentController.h new file mode 100644 index 0000000..ac51484 --- /dev/null +++ b/Seqotron/MFDocumentController.h @@ -0,0 +1,28 @@ +// +// MFDocumentController.h +// Seqotron +// +// Created by Mathieu Fourment on 16/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +@interface MFDocumentController : NSDocumentController + +@end diff --git a/Seqotron/MFDocumentController.m b/Seqotron/MFDocumentController.m new file mode 100644 index 0000000..25a1a33 --- /dev/null +++ b/Seqotron/MFDocumentController.m @@ -0,0 +1,82 @@ +// +// MFDocumentController.m +// Seqotron +// +// Created by Mathieu Fourment on 16/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFDocumentController.h" + +#import "MFReaderCluster.h" +#import "MFString.h" + +@implementation MFDocumentController + + + +// By default a file is an alignment what ever the extension is. +-(NSString *)typeForContentsOfURL:(NSURL *)url error:(NSError **)error { + if ( ![url isFileURL] ) { + return nil; + } + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithFile:url.path]; + NSString *line = nil; + + BOOL isTree = NO; + BOOL isSequence = NO; + BOOL isNexus = NO; + + while ( (line = [reader readLine]) ) { + // Allow the first line to be empty + if( [[line stringByTrimmingPaddingWhitespace] length] == 0 ){ + continue; + } + if ( [[line uppercaseString] hasPrefix:@"#NEXUS"]) { + isNexus = YES; + } + else if( [line hasPrefix:@"("]){ + isTree = YES; + } + break; + } + + if ( isNexus ) { + while ( (line = [reader readLine]) ) { + if( [[line stringByTrimmingPaddingWhitespace] length] == 0 ){ + continue; + } + if( [[line uppercaseString] hasPrefix:@"BEGIN TREES"] ){ + NSLog(@"nexus tree"); + isTree = YES; + } + else if( [[line uppercaseString] hasPrefix:@"BEGIN DATA"] || [[line uppercaseString] hasPrefix:@"BEGIN CHARACTERS"] ){ + isSequence = YES; + NSLog(@"nexus sequence"); + } + if( isTree && isSequence)break; + } + } + [reader release]; + + if(isTree) return @"Tree"; + + return @"Alignment"; +} + +@end diff --git a/Seqotron/MFExternalOperation.h b/Seqotron/MFExternalOperation.h new file mode 100644 index 0000000..82c62e1 --- /dev/null +++ b/Seqotron/MFExternalOperation.h @@ -0,0 +1,46 @@ +// +// MFExternalOperation.h +// Seqotron +// +// Created by Mathieu on 13/01/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFOperation.h" + +extern NSString *MFExternalOperationLaunchPathKey; +extern NSString *MFExternalOperationArgumentsKey; +extern NSString *MFExternalOperationInputKey; +extern NSString *MFExternalOperationStdoutKey; +extern NSString *MFExternalOperationStderrKey; + +@interface MFExternalOperation : MFOperation{ + NSTask *_task; + NSFileHandle *_stdOutHandle; + NSFileHandle *_stdInHandle; + BOOL _parseStdout;// if YES the output is parsed. It can be an alignment or a tree + NSString *_currentLineStdOut; + NSTimer *_timer; + +} + +-(id)initWithOptions:(NSDictionary*)options; + +@end diff --git a/Seqotron/MFExternalOperation.m b/Seqotron/MFExternalOperation.m new file mode 100644 index 0000000..e78cf0a --- /dev/null +++ b/Seqotron/MFExternalOperation.m @@ -0,0 +1,115 @@ +// +// MFExternalOperation.m +// Seqotron +// +// Created by Mathieu on 13/01/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFExternalOperation.h" + +NSString *MFExternalOperationLaunchPathKey = @"tk.phylogenetics.external.operation.launchPath"; +NSString *MFExternalOperationArgumentsKey = @"tk.phylogenetics.external.operation.arguments"; +NSString *MFExternalOperationInputKey = @"tk.phylogenetics.external.operation.input"; +NSString *MFExternalOperationStdoutKey = @"tk.phylogenetics.external.read.stdout"; +NSString *MFExternalOperationStderrKey = @"tk.phylogenetics.external.read.stderr"; + +@implementation MFExternalOperation + + +-(id)initWithOptions:(NSDictionary*)options{ + if( self = [super initWithOptions:options]){ + _task = [[NSTask alloc]init]; + _stdOutHandle = nil; + _parseStdout = YES; + _stdInHandle = nil; + _currentLineStdOut = [@"" copy]; + + [_task setLaunchPath:[options objectForKey:MFExternalOperationLaunchPathKey]]; + [_task setArguments:[options objectForKey:MFExternalOperationArgumentsKey]]; + + if ( [[options objectForKey:MFExternalOperationStdoutKey] boolValue] ) { + NSPipe *stdOutPipe = [NSPipe pipe]; + _stdOutHandle = [stdOutPipe fileHandleForReading]; + [_task setStandardError: stdOutPipe]; + } + else if ( [[options objectForKey:MFExternalOperationStderrKey] boolValue] ) { + NSPipe *stdOutPipe = [NSPipe pipe]; + _stdOutHandle = [stdOutPipe fileHandleForReading]; + [_task setStandardError: stdOutPipe]; + + _timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(logMethod:) userInfo:nil repeats:YES]; + + [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes]; + } + } + return self; +} + +-(void)dealloc{ + [_currentLineStdOut release]; + [_task release]; + [super dealloc]; +} + + +-(void)main{ + NSLog(@"Operation running %@",self.description); + +// dispatch_sync(dispatch_get_main_queue(), ^{ +// [self.delegate operation:self setDescription:self.description]; +// }); + + [_task launch]; + + + NSData *data; + if( _stdOutHandle != nil ){ + + if( !_parseStdout ){ + data = [_stdOutHandle readDataToEndOfFile]; + } + else { + while ( (data = [_stdOutHandle availableData]) ){ + NSString *str = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding]; + [_currentLineStdOut release]; + _currentLineStdOut = [str copy]; + + [str release]; + } + } + } + else { + [_task waitUntilExit]; + } + NSLog(@"Operation finished"); +} + +-(void) cancel { + [_timer invalidate]; + [_task terminate]; + [super cancel]; +} + +// run on the main thread +- (void)logMethod:(NSTimer*)theTimer { + //NSLog(@"targetMethod: %d",[NSThread isMainThread]); + [self.delegate operation:self setDescription:_currentLineStdOut]; +} + +@end diff --git a/Seqotron/MFFASTAImporter.h b/Seqotron/MFFASTAImporter.h new file mode 100755 index 0000000..0575009 --- /dev/null +++ b/Seqotron/MFFASTAImporter.h @@ -0,0 +1,32 @@ +// +// MFFASTAImporter.h +// Seqotron +// +// Created by Mathieu Fourment on 7/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFSequenceImporter.h" + +@interface MFFASTAImporter : NSObject { + +} + +@end diff --git a/Seqotron/MFFASTAImporter.m b/Seqotron/MFFASTAImporter.m new file mode 100755 index 0000000..86790c0 --- /dev/null +++ b/Seqotron/MFFASTAImporter.m @@ -0,0 +1,110 @@ +// +// MFFASTAImporter.m +// Seqotron +// +// Created by Mathieu Fourment on 7/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFFASTAImporter.h" + +#import "MFSequence.h" +#import "MFReaderCluster.h" + +@implementation MFFASTAImporter + +- (MFSequenceSet *)readSequencesFromFile:(NSString *)path{ + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithFile:path]; + + MFSequenceSet *sequences = [self readSequences:reader]; + + [reader release]; + + return sequences; +} + +-(MFSequenceSet*)readSequencesFromURL:(NSURL*)url{ + NSString *content = [ NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil]; + MFSequenceSet *sequences = [self readSequencesFromString:content]; + + return sequences; +} + +-(MFSequenceSet*)readSequencesFromData:(NSData*)data{ + NSString *content = [[NSString alloc] initWithData: data encoding: NSASCIIStringEncoding]; + + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithString:content]; + + MFSequenceSet *sequences = [self readSequences:reader]; + + [reader release]; + [content release]; + + return sequences; +} + +-(MFSequenceSet*)readSequencesFromString:(NSString*)content{ + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithString:content]; + + MFSequenceSet *sequences = [self readSequences:reader]; + + [reader release]; + + return sequences; +} + +- (MFSequenceSet *)readSequences:(MFReaderCluster *)reader{ + + MFSequenceSet *sequences = [[MFSequenceSet alloc] init]; + + NSMutableString *sequenceBuffer = [ [NSMutableString alloc] initWithCapacity:100 ]; + NSMutableString *nameBuffer = [ [NSMutableString alloc] initWithCapacity:100 ]; + [sequenceBuffer setString: @""]; + + NSString *line; + while ( (line = [reader readLine]) ) { + + if( [line hasPrefix:@">"] ){ + if( ![ sequenceBuffer isEqualToString: @"" ]){ + MFSequence *sequence = [[ MFSequence alloc] initWithString:sequenceBuffer name:nameBuffer]; + + [sequences addSequence:sequence]; + [sequence release]; + [sequenceBuffer setString: @""]; + } + [nameBuffer setString:[line substringFromIndex:1]]; + } + else if( [line length] > 0 ) { + NSArray* temp = [[line uppercaseString] componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + [sequenceBuffer appendString: [temp componentsJoinedByString:@""]]; + } + } + + if( ![ sequenceBuffer isEqualToString: @"" ]){ + MFSequence *sequence = [[ MFSequence alloc] initWithString:sequenceBuffer name:nameBuffer]; + [sequences addSequence:sequence]; + [sequence release]; + } + + [sequenceBuffer release]; + [nameBuffer release]; + + return [sequences autorelease]; +} + +@end diff --git a/Seqotron/MFFileReader.h b/Seqotron/MFFileReader.h new file mode 100755 index 0000000..886b7dc --- /dev/null +++ b/Seqotron/MFFileReader.h @@ -0,0 +1,38 @@ +// +// MFFileReader.h +// Seqotron +// +// Created by Mathieu Fourment on 7/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFReaderCluster.h" + +@interface MFFileReader : MFReaderCluster{ + FILE *_file; + char _buffer[4097]; + NSMutableString *_line; +} + +-(id)initWithFile:(NSString*)path; + +//-(NSString*) readLine; + +@end diff --git a/Seqotron/MFFileReader.m b/Seqotron/MFFileReader.m new file mode 100755 index 0000000..a6f32f9 --- /dev/null +++ b/Seqotron/MFFileReader.m @@ -0,0 +1,166 @@ +// +// MFFileReader.m +// Seqotron +// +// Created by Mathieu Fourment on 7/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFFileReader.h" + +@implementation MFFileReader + +-(id)initWithFile:(NSString*)path{ + self = [super init]; + if (self) { + _line = [[NSMutableString alloc]init]; + _file = fopen([path UTF8String], "r"); + if(_file == NULL ){ + NSLog(@"Cannot open file %@",path ); + } + _buffer[0] = '\0'; + } + return self; +} + +-(void)dealloc{ + fclose(_file); + [_line release]; + [super dealloc]; +} + +-(NSString*) readLine2{ + if ( feof(_file) ) { + return nil; + } + + [_line setString:@""]; + char c; + while ( !feof(_file) ) { + c = fgetc(_file); + + if ( c == '\n' || c == '\r' ) { + // check if it is windows + if ( c == '\r' ) { + fpos_t pos; + fgetpos(_file, &pos); + char cc = fgetc(_file); + if( cc != '\n' ){ + fsetpos(_file, &pos); + } + } + break; + } + else { + if( c == EOF ){ + break; + } + else { + [_line appendFormat:@"%c", c]; + } + } + } + +// int charsRead; +// do { +// if(fscanf(_file, "%4095[^\n]%n%*c", _buffer, &charsRead) == 1) +// [_line appendFormat:@"%s", _buffer]; +// else +// break; +// } while(charsRead == 4095); + + return _line; +} + +-(NSString*) readLine{ + + size_t ret,i,len; + char c; + + if ( feof(_file) && _buffer[0] == '\e' ) { + return nil; + } + + len = strlen(_buffer); + + for ( i = 0; i < len; i++ ) { + c = _buffer[i]; + if( c == '\n' || c == '\r' ){ + _buffer[i] = '\0'; + [_line setString:[NSString stringWithUTF8String:_buffer]]; + + if( i < len-1){ + if( c == '\r' && _buffer[i+1] == '\n'){ + i++; + } + } + memmove(&_buffer[0], &_buffer[i+1], (len-i-1)*sizeof(char)); + _buffer[len-i-1] = '\0'; + + return _line; + } + } + + [_line setString:[NSString stringWithUTF8String:_buffer]]; + + // last line of the file without \n and \r at the end + if( i == strlen(_buffer) && feof(_file) ){ + _buffer[0] = '\e'; + return _line; + } + + _buffer[0] = '\0'; + + while ( 1 ) { + ret = fread(_buffer, sizeof(char), 4096, _file); + if( ret == 0 ){ + _buffer[0] = '\e'; + break; + } + + for ( i = 0; i < ret; i++ ) { + c = _buffer[i]; + if( c == '\n' || c == '\r' ){ + _buffer[i] = '\0'; + [_line appendFormat:@"%s", _buffer]; + + if( i < ret-1){ + if( c == '\r' && _buffer[i+1] == '\n'){ + i++; + } + } + memmove(&_buffer[0], &_buffer[i+1], (ret-i-1)*sizeof(char)); + _buffer[ret-i-1] = '\0'; + + return _line; + } + } + _buffer[i] = '\0'; + [_line appendFormat:@"%s", _buffer]; + + } + return _line; +} + +- (void)rewind{ + rewind(_file); + _buffer[0] = '\0'; + [_line setString:@""]; +} + +@end diff --git a/Seqotron/MFGDEImporter.h b/Seqotron/MFGDEImporter.h new file mode 100755 index 0000000..e259a42 --- /dev/null +++ b/Seqotron/MFGDEImporter.h @@ -0,0 +1,30 @@ +// +// MFGDEImporter.h +// Seqotron +// +// Created by Mathieu Fourment on 7/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFSequenceImporter.h" + +@interface MFGDEImporter : NSObject + +@end diff --git a/Seqotron/MFGDEImporter.m b/Seqotron/MFGDEImporter.m new file mode 100755 index 0000000..0726521 --- /dev/null +++ b/Seqotron/MFGDEImporter.m @@ -0,0 +1,127 @@ +// +// MFFASTAImporter.m +// Seqotron +// +// Created by Mathieu Fourment on 7/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFGDEImporter.h" + +#import "MFSequence.h" +#import "MFReaderCluster.h" +#import "MFNucleotide.h" +#import "MFProtein.h" + +// http://cs.mcgill.ca/~birch/tutorials/GDE/overview/GDE.file_formats.html +// Flatfile format only + +@implementation MFGDEImporter + +- (MFSequenceSet *)readSequencesFromFile:(NSString *)path{ + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithFile:path]; + + MFSequenceSet *sequences = [self readSequences:reader]; + + [reader release]; + + return sequences; +} + +-(MFSequenceSet*)readSequencesFromURL:(NSURL*)url{ + NSString *content = [ NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil]; + MFSequenceSet *sequences = [self readSequencesFromString:content]; + + return sequences; +} + +-(MFSequenceSet*)readSequencesFromData:(NSData*)data{ + NSString *content = [[NSString alloc] initWithData: data encoding: NSASCIIStringEncoding]; + + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithString:content]; + + MFSequenceSet *sequences = [self readSequences:reader]; + + [reader release]; + [content release]; + + return sequences; +} + +-(MFSequenceSet*)readSequencesFromString:(NSString*)content{ + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithString:content]; + + MFSequenceSet *sequences = [self readSequences:reader]; + + [reader release]; + + return sequences; +} + +- (MFSequenceSet *)readSequences:(MFReaderCluster *)reader{ + + MFSequenceSet *sequences = [[MFSequenceSet alloc] init]; + + NSMutableString *sequenceBuffer = [ [NSMutableString alloc] initWithCapacity:100 ]; + NSMutableString *nameBuffer = [ [NSMutableString alloc] initWithCapacity:100 ]; + [sequenceBuffer setString: @""]; + + MFDataType *dataType = nil; + + NSString *line; + while ( (line = [reader readLine]) ) { + + if( [line hasPrefix:@"#"] || [line hasPrefix:@"%"] ){ + if( dataType == nil ){ + if( [line hasPrefix:@"%"] ){ + dataType = [[MFProtein alloc]init]; + } + else if( [line hasPrefix:@"#"] ){ + dataType = [[MFNucleotide alloc]init]; + } + } + if( ![ sequenceBuffer isEqualToString: @"" ]){ + + NSArray* temp = [sequenceBuffer componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + NSString *seq = [temp componentsJoinedByString:@""]; + MFSequence *sequence = [[MFSequence alloc]initWithString:seq name:nameBuffer dataType:dataType]; + + [sequences addSequence:sequence]; + [sequence release]; + [sequenceBuffer setString: @""]; + } + [nameBuffer setString:[line substringFromIndex:1]]; + } + else { + [sequenceBuffer appendString: [line uppercaseString]]; + } + } + + if( ![ sequenceBuffer isEqualToString: @"" ]){ + MFSequence *sequence = [[ MFSequence alloc] initWithString:sequenceBuffer name:nameBuffer]; + [sequences addSequence:sequence]; + [sequence release]; + } + + [sequenceBuffer release]; + [nameBuffer release]; + [dataType release]; + return [sequences autorelease]; +} + +@end diff --git a/Seqotron/MFGraphic.h b/Seqotron/MFGraphic.h new file mode 100755 index 0000000..bedf38c --- /dev/null +++ b/Seqotron/MFGraphic.h @@ -0,0 +1,102 @@ +// +// MFGraphic.h +// Seqotron +// +// Created by Mathieu on 5/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +@interface MFGraphic : NSObject{ + NSRect _bounds; + BOOL _isDrawingStroke; + NSColor *_strokeColor; + CGFloat _strokeWidth; + BOOL _isDrawingFill; + NSColor *_fillColor; +} + + +- (NSRect)bounds; + +- (void)setBounds:(NSRect)bounds; + +- (CGFloat)xPosition; + +- (CGFloat)yPosition; + +- (CGFloat)width; + +- (CGFloat)height; + +- (void)setXPosition:(CGFloat)xPosition; + +- (void)setYPosition:(CGFloat)yPosition; + +- (void)setWidth:(CGFloat)width; + +- (void)setHeight:(CGFloat)height; + +- (BOOL)isDrawingStroke; + +- (NSColor *)strokeColor; + +- (void)setStrokeColor:(NSColor*)color; + +- (CGFloat)strokeWidth; + +- (void)setStrokeWidth:(CGFloat)color; + + +- (BOOL)isDrawingFill; +- (NSColor *)fillColor; + +#pragma mark *** Drawing *** + +// Return the bounding box of everything the receiver might draw when sent a -draw...InView: message. The default implementation of this method returns a bounds that assumes the default implementations of -drawContentsInView: and -drawHandlesInView:. Subclasses that override this probably have to override +keyPathsForValuesAffectingDrawingBounds too. +- (NSRect)drawingBounds; + +// Draw the contents the receiver in a specific view. Use isBeingCreatedOrEditing if the graphic draws differently during its creation or while it's being edited. The default implementation of this method just draws the result of invoking -bezierPathForDrawing using the current fill and stroke parameters. Subclasses have to override either this method or -bezierPathForDrawing. Subclasses that override this may have to override +keyPathsForValuesAffectingDrawingBounds, +keyPathsForValuesAffectingDrawingContents, and -drawingBounds too. +- (void)drawContentsInView:(NSView *)view isSelected:(BOOL)isSelected; + +// Return a bezier path that can be stroked and filled to draw the graphic, if the graphic can be drawn so simply, nil otherwise. The default implementation of this method returns nil. Subclasses have to override either this method or -drawContentsInView:. Any returned bezier path should already have the graphic's current stroke width set in it. +- (NSBezierPath *)bezierPathForDrawing; + +// Draw the handles of the receiver in a specific view. The default implementation of this method just invokes -drawHandleInView:atPoint: for each point at the corners and on the sides of the rectangle returned by -bounds. Subclasses that override this probably have to override -handleUnderPoint: too. +- (void)drawHandlesInView:(NSView *)view; + +// Draw handle at a specific point in a specific view. Subclasses that override -drawHandlesInView: can invoke this to easily draw handles whereever they like. +- (void)drawHandleInView:(NSView *)view atPoint:(NSPoint)point; + + +// Set the color of the graphic, whatever that means. The default implementation of this method just sets isDrawingFill to YES and fillColor to the passed-in color. In Sketch this method is invoked when the user drops a color chip on the graphic or uses the color panel to change the color of all of the selected graphics. +- (void)setColor:(NSColor *)color; + +// Return YES if it's useful to let the user toggle drawing of the fill or stroke, NO otherwise. The default implementations of these methods return YES. +- (BOOL)canSetDrawingFill; +- (BOOL)canSetDrawingStroke; + +#pragma mark *** Editing *** + +// Given that the receiver has just been created or double-clicked on or something, create and return a view that can present its editing interface to the user, or return nil. The returned view should be suitable for becoming a subview of a view whose bounds is passed in. Its frame should match the bounds of the receiver. The receiver should not assume anything about the lifetime of the returned editing view; it may remain in use even after subsequent invocations of this method, which should, again, create a new editing view each time. In other words, overrides of this method should be prepared for a graphic to have more than editing view outstanding. The default implementation of this method returns nil. In Sketch SKTText overrides it. +- (NSView *)newEditingViewWithSuperviewBounds:(NSRect)superviewBounds NS_RETURNS_NOT_RETAINED; + +// Given an editing view that was returned by a previous invocation of -newEditingViewWithSuperviewBounds:, tear down whatever connections exist between it and the receiver. +- (void)finalizeEditingView:(NSView *)editingView; + + +@end diff --git a/Seqotron/MFGraphic.m b/Seqotron/MFGraphic.m new file mode 100755 index 0000000..3fb0dc1 --- /dev/null +++ b/Seqotron/MFGraphic.m @@ -0,0 +1,252 @@ +// +// MFGraphic.m +// Seqotron +// +// Created by Mathieu on 5/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFGraphic.h" + + +static CGFloat MFGraphicHandleWidth = 6.0f; +static CGFloat MFGraphicHandleHalfWidth = 6.0f / 2.0f; + +@implementation MFGraphic + +- (id)init { + if ( self = [super init] ) { + _bounds = NSZeroRect; + _strokeColor = [[NSColor blackColor] retain]; + _strokeWidth = 0.5f; + _isDrawingStroke = YES; + _isDrawingFill = NO; + _fillColor = [[NSColor whiteColor] retain]; + + } + return self; + +} + +-(void)dealloc{ + [_strokeColor release]; + [_fillColor release]; + [super dealloc]; +} + +- (NSRect)bounds { + return _bounds; +} + +- (void)setBounds:(NSRect)bounds { + _bounds = bounds; + +} + +- (CGFloat)xPosition { + return [self bounds].origin.x; +} +- (CGFloat)yPosition { + return [self bounds].origin.y; +} +- (CGFloat)width { + return [self bounds].size.width; +} +- (CGFloat)height { + return [self bounds].size.height; +} +- (void)setXPosition:(CGFloat)xPosition { + NSRect bounds = [self bounds]; + bounds.origin.x = xPosition; + [self setBounds:bounds]; +} +- (void)setYPosition:(CGFloat)yPosition { + NSRect bounds = [self bounds]; + bounds.origin.y = yPosition; + [self setBounds:bounds]; +} +- (void)setWidth:(CGFloat)width { + NSRect bounds = [self bounds]; + bounds.size.width = width; + [self setBounds:bounds]; +} +- (void)setHeight:(CGFloat)height { + NSRect bounds = [self bounds]; + bounds.size.height = height; + [self setBounds:bounds]; +} + +- (BOOL)isDrawingStroke { + return _isDrawingStroke; +} + +- (NSColor *)strokeColor { + return [[_strokeColor retain] autorelease]; +} + +- (void)setStrokeColor:(NSColor*)color { + _strokeColor = color; +} + +- (CGFloat)strokeWidth { + return _strokeWidth; +} + +- (void)setStrokeWidth:(CGFloat)color { + _strokeWidth = color; +} + +- (BOOL)isDrawingFill { + return _isDrawingFill; +} + +- (NSColor *)fillColor { + return [[_fillColor retain] autorelease]; +} + +- (void)setColor:(NSColor *)color { + // Can we fill the graphic? + if ([self canSetDrawingFill]) { + // Are we filling it? If not, start, using the new color. + if (![self isDrawingFill]) { + [self setValue:[NSNumber numberWithBool:YES] forKey:@"drawingFill"]; + } + [self setValue:color forKey:@"fillColor"]; + + } + +} + +- (NSRect)drawingBounds2 { + CGFloat outset = MFGraphicHandleHalfWidth; + + CGFloat inset = 0.0f - outset; + NSRect drawingBounds = NSInsetRect([self bounds], inset, inset); + + // -drawHandleInView:atPoint: draws a one-unit drop shadow too. + drawingBounds.size.width += 1.0f; + drawingBounds.size.height += 1.0f; + return drawingBounds; +} + +- (NSRect)drawingBounds { + CGFloat outset = MFGraphicHandleHalfWidth; + if ([self isDrawingStroke]) { + CGFloat strokeOutset = [self strokeWidth] / 2.0f; + if (strokeOutset > outset) { + outset = strokeOutset; + } + } + CGFloat inset = 0.0f - outset; + NSRect drawingBounds = NSInsetRect([self bounds], inset, inset); + + // -drawHandleInView:atPoint: draws a one-unit drop shadow too. + drawingBounds.size.width += 1.0f; + drawingBounds.size.height += 1.0f; + return drawingBounds; + +} + +- (void)drawContentsInView:(NSView *)view isSelected:(BOOL)isSelected{ + + // If the graphic is so so simple that it can be boiled down to a bezier path then just draw a bezier path. It's -bezierPathForDrawing's responsibility to return a path with the current stroke width. + NSBezierPath *path = [self bezierPathForDrawing]; + if (path) { + + if ([self isDrawingStroke]) { + [[self strokeColor] set]; + [path stroke]; + } + } + +} + +- (NSBezierPath *)bezierPathForDrawing { + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)] + userInfo:nil]; +} + + +- (void)drawHandlesInView:(NSView *)view { + + // Draw handles at the corners and on the sides. + NSRect bounds = [self bounds]; + [self drawHandleInView:view atPoint:NSMakePoint(NSMinX(bounds), NSMinY(bounds))]; + [self drawHandleInView:view atPoint:NSMakePoint(NSMidX(bounds), NSMinY(bounds))]; + [self drawHandleInView:view atPoint:NSMakePoint(NSMaxX(bounds), NSMinY(bounds))]; + [self drawHandleInView:view atPoint:NSMakePoint(NSMinX(bounds), NSMidY(bounds))]; + [self drawHandleInView:view atPoint:NSMakePoint(NSMaxX(bounds), NSMidY(bounds))]; + [self drawHandleInView:view atPoint:NSMakePoint(NSMinX(bounds), NSMaxY(bounds))]; + [self drawHandleInView:view atPoint:NSMakePoint(NSMidX(bounds), NSMaxY(bounds))]; + [self drawHandleInView:view atPoint:NSMakePoint(NSMaxX(bounds), NSMaxY(bounds))]; + +} + + +- (void)drawHandleInView:(NSView *)view atPoint:(NSPoint)point { + + // Figure out a rectangle that's centered on the point but lined up with device pixels. + NSRect handleBounds; + handleBounds.origin.x = point.x - MFGraphicHandleHalfWidth; + handleBounds.origin.y = point.y - MFGraphicHandleHalfWidth; + handleBounds.size.width = MFGraphicHandleWidth; + handleBounds.size.height = MFGraphicHandleWidth; + handleBounds = [view centerScanRect:handleBounds]; + + // Draw the shadow of the handle. + NSRect handleShadowBounds = NSOffsetRect(handleBounds, 1.0f, 1.0f); + [[NSColor controlDarkShadowColor] set]; + NSRectFill(handleShadowBounds); + + // Draw the handle itself. + [[NSColor knobColor] set]; + NSRectFill(handleBounds); + +} + +- (NSView *)newEditingViewWithSuperviewBounds:(NSRect)superviewBounds { + + // Live to be overridden. + return nil; + +} + + +- (void)finalizeEditingView:(NSView *)editingView { + + // Live to be overridden. + +} + +- (BOOL)canSetDrawingFill { + + // The default implementation of -drawContentsInView: can draw fills. + return YES; + +} + + +- (BOOL)canSetDrawingStroke { + + // The default implementation of -drawContentsInView: can draw strokes. + return YES; + +} + +@end diff --git a/Seqotron/MFJukeCantorDistanceMatrix.h b/Seqotron/MFJukeCantorDistanceMatrix.h new file mode 100755 index 0000000..e489d77 --- /dev/null +++ b/Seqotron/MFJukeCantorDistanceMatrix.h @@ -0,0 +1,28 @@ +// +// MFJukeCantorDistanceMatrix.h +// Seqotron +// +// Created by Mathieu on 5/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFDistanceMatrix.h" + +@interface MFJukeCantorDistanceMatrix : MFDistanceMatrix + +@end diff --git a/Seqotron/MFJukeCantorDistanceMatrix.m b/Seqotron/MFJukeCantorDistanceMatrix.m new file mode 100755 index 0000000..5b01f9b --- /dev/null +++ b/Seqotron/MFJukeCantorDistanceMatrix.m @@ -0,0 +1,47 @@ +// +// MFJukeCantorDistanceMatrix.m +// Seqotron +// +// Created by Mathieu on 5/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFJukeCantorDistanceMatrix.h" + +@implementation MFJukeCantorDistanceMatrix + +- (float)calculatePairwiseDistanceBetween:(MFSequence*)sequence1 and:(MFSequence*)sequence2{ + NSUInteger d = 0; + NSUInteger n = 0; + const char *c1 = [[sequence1 sequence]UTF8String]; + const char *c2 = [[sequence2 sequence]UTF8String]; + MFDataType *dataType = sequence1.dataType; + NSUInteger dimension = [sequence1 length]; + + for ( NSUInteger k = 0; k < dimension; k++ ) { + if( [dataType isKnownChar:c1[k]] && [dataType isKnownChar:c2[k]] ){ + if( c1[k] != c2[k] ){ + d++; + } + n++; + } + } + return -0.75 *log(1- (4.0/3.0)*(float)d/n); +} + +@end diff --git a/Seqotron/MFK2PDistanceMatrix.h b/Seqotron/MFK2PDistanceMatrix.h new file mode 100755 index 0000000..dcf75c1 --- /dev/null +++ b/Seqotron/MFK2PDistanceMatrix.h @@ -0,0 +1,28 @@ +// +// MFK2PDistanceMatrix.h +// Seqotron +// +// Created by Mathieu on 5/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFDistanceMatrix.h" + +@interface MFK2PDistanceMatrix : MFDistanceMatrix + +@end diff --git a/Seqotron/MFK2PDistanceMatrix.m b/Seqotron/MFK2PDistanceMatrix.m new file mode 100755 index 0000000..803a6f6 --- /dev/null +++ b/Seqotron/MFK2PDistanceMatrix.m @@ -0,0 +1,59 @@ +// +// MFK2PDistanceMatrix.m +// Seqotron +// +// Created by Mathieu on 5/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFK2PDistanceMatrix.h" + +@implementation MFK2PDistanceMatrix + +- (float)calculateDistanceBetween:(MFSequence*)sequence1 and:(MFSequence*)sequence2{ + float transversion = 0; + float transition = 0; + float n = 0; + const char *c1 = [[sequence1 sequence]UTF8String]; + const char *c2 = [[sequence2 sequence]UTF8String]; + MFDataType *dataType = sequence1.dataType; + NSUInteger dimension = [sequence1 length]; + + for ( NSUInteger k = 0; k < dimension; k++ ) { + if( [dataType isKnownChar:c1[k]] && [dataType isKnownChar:c2[k]] ){ + if( c1[k] != c2[k] ){ + if ( (c1[k] == 'A' && c2[k] == 'G') // A G + || (c1[k] == 'G' && c2[k] == 'A') // G A + || (c1[k] == 'C' && c2[k] == 'T') // C T + || (c1[k] == 'T' && c2[k] == 'C') // T C + || (c1[k] == 'C' && c2[k] == 'U') // C U + || (c1[k] == 'U' && c2[k] == 'C') ){ // U C + transition++; + } + else { + transversion++; + } + } + n++; + + } + } + return 0.5 * log(1.0/(1-(2*transition-transversion)/n)) + 0.25*log(1/(1-2*transversion/n)); +} + +@end diff --git a/Seqotron/MFK83DistanceMatrix.h b/Seqotron/MFK83DistanceMatrix.h new file mode 100644 index 0000000..b31e293 --- /dev/null +++ b/Seqotron/MFK83DistanceMatrix.h @@ -0,0 +1,28 @@ +// +// MFK83DistanceMatrix.h +// Seqotron +// +// Created by Mathieu on 11/05/2015. +// Copyright (c) 2015 University of Sydney. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFDistanceMatrix.h" + +@interface MFK83DistanceMatrix : MFDistanceMatrix + +@end diff --git a/Seqotron/MFK83DistanceMatrix.m b/Seqotron/MFK83DistanceMatrix.m new file mode 100644 index 0000000..ffebd4c --- /dev/null +++ b/Seqotron/MFK83DistanceMatrix.m @@ -0,0 +1,48 @@ +// +// MFK83DistanceMatrix.m +// Seqotron +// +// Created by Mathieu on 11/05/2015. +// Copyright (c) 2015 University of Sydney. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFK83DistanceMatrix.h" + +@implementation MFK83DistanceMatrix + +- (float)calculatePairwiseDistanceBetween:(MFSequence*)sequence1 and:(MFSequence*)sequence2{ + float d = 0; + NSUInteger n = 0; + const char *c1 = [[sequence1 sequence]UTF8String]; + const char *c2 = [[sequence2 sequence]UTF8String]; + MFDataType *dataType = sequence1.dataType; + NSUInteger dimension = [sequence1 length]; + + for ( NSUInteger k = 0; k < dimension; k++ ) { + if( [dataType isKnownChar:c1[k]] && [dataType isKnownChar:c2[k]] ){ + if( c1[k] != c2[k] ){ + d++; + } + n++; + } + } + float p = d/n; + return -log(1.0-p-0.2*p*p); +} + +@end diff --git a/Seqotron/MFLineGraphic.h b/Seqotron/MFLineGraphic.h new file mode 100755 index 0000000..acfa606 --- /dev/null +++ b/Seqotron/MFLineGraphic.h @@ -0,0 +1,41 @@ +// +// MFBranchGraphic.h +// Seqotron +// +// Created by Mathieu on 5/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFGraphic.h" + +@interface MFLineGraphic : MFGraphic{ + + // YES if the line's ending is to the right or below, respectively, it's beginning, NO otherwise. Because we reuse SKTGraphic's "bounds" property, we have to keep track of the corners of the bounds at which the line begins and ends. A more natural thing to do would be to just record two points, but then we'd be wasting an NSRect's worth of ivar space per instance, and have to override more SKTGraphic methods to boot. This of course raises the question of why SKTGraphic has a bounds property when it's not readily applicable to every conceivable subclass. Perhaps in the future it won't, but right now in Sketch it's the handy thing to do for four out of five subclasses. + BOOL _pointsRight; + BOOL _pointsDown; +} + +- (NSPoint)beginPoint; + +- (NSPoint)endPoint; + +- (void)setBeginPoint:(NSPoint)beginPoint; + +- (void)setEndPoint:(NSPoint)endPoint; + +@end diff --git a/Seqotron/MFLineGraphic.m b/Seqotron/MFLineGraphic.m new file mode 100755 index 0000000..dc08a70 --- /dev/null +++ b/Seqotron/MFLineGraphic.m @@ -0,0 +1,155 @@ +// +// MFLineGraphic.m +// Seqotron +// +// Created by Mathieu on 5/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFLineGraphic.h" + +@implementation MFLineGraphic + +- (NSString*)description{ + return [NSString stringWithFormat: @"MFLineGraphic x %f y %f w %f h %f",_bounds.origin.x,_bounds.origin.y, _bounds.size.width, _bounds.size.height ]; +} + +- (void)drawContentsInView:(NSView *)view isSelected:(BOOL)isSelected{ + + // If the graphic is so so simple that it can be boiled down to a bezier path then just draw a bezier path. It's -bezierPathForDrawing's responsibility to return a path with the current stroke width. + NSBezierPath *path = [self bezierPathForDrawing]; + if (path) { + if(isSelected){ + CGFloat x = NSMinX([self bounds]); + CGFloat y = NSMinY([self bounds]); + CGFloat xx = NSMaxX([self bounds]); + CGFloat stroke2 = [self strokeWidth]*6; + NSRect rect = NSMakeRect(x, y-stroke2, xx-x, stroke2*2); + [[NSColor knobColor] set]; + NSRectFill(rect); + } + if ([self isDrawingStroke]) { + [[self strokeColor] set]; + [path stroke]; + } + } + +} + +- (NSBezierPath *)bezierPathForDrawing { + NSBezierPath *path = [NSBezierPath bezierPath]; + [path moveToPoint:[self beginPoint]]; + [path lineToPoint:[self endPoint]]; + [path setLineWidth:[self strokeWidth]]; + return path; +} + +- (NSPoint)beginPoint { + + // Convert from our odd storage format to something natural. + NSPoint beginPoint; + NSRect bounds = [self bounds]; + beginPoint.x = _pointsRight ? NSMinX(bounds) : NSMaxX(bounds); + beginPoint.y = _pointsDown ? NSMinY(bounds) : NSMaxY(bounds); + return beginPoint; + +} + +- (NSPoint)endPoint { + + // Convert from our odd storage format to something natural. + NSPoint endPoint; + NSRect bounds = [self bounds]; + endPoint.x = _pointsRight ? NSMaxX(bounds) : NSMinX(bounds); + endPoint.y = _pointsDown ? NSMaxY(bounds) : NSMinY(bounds); + return endPoint; + +} + +- (void)setBeginPoint:(NSPoint)beginPoint { + + // It's easiest to compute the results of setting these points together. + [self setBounds:[[self class] boundsWithBeginPoint:beginPoint endPoint:[self endPoint] pointsRight:&_pointsRight down:&_pointsDown]]; + +} + + +- (void)setEndPoint:(NSPoint)endPoint { + + // It's easiest to compute the results of setting these points together. + [self setBounds:[[self class] boundsWithBeginPoint:[self beginPoint] endPoint:endPoint pointsRight:&_pointsRight down:&_pointsDown]]; + +} + +- (BOOL)canSetDrawingFill { + + // Don't let the user think we can fill a line. + return NO; + +} + + +- (BOOL)canSetDrawingStroke { + + // Don't let the user think can ever not stroke a line. + return NO; + +} + +- (BOOL)isDrawingFill { + + // You can't fill a line. + return NO; + +} + + +- (BOOL)isDrawingStroke { + + // You can't not stroke a line. + return YES; + +} + +- (void)setColor:(NSColor *)color { + + // Because lines aren't filled we'll consider the stroke's color to be the one. + [self setValue:color forKey:@"strokeColor"]; + +} + ++ (NSRect)boundsWithBeginPoint:(NSPoint)beginPoint endPoint:(NSPoint)endPoint pointsRight:(BOOL *)outPointsRight down:(BOOL *)outPointsDown { + + // Convert the begin and end points of the line to its bounds and flags specifying the direction in which it points. + BOOL pointsRight = beginPoint.x. +// + +#import + +#import "MFSequenceImporter.h" + +@interface MFMEGAImporter : NSObject + +@end diff --git a/Seqotron/MFMEGAImporter.m b/Seqotron/MFMEGAImporter.m new file mode 100755 index 0000000..40cd9ab --- /dev/null +++ b/Seqotron/MFMEGAImporter.m @@ -0,0 +1,197 @@ +// +// MFMEGAImporter.m +// Seqotron +// +// Created by Mathieu Fourment on 7/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFMEGAImporter.h" + + +#import "MFSequence.h" +#import "MFReaderCluster.h" +#import "MFString.h" + + +// does not allow spaces inside sequence names + +@implementation MFMEGAImporter + +- (MFSequenceSet *)readSequencesFromFile:(NSString *)path{ + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithFile:path]; + + MFSequenceSet *sequences = [self readSequences:reader]; + + [reader release]; + + return sequences; +} + +-(MFSequenceSet*)readSequencesFromURL:(NSURL*)url{ + NSString *content = [ NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil]; + MFSequenceSet *sequences = [self readSequencesFromString:content]; + + return sequences; +} + +-(MFSequenceSet*)readSequencesFromData:(NSData*)data{ + NSString *content = [[NSString alloc] initWithData: data encoding: NSASCIIStringEncoding]; + + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithString:content]; + + MFSequenceSet *sequences = [self readSequences:reader]; + + [reader release]; + [content release]; + + return sequences; +} + +-(MFSequenceSet*)readSequencesFromString:(NSString*)content{ + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithString:content]; + + MFSequenceSet *sequences = [self readSequences:reader]; + + [reader release]; + + return sequences; +} + +// white spaces are not allowed in the name of sequences +- (MFSequenceSet *)readSequences:(MFReaderCluster *)reader{ + + NSString *line; + while ( (line = [reader readLine]) ) { + if( ![line isEmpty] ){ + if( [[line uppercaseString] hasPrefix:@"#MEGA"] ){ + // Line after is a comment + while ( (line = [reader readLine]) ) { + if( [line hasPrefix:@"#"] ) break; + } + } + if( [line hasPrefix:@"#"] ){ + line = [line stringByTrimmingPaddingWhitespace]; + [reader rewind]; + if( [line rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]].location == NSNotFound ){ + return [self readSequentialSequences:reader]; + } + else { + return [self readInterleavedSequences:reader]; + } + } + } + } + + return nil; + +} + +- (MFSequenceSet *)readSequentialSequences:(MFReaderCluster *)reader{ + + MFSequenceSet *sequences = [[MFSequenceSet alloc] init]; + + NSMutableString *sequenceBuffer = [ [NSMutableString alloc] initWithCapacity:100 ]; + NSMutableString *nameBuffer = [ [NSMutableString alloc] initWithCapacity:100 ]; + [sequenceBuffer setString: @""]; + + NSString *line; + while ( (line = [reader readLine]) ) { + if( ![line isEmpty] ){ + if( [[line uppercaseString] hasPrefix:@"#MEGA"] ){ + // Line after is a comment + while ( (line = [reader readLine]) ) { + if( [line hasPrefix:@"#"] ) break; + } + } + if( [line hasPrefix:@"#"] ){ + if( ![ sequenceBuffer isEqualToString: @"" ]){ + NSArray* temp = [sequenceBuffer componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + NSString *seq = [temp componentsJoinedByString:@""]; + MFSequence *sequence = [[ MFSequence alloc] initWithString:seq name:nameBuffer]; + + [sequences addSequence:sequence]; + [sequence release]; + [sequenceBuffer setString: @""]; + } + [nameBuffer setString:[line substringFromIndex:1]]; + } + else { + [sequenceBuffer appendString: [line uppercaseString]]; + } + } + } + + if( ![ sequenceBuffer isEqualToString: @"" ]){ + MFSequence *sequence = [[ MFSequence alloc] initWithString:sequenceBuffer name:nameBuffer]; + [sequences addSequence:sequence]; + [sequence release]; + } + + [sequenceBuffer release]; + [nameBuffer release]; + + return [sequences autorelease]; + +} + +- (MFSequenceSet *)readInterleavedSequences:(MFReaderCluster *)reader{ + + MFSequenceSet *sequences = [[MFSequenceSet alloc] init]; + NSMutableDictionary *dict = [[NSMutableDictionary alloc]init]; + + NSString *line; + while ( (line = [reader readLine]) ) { + if( ![line isEmpty] ){ + if( [[line uppercaseString] hasPrefix:@"#MEGA"] ){ + // Line after is a comment + while ( (line = [reader readLine]) ) { + if( [line hasPrefix:@"#"] ) break; + } + } + if( [line hasPrefix:@"#"] ){ + NSUInteger index = [line rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]].location; + NSString *name = [line substringWithRange:NSMakeRange(1, index)]; + NSString *seq = [[line substringFromIndex:index+1]stringByTrimmingPaddingWhitespace]; + seq = [[seq componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]componentsJoinedByString:@""]; + + if( [dict objectForKey:name] ) { + [[dict objectForKey:name]appendString:seq]; + } + else { + NSMutableString *str = [seq mutableCopy]; + [dict setObject:str forKey:name]; + [str release]; + } + } + } + } + + for (NSString *name in [dict allKeys]) { + MFSequence *sequence = [[ MFSequence alloc] initWithString:[dict objectForKey:name] name:name]; + [sequences addSequence:sequence]; + [sequence release]; + } + + [dict release]; + + return [sequences autorelease]; + +} + +@end diff --git a/Seqotron/MFNBRFImporter.h b/Seqotron/MFNBRFImporter.h new file mode 100755 index 0000000..947287e --- /dev/null +++ b/Seqotron/MFNBRFImporter.h @@ -0,0 +1,30 @@ +// +// MFNBRFImporter.h +// Seqotron +// +// Created by Mathieu Fourment on 7/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFSequenceImporter.h" + +@interface MFNBRFImporter : NSObject + +@end diff --git a/Seqotron/MFNBRFImporter.m b/Seqotron/MFNBRFImporter.m new file mode 100755 index 0000000..6675cec --- /dev/null +++ b/Seqotron/MFNBRFImporter.m @@ -0,0 +1,143 @@ +// +// MFNBRFImporter.m +// Seqotron +// +// Created by Mathieu Fourment on 7/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFNBRFImporter.h" + +#import "MFSequence.h" +#import "MFReaderCluster.h" + +@implementation MFNBRFImporter + +/* + P1 - Protein (complete) + F1 - Protein (fragment) + D1 - DNA (e.g. EMBOSS seqret output) + DL - DNA (linear) + DC - DNA (circular) + RL - RNA (linear) + RC - RNA (circular) + N3 - tRNA + N1 - Other functional RNA + XX - Unknown + */ + +/* + >P1;CRAB_ANAPL + ALPHA CRYSTALLIN B CHAIN (ALPHA(B)-CRYSTALLIN). + MDITIHNPLI RRPLFSWLAP SRIFDQIFGE HLQESELLPA SPSLSPFLMR + SPIFRMPSWL ETGLSEMRLE KDKFSVNLDV KHFSPEELKV KVLGDMVEIH + GKHEERQDEH GFIAREFNRK YRIPADVDPL TITSSLSLDG VLTVSAPRKQ + SDVPERSIPI TREEKPAIAG AQRK* + + >P1;CRAB_BOVIN + ALPHA CRYSTALLIN B CHAIN (ALPHA(B)-CRYSTALLIN). + MDIAIHHPWI RRPFFPFHSP SRLFDQFFGE HLLESDLFPA STSLSPFYLR + PPSFLRAPSW IDTGLSEMRL EKDRFSVNLD VKHFSPEELK VKVLGDVIEV + HGKHEERQDE HGFISREFHR KYRIPADVDP LAITSSLSSD GVLTVNGPRK + QASGPERTIP ITREEKPAVT AAPKK* + */ + +- (MFSequenceSet *)readSequencesFromFile:(NSString *)path{ + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithFile:path]; + + MFSequenceSet *sequences = [self readSequences:reader]; + + [reader release]; + + return sequences; +} + +-(MFSequenceSet*)readSequencesFromURL:(NSURL*)url{ + NSString *content = [ NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil]; + MFSequenceSet *sequences = [self readSequencesFromString:content]; + + return sequences; +} + +-(MFSequenceSet*)readSequencesFromData:(NSData*)data{ + NSString *content = [[NSString alloc] initWithData: data encoding: NSASCIIStringEncoding]; + + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithString:content]; + + MFSequenceSet *sequences = [self readSequences:reader]; + + [reader release]; + [content release]; + + return sequences; +} + +-(MFSequenceSet*)readSequencesFromString:(NSString*)content{ + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithString:content]; + + MFSequenceSet *sequences = [self readSequences:reader]; + + [reader release]; + + return sequences; +} + +- (MFSequenceSet *)readSequences:(MFReaderCluster *)reader{ + + MFSequenceSet *sequences = [[MFSequenceSet alloc] init]; + + NSMutableString *sequenceBuffer = [ [NSMutableString alloc] initWithCapacity:100 ]; + NSMutableString *nameBuffer = [ [NSMutableString alloc] initWithCapacity:100 ]; + [sequenceBuffer setString: @""]; + NSCharacterSet *charSet = [NSCharacterSet characterSetWithCharactersInString:@" \t*"]; + + NSString *line; + while ( (line = [reader readLine]) ) { + + // P1, F1, DL, DC, RL, RC, XX + if( [line hasPrefix:@">"] ){ + if( ![ sequenceBuffer isEqualToString: @"" ]){ + NSString *seq = [sequenceBuffer stringByTrimmingCharactersInSet:charSet]; + MFSequence *sequence = [[ MFSequence alloc] initWithString:seq name:nameBuffer]; + + [sequences addSequence:sequence]; + [sequence release]; + [sequenceBuffer setString: @""]; + } + [nameBuffer setString:[line substringFromIndex:4]]; + [reader readLine]; // that's a comment line + } + else { + [sequenceBuffer appendString: [line uppercaseString]]; + } + } + + if( ![sequenceBuffer isEqualToString: @"" ]){ + NSString *seq = [sequenceBuffer stringByTrimmingCharactersInSet:charSet]; + MFSequence *sequence = [[ MFSequence alloc] initWithString:seq name:nameBuffer]; + [sequences addSequence:sequence]; + [sequence release]; + } + + [sequenceBuffer release]; + [nameBuffer release]; + + return [sequences autorelease]; +} + +@end diff --git a/Seqotron/MFNamesView.h b/Seqotron/MFNamesView.h new file mode 100755 index 0000000..c9a1ab5 --- /dev/null +++ b/Seqotron/MFNamesView.h @@ -0,0 +1,45 @@ +// +// MFNamesView.h +// Seqotron +// +// Created by Mathieu Fourment on 25/01/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + + +@interface MFNamesView : NSView { + + NSMutableDictionary *_attsDict; + CGFloat _residueHeight; + CGFloat _residueWidth; + CGFloat _lineGap; + + NSPoint dragStartPoint; + NSPoint dragPoint; + NSArray *_sequences; + NSIndexSet *_selectionIndexes; +} + +@property (readwrite,copy) NSString *fontName; +@property (readwrite) CGFloat fontSize; +@property (readwrite) CGFloat rowSpacing; + +@end + diff --git a/Seqotron/MFNamesView.m b/Seqotron/MFNamesView.m new file mode 100755 index 0000000..e6518fe --- /dev/null +++ b/Seqotron/MFNamesView.m @@ -0,0 +1,294 @@ +// +// MFNamesView.m +// Seqotron +// +// Created by Mathieu Fourment on 25/01/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFNamesView.h" + +#import "NSObject+TDBindings.h" + +@implementation MFNamesView + +@synthesize fontName,fontSize,rowSpacing; + +-(void)awakeFromNib { + [super awakeFromNib]; + NSLog(@"MFNamesView view awakeFromNib"); + _attsDict = [[NSMutableDictionary alloc] init]; + + fontSize = [[[NSUserDefaults standardUserDefaults] objectForKey:@"MFDefaultSequenceFontSize"]floatValue]; + fontName = [[[NSUserDefaults standardUserDefaults] objectForKey:@"MFDefaultSequenceFontName"]copy]; + //_rowSpacing = [[[NSUserDefaults standardUserDefaults] objectForKey:@"MFDefaultSequenceRowSpacing"]floatValue]; + + _sequences = nil; + _selectionIndexes = nil; + [self initSize]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(superviewResized:) + name:NSViewFrameDidChangeNotification object:[self superview]]; +} + +-(void)dealloc{ + NSLog(@"MFNanesView dealloc"); + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [fontName release]; + [_attsDict release]; + [_sequences release]; + [_selectionIndexes release]; + [super dealloc]; +} + +- (BOOL)isFlipped{ + return YES; +} + +- (BOOL)acceptsFirstResponder { + return YES; +} + +- (void)drawRect:(NSRect)dirtyRect{ + // Draw background + [[NSColor whiteColor] set]; + NSRectFill(dirtyRect); + + NSUInteger numberOfSequences = [_sequences count]; + if( numberOfSequences == 0 ){ + return; + } + + NSUInteger nRow = MIN(ceil(dirtyRect.size.height/(_residueHeight + self.rowSpacing)), numberOfSequences); + NSUInteger seqPos = dirtyRect.origin.y/(_residueHeight + self.rowSpacing); + + // Draw selection background + if( [self.selectionIndexes count] > 0 ){ + //[[[NSColor redColor]colorWithAlphaComponent:0.3]set]; + [[NSColor knobColor]set]; + + for ( NSUInteger i = seqPos; i < MIN(numberOfSequences,seqPos+nRow); i++ ) { + + if( [self.selectionIndexes containsIndex:i] ){ + NSRect selected = NSMakeRect(0, (_residueHeight+self.rowSpacing)*i+self.rowSpacing/2, [self frame].size.width, (_residueHeight+self.rowSpacing)); + NSRectFillUsingOperation(selected, NSCompositeSourceAtop); + } + } + } + + NSPoint point = NSMakePoint(0, 0); + + [[NSColor blackColor] set]; + // Unlike drawSequences in MFSequencesView I draw the whole the sequence name instead of the sequences visible in clipview + for ( NSUInteger i = seqPos; i < MIN(numberOfSequences,seqPos+nRow); i++ ) { + NSString *name = [_sequences objectAtIndex:i]; + point.y = (i * (_residueHeight + self.rowSpacing)) - _lineGap + self.rowSpacing; + [name drawAtPoint:point withAttributes:_attsDict]; + } +} + + +- (void)mouseDown:(NSEvent *)anEvent { + if( [_sequences count] == 0 ) return; + + if ( !([anEvent modifierFlags] & NSShiftKeyMask) ) { + dragStartPoint = [self convertPoint:[anEvent locationInWindow] fromView:nil]; + } +} + +- (void)mouseUp:(NSEvent *)anEvent{ + dragPoint = [self convertPoint: [anEvent locationInWindow] fromView:nil]; + + NSUInteger numberOfSequences = [_sequences count]; + + NSUInteger seqStart = dragStartPoint.y/(_residueHeight+self.rowSpacing); + // more space in the scrollview than sequences and we clicked it: remove all selections if any + if(seqStart >= numberOfSequences){ + if( [[self selectionIndexes] count] > 0 ){ + [self setSelectionIndexes:[NSIndexSet indexSet]]; + [self propagateValue:[NSIndexSet indexSet] forBinding:@"selectionIndexes"]; + } + return; + } + + NSUInteger seqEnd = dragPoint.y/(_residueHeight+self.rowSpacing); + + if( seqEnd == seqStart && [[self selectionIndexes]containsIndex:seqStart] ){ + if( [[self selectionIndexes] count] == 1 ){ + [self setSelectionIndexes:[NSIndexSet indexSet] ]; + [self propagateValue:[NSIndexSet indexSet] forBinding:@"selectionIndexes"]; + return; + } + else if( [[self selectionIndexes] count] > 1 && [anEvent modifierFlags] & NSCommandKeyMask ){ + NSMutableIndexSet *indexes = [[self selectionIndexes]mutableCopy]; + [indexes removeIndex:seqEnd]; + [self setSelectionIndexes:indexes ]; + [self propagateValue:indexes forBinding:@"selectionIndexes"]; + [indexes release]; + return; + } + } + + seqEnd = MAX(seqEnd,0); // check if released above the window + seqEnd = MIN(seqEnd, numberOfSequences-1); // check if released above the window + + if(seqStart > seqEnd ){ + NSUInteger temp = seqEnd; + seqEnd = seqStart; + seqStart = temp; + } + + NSMutableIndexSet *indexes; + + // replace selection + // shift: extend selection + if ( [anEvent modifierFlags] & NSShiftKeyMask ) { + indexes = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(seqStart, seqEnd-seqStart+1)]; + [indexes addIndexes:[self selectionIndexes]]; + } + // command: add selection + else if ( [anEvent modifierFlags] & NSCommandKeyMask ) { + indexes = [[NSMutableIndexSet alloc] initWithIndex:seqEnd]; + [indexes addIndexes:[self selectionIndexes]]; + + } + else { + indexes = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(seqStart, seqEnd-seqStart+1)]; + } + + [self setSelectionIndexes:indexes]; + [self propagateValue:indexes forBinding:@"selectionIndexes"]; + [indexes release]; + + [self autoscroll:anEvent]; + [self setNeedsDisplay: YES]; +} + +- (void)setSelectionIndexes:(NSIndexSet *)indexes{ + //if ( indexes != selectionIndexes ) { + [_selectionIndexes release]; + _selectionIndexes = [indexes copy]; + [self setNeedsDisplay:YES]; + //} +} + +- (NSIndexSet*)selectionIndexes{ + return _selectionIndexes; +} + +- (void)setSequences:(NSArray *)seqs{ + if ( _sequences != seqs ) { + [_sequences release]; + _sequences = [seqs copy]; + [self updateFrameSize]; + [self setNeedsDisplay:YES]; + } +} + +- (NSArray*)sequences{ + return _sequences; +} + +- (void)setFontSize:(CGFloat)size{ + if( fontSize != size ){ + fontSize = size; + [self initSize]; + [self setNeedsDisplay:YES]; + } +} + +- (CGFloat)fontSize{ + return fontSize; +} + +- (void)setRowSpacing:(CGFloat)spacing{ + if( rowSpacing != spacing ){ + rowSpacing = spacing; + [self setNeedsDisplay:YES]; + } +} + +- (CGFloat)rowSpacing{ + return rowSpacing; +} + +- (void)setFontName:(NSString*)name{ + if( [fontName isEqualToString:name] ){ + [fontName release]; + fontName = [name retain]; + [self initSize]; + [self setNeedsDisplay:YES]; + } +} + +- (NSString*)fontName{ + return fontName; +} + +- (void)superviewResized:(NSNotification *)notification{ + if( [_sequences count] > 0 ){ + [self updateFrameSize]; + [self setNeedsDisplay:YES]; + } +} + +-(void)updateFrameSize{ + NSUInteger numberOfSequences = [_sequences count]; + + NSUInteger maxLength = 0; + for ( int i = 0; i < numberOfSequences; i++ ) { + NSString *name = [_sequences objectAtIndex:i]; + if( [name length] > maxLength ){ + maxLength = [name length]; + } + } + + NSSize superViewSize = [[self superview]bounds].size; + + NSSize size; + size.height = (_residueHeight+self.rowSpacing) * numberOfSequences+self.rowSpacing; + size.width = _residueWidth * maxLength; + + // not enough sequences to fill the scrollview vertically + if(size.height < superViewSize.height){ + size.height = superViewSize.height; + } + // Sequence names are not long enough to fill the scrollview horizontally + if(size.width < superViewSize.width){ + size.width = superViewSize.width; + } + + if( !NSEqualSizes([self frame].size, size) ){ + [self setFrameSize:size]; + } +} + +-(void)initSize{ + NSFont *font = [NSFont fontWithName:fontName size:fontSize]; + [_attsDict setObject:font forKey:NSFontAttributeName]; + + NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:@"A" attributes:_attsDict]; + + _lineGap = [string size].height+[font descender] - [font capHeight]; + _residueHeight = [font capHeight]; + _residueWidth = [string size].width; + [string release]; + +} + +@end diff --git a/Seqotron/MFNeighborJoining.h b/Seqotron/MFNeighborJoining.h new file mode 100755 index 0000000..bc41dd5 --- /dev/null +++ b/Seqotron/MFNeighborJoining.h @@ -0,0 +1,48 @@ +// +// MFNeighborJoining.h +// Seqotron +// +// Created by Mathieu on 3/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFDistanceMatrix.h" +#import "MFTree.h" + +@interface MFNeighborJoining : NSObject{ + MFDistanceMatrix *_distanceMatrix; + NSUInteger _dimension; + NSMutableArray *_nodes; + + NSUInteger _ncluster; + NSUInteger _imin; + NSUInteger _jmin; + + float **_matrix; + float *_r; + int *_alias; +} + + +-(id)initWithDistanceMatrix:(MFDistanceMatrix*) matrix; + +- (MFTree*)inferTree; + +@end diff --git a/Seqotron/MFNeighborJoining.m b/Seqotron/MFNeighborJoining.m new file mode 100755 index 0000000..1d9fd9e --- /dev/null +++ b/Seqotron/MFNeighborJoining.m @@ -0,0 +1,154 @@ +// +// MFNeighborJoining.m +// Seqotron +// +// Created by Mathieu on 3/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFNeighborJoining.h" +#import "MFNode.h" + +@implementation MFNeighborJoining + +- (id)initWithDistanceMatrix:(MFDistanceMatrix*) matrix{ + if( self = [super init]){ + _distanceMatrix = [matrix retain]; + _dimension = matrix.dimension; + _ncluster = matrix.dimension; + _nodes = [[NSMutableArray alloc]initWithCapacity:_dimension]; + + _alias = malloc(_dimension*sizeof(int)); + _r = malloc(_dimension*sizeof(float)); + + for (NSUInteger i = 0; i < _dimension; i++) { + MFNode *node = [[MFNode alloc]initWithName:[matrix nameAtIndex:i]]; + [_nodes addObject:node]; + [node release]; + _alias[i] = (int)i; + } + _matrix = [matrix floatMatrix]; + } + return self; +} + +- (void)dealloc{ + free(_alias); + free(_r); + [_nodes release]; + [_distanceMatrix release]; + [super dealloc]; +} + +- (MFTree*)inferTree{ + if( _ncluster != _dimension ){ + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:[NSString stringWithFormat:@"Can not reuse %@ in MFNeighborJoining", NSStringFromSelector(_cmd)] + userInfo:nil]; + } + + while( _ncluster > 2 ){ + // calculate net divergence + for ( NSUInteger i = 0; i < _ncluster; i++ ) { + _r[i] = 0; + for ( NSUInteger j = 0; j < _ncluster; j++ ) { + _r[i] += _matrix[ _alias[i] ][ _alias[j] ]; + } + } + + [self findMinIndexes]; + + MFNode *node = [[MFNode alloc]initWithName:[NSString stringWithFormat:@"node%lu",_ncluster]]; + MFNode *inode = [_nodes objectAtIndex:_alias[_imin]]; + MFNode *jnode = [_nodes objectAtIndex:_alias[_jmin]]; + + + CGFloat il = (_matrix[ _alias[_imin] ][ _alias[_jmin] ] + (_r[_imin] - _r[_jmin])/(_ncluster-2))*0.5; + CGFloat jl = _matrix[ _alias[_imin] ][ _alias[_jmin] ]-il; + + [inode setBranchLength:MAX(il,0)]; + [jnode setBranchLength:MAX(jl,0)]; + + [node addChild:inode]; + [node addChild:jnode]; + + [_nodes replaceObjectAtIndex:_alias[_imin] withObject:node]; + [_nodes replaceObjectAtIndex:_alias[_jmin] withObject:[NSNull null]]; + + [node release]; + + // Recalculate distance matrix + + NSUInteger k = 0; + for ( ; k < _imin; k++) { + int ak = _alias[k]; + _matrix[ak][_alias[_imin]] = _matrix[_alias[_imin]][ak] = (_matrix[ _alias[k] ][ _alias[_imin] ] + _matrix[ _alias[k] ][ _alias[_jmin] ] - _matrix[ _alias[_imin] ][ _alias[_jmin] ]) * 0.5; + } + for ( k++; k < _jmin; k++) { + int ak = _alias[k]; + _matrix[ak][_alias[_imin]] = _matrix[_alias[_imin]][ak] = (_matrix[ _alias[k] ][ _alias[_imin] ] + _matrix[ _alias[k] ][ _alias[_jmin] ] - _matrix[ _alias[_imin] ][ _alias[_jmin] ]) * 0.5; + } + + for ( k++; k < _ncluster; k++) { + int ak = _alias[k]; + _matrix[ak][_alias[_imin]] = _matrix[_alias[_imin]][ak] = (_matrix[ _alias[k] ][ _alias[_imin] ] + _matrix[ _alias[k] ][ _alias[_jmin] ] - _matrix[ _alias[_imin] ][ _alias[_jmin] ]) * 0.5; + } + memmove(&_alias[_jmin], &_alias[_jmin+1], sizeof(int)*(_ncluster-_jmin-1)); + + _ncluster--; + } + + [self findMinIndexes]; + + MFNode *node = [[MFNode alloc]initWithName:@"node0"]; + MFNode *inode = [_nodes objectAtIndex:_alias[_imin]]; + MFNode *jnode = [_nodes objectAtIndex:_alias[_jmin]]; + + CGFloat il = _matrix[ _alias[_imin] ][ _alias[_jmin] ]*0.5; + CGFloat jl = _matrix[ _alias[_imin] ][ _alias[_jmin] ]-il; + [inode setBranchLength:MAX(il,0)]; + [jnode setBranchLength:MAX(jl,0)]; + + [node addChild:inode]; + [node addChild:jnode]; + + MFTree *tree = [[MFTree alloc] initWithRoot:node]; + [node release]; + + return [tree autorelease]; +} + +- (void)findMinIndexes{ + float min = INFINITY; + _imin = 0; + _jmin = 0; + float denom = 1.0/(_ncluster-2); + for( NSUInteger i = 0; i < _ncluster; i++ ){ + for( NSUInteger j = i+1; j < _ncluster; j++ ){ + float sij = _matrix[ _alias[i] ][ _alias[j] ] - (_r[i] + _r[j] ) * denom; + + if( sij < min ){ + _imin = i; + _jmin = j; + min = sij; + } + } + } +} + +@end diff --git a/Seqotron/MFNewickExporter.h b/Seqotron/MFNewickExporter.h new file mode 100644 index 0000000..f1e7bc5 --- /dev/null +++ b/Seqotron/MFNewickExporter.h @@ -0,0 +1,31 @@ +// +// MFNewickExporter.h +// Seqotron +// +// Created by Mathieu Fourment on 21/01/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFTreeExporter.h" +#import "MFTree.h" + +@interface MFNewickExporter : MFTreeExporter + +@end diff --git a/Seqotron/MFNewickExporter.m b/Seqotron/MFNewickExporter.m new file mode 100644 index 0000000..a896717 --- /dev/null +++ b/Seqotron/MFNewickExporter.m @@ -0,0 +1,140 @@ +// +// MFNewickExporter.m +// Seqotron +// +// Created by Mathieu Fourment on 21/01/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFNewickExporter.h" + +#import "MFTree.h" + +@implementation MFNewickExporter + + +-(NSString*)string{ + NSMutableString *str = [[NSMutableString alloc]init]; + + for (MFTree *tree in _trees) { + [str appendString:[self newick:tree options:_options]]; + [str appendString:@"\r"]; + } + return [str autorelease]; +} + + +-(NSString*)newick:(MFTree*)tree options:(NSDictionary*)options { + NSMutableString *newick = [[NSMutableString alloc]init]; + [self newickFromNode:[tree root] inString:newick options:options]; + [newick appendString:@";"]; + return [newick autorelease]; +} + + +-(void)newickFromNode:(MFNode*)node inString:(NSMutableString*)newick options:(NSDictionary*)options{ + if( ![node isLeaf] ){ + [newick appendString:@"("]; + for ( NSUInteger i = 0; i < [node childCount]; i++ ) { + [self newickFromNode:[node childAtIndex:i] inString:newick options:options]; + if(i < [node childCount]-1) [newick appendString:@","]; + } + [newick appendString:@")"]; + if( [options objectForKey:MFTreeExporterShowInternalNodeNameKey] && [[options objectForKey:MFTreeExporterShowInternalNodeNameKey]boolValue] ){ + if( node.parent != nil) [newick appendFormat:@"%@:%f", [node name], [node branchLength] ]; + } + else if( [options objectForKey:MFTreeExporterShowBootstrapNodeNameKey] && [[options objectForKey:MFTreeExporterShowBootstrapNodeNameKey]boolValue] ){ + if( node.parent != nil) [newick appendFormat:@"%@:%f", [node name], [node branchLength] ]; + } + else { + if( node.parent != nil) [newick appendFormat:@":%f", [node branchLength] ]; + } + } + else { + [newick appendFormat:@"%@:%f", [node name], [node branchLength] ]; + } +} + +#pragma mark -- old -- + +-(NSString*)string2{ + NSMutableString *str = [[NSMutableString alloc]init]; + + BOOL showInternal = ( [_options objectForKey:MFTreeExporterShowInternalNodeNameKey] && [[_options objectForKey:MFTreeExporterShowInternalNodeNameKey]boolValue]); + + for ( NSUInteger i = 0; i < [_trees count]; i++ ) { + MFTree *tree = [_trees objectAtIndex:i]; + [str appendString:[self newick:tree internalNodeName:showInternal]]; + [str appendString:@"\r"]; + } + return [str autorelease]; +} + + +-(NSString*)newick:(MFTree*)tree internalNodeName:(BOOL)internal { + NSMutableString *newick = [[NSMutableString alloc]init]; + [self newickFromNode:[tree root]internalNodeName:internal inString:newick]; + [newick appendString:@";"]; + return [newick autorelease]; +} + +-(void)newickFromNode:(MFNode*)node internalNodeName:(BOOL)internal inString:(NSMutableString*)newick { + if( ![node isLeaf] ){ + [newick appendString:@"("]; + for ( NSUInteger i = 0; i < [node childCount]; i++ ) { + [self newickFromNode:[node childAtIndex:i] internalNodeName:internal inString:newick]; + if(i < [node childCount]-1) [newick appendString:@","]; + } + [newick appendString:@")"]; + if( internal){ + if( node.parent != nil) [newick appendFormat:@"%@:%f", [node name], [node branchLength] ]; + } + else { + if( node.parent != nil) [newick appendFormat:@":%f", [node branchLength] ]; + } + } + else { + [newick appendFormat:@"%@:%f", [node name], [node branchLength] ]; + } +} + +-(void)newickFromNode2:(MFNode*)node inString:(NSMutableString*)newick options:(NSDictionary*)options{ + if( ![node isLeaf] ){ + [newick appendString:@"("]; + for ( NSUInteger i = 0; i < [node childCount]; i++ ) { + [self newickFromNode:[node childAtIndex:i] inString:newick options:options]; + if(i < [node childCount]-1) [newick appendString:@","]; + } + [newick appendString:@")"]; + if( [options objectForKey:MFTreeExporterShowInternalNodeNameKey] && [[options objectForKey:MFTreeExporterShowInternalNodeNameKey]boolValue] ){ + if( node.parent != nil) [newick appendFormat:@"%@:%f", [node name], [node branchLength] ]; + } + else if( [options objectForKey:MFTreeExporterShowBootstrapNodeNameKey] && [[options objectForKey:MFTreeExporterShowBootstrapNodeNameKey]boolValue] ){ + if( node.parent != nil) [newick appendFormat:@"%@:%f", [node name], [node branchLength] ]; + } + else { + if( node.parent != nil) [newick appendFormat:@":%f", [node branchLength] ]; + } + } + else { + [newick appendFormat:@"%@:%f", [node name], [node branchLength] ]; + } +} + + +@end diff --git a/Seqotron/MFNewickImporter.h b/Seqotron/MFNewickImporter.h new file mode 100644 index 0000000..de491ee --- /dev/null +++ b/Seqotron/MFNewickImporter.h @@ -0,0 +1,30 @@ +// +// MFNewickImporter.h +// Seqotron +// +// Created by Mathieu Fourment on 15/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFTreeImporter.h" + +@interface MFNewickImporter : NSObject + +@end diff --git a/Seqotron/MFNewickImporter.m b/Seqotron/MFNewickImporter.m new file mode 100644 index 0000000..ad62ce1 --- /dev/null +++ b/Seqotron/MFNewickImporter.m @@ -0,0 +1,90 @@ +// +// MFNewickImporter.m +// Seqotron +// +// Created by Mathieu Fourment on 15/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFNewickImporter.h" +#import "MFReaderCluster.h" +#import "MFString.h" +#import "MFTree.h" + +@implementation MFNewickImporter + +-(NSArray*)readTreesFromFile:(NSString*)path{ + + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithFile:path]; + + NSArray *trees = [self readTrees:reader]; + + [reader release]; + + return trees; +} + +-(NSArray*)readTreesFromURL:(NSURL*)url{ + NSString *content = [ NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil]; + NSArray *trees = [self readTreesFromString:content]; + return trees; +} + +-(NSArray*)readTreesFromData:(NSData*)data{ + + NSString *content = [[NSString alloc] initWithData: data encoding: NSASCIIStringEncoding]; + NSArray *trees = [self readTreesFromString:content]; + [content release]; + return trees; +} + +-(NSArray*)readTreesFromString:(NSString*)content{ + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithString:content]; + + NSArray *trees = [self readTrees:reader]; + + [reader release]; + + return trees; +} + +- (NSArray*)readTrees:(MFReaderCluster*)reader{ + NSString *line; + NSMutableArray *trees = [[NSMutableArray alloc]init]; + NSMutableString *mutString = [[NSMutableString alloc]init]; + while ( (line = [reader readLine]) ) { + line = [line stringByTrimmingPaddingWhitespace]; + if ( [line length] > 0 ) { + if( [line hasSuffix:@";"] ){ + [mutString appendString:line]; + MFTree *tree = [[MFTree alloc]initWithNewick:mutString]; + [trees addObject:tree]; + [tree release]; + [mutString setString:@""]; + } + else { + [mutString appendString:line]; + } + } + + } + [mutString release]; + return [trees autorelease]; +} + +@end diff --git a/Seqotron/MFNexusExporter.h b/Seqotron/MFNexusExporter.h new file mode 100644 index 0000000..21ccd88 --- /dev/null +++ b/Seqotron/MFNexusExporter.h @@ -0,0 +1,34 @@ +// +// MFNexusExporter.h +// Seqotron +// +// Created by Mathieu Fourment on 21/01/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#include "MFTreeExporter.h" + +#include "MFTree.h" + +extern NSString *MFNexusExporterTranslateTaxaKey; // default: YES + +@interface MFNexusExporter : MFTreeExporter + +@end diff --git a/Seqotron/MFNexusExporter.m b/Seqotron/MFNexusExporter.m new file mode 100644 index 0000000..47ac1d0 --- /dev/null +++ b/Seqotron/MFNexusExporter.m @@ -0,0 +1,117 @@ +// +// MFNexusExporter.m +// Seqotron +// +// Created by Mathieu Fourment on 21/01/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFNexusExporter.h" + +NSString *MFNexusExporterTranslateTaxaKey = @"tk.phylogenetics.seqotron.exporter.tree.nexus.translate"; + +@implementation MFNexusExporter + +-(NSString*)string{ + NSMutableString *str = [[NSMutableString alloc]init]; + + [str appendString:@"#NEXUS\r"]; + + [str appendString:@"BEGIN TREES;\r"]; + + if( ![_options objectForKey:MFNexusExporterTranslateTaxaKey] || [[_options objectForKey:MFNexusExporterTranslateTaxaKey]boolValue]){ + NSString *translate = [self setUpMap]; + [str appendString:translate]; + } + + for ( NSUInteger i = 0; i < [_trees count]; i++ ) { + MFTree *tree = [_trees objectAtIndex:i]; + [str appendFormat:@"tree TREE%lu = ",i]; + [str appendString:[self newick:tree]]; + [str appendString:@"\r"]; + } + + [str appendString:@"END;\r"]; + return [str autorelease]; +} + +-(NSString*)newick:(MFTree*)tree { + NSMutableString *newick = [[NSMutableString alloc]init]; + [self newickFromNode:[tree root] inString:newick]; + [newick appendString:@";"]; + return [newick autorelease]; +} + +-(void)newickFromNode:(MFNode*)node inString:(NSMutableString*)newick { + if( ![node isLeaf] ){ + [newick appendString:@"("]; + for ( NSUInteger i = 0; i < [node childCount]; i++ ) { + [self newickFromNode:[node childAtIndex:i] inString:newick]; + if(i < [node childCount]-1) [newick appendString:@","]; + } + [newick appendString:@")"]; + if( node.parent != nil){ + [newick appendFormat:@":%f", [node branchLength] ]; + } + } + else { + NSString *desc = [node name]; + if( _map ){ + desc = [_map objectForKey:desc]; + } + [newick appendFormat:@"%@:%f", desc, [node branchLength] ]; + } + + if( [[node attributes]count] > 0 ){ + NSMutableString *comment = [[NSMutableString alloc]init]; + for (NSString *key in [[node attributes]allKeys]) { + //if( [_options objectForKey:key] && [[_options objectForKey:key]boolValue] ){ + // do not print Seqotron private tags + if( ![key hasPrefix:@"?"] ){ + [comment appendFormat:@"%@=%@,",key, [node.attributes objectForKey:key] ]; + } + } + if( [comment length] > 0 ){ + [comment deleteCharactersInRange:NSMakeRange([comment length]-1, 1)]; + [newick appendFormat:@"[&%@]", comment]; + } + [comment release]; + } +} + + +-(NSString*) setUpMap{ + if( !_map )_map = [[NSMutableDictionary alloc]init]; + NSMutableString *translate = [[NSMutableString alloc]init]; + [translate appendString:@"\tTranslate\r"]; + MFTree *tree = [_trees objectAtIndex:0]; + __block NSUInteger i = 1; + [tree enumerateNodesWithAlgorithm:MFTreeTraverseAlgorithmPostorder usingBlock:^(MFNode *node){ + if( [node isLeaf]){ + NSString *alias = [NSString stringWithFormat:@"%lu",i]; + [_map setObject: alias forKey:[node name]]; + [translate appendFormat:@"\t\t%@ %@,\r", alias, [node name]]; + i++; + } + }]; + [translate deleteCharactersInRange:NSMakeRange([translate length]-2, 1)]; // remove the last comma + [translate appendString:@";\r"]; + return [translate autorelease]; +} + +@end diff --git a/Seqotron/MFNexusImporter.h b/Seqotron/MFNexusImporter.h new file mode 100755 index 0000000..c9fd60b --- /dev/null +++ b/Seqotron/MFNexusImporter.h @@ -0,0 +1,43 @@ +// +// MFNexusSequenceImporter.h +// Seqotron +// +// Created by Mathieu Fourment on 4/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFSequenceImporter.h" +#import "MFTreeImporter.h" +#import "MFDataType.h" + +@interface MFNexusImporter : NSObject { + NSInteger _numberOfSequences; + NSUInteger _numberOfSites; + BOOL _nolabels; + BOOL _interleaved; + MFDataType *_dataType; + NSString *_gap; + NSString *_missing; + NSString *_matchchar; + NSUInteger _contentsEnd; + NSMutableArray *_taxa; +} + +@end diff --git a/Seqotron/MFNexusImporter.m b/Seqotron/MFNexusImporter.m new file mode 100755 index 0000000..dddd9a4 --- /dev/null +++ b/Seqotron/MFNexusImporter.m @@ -0,0 +1,541 @@ +// +// MFNexusSequenceImporter.m +// Seqotron +// +// Created by Mathieu Fourment on 4/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFNexusImporter.h" +#import "MFString.h" +#import "MFSequence.h" +#import "MFNucleotide.h" +#import "MFProtein.h" +#import "MFReaderCluster.h" +#import "MFTree.h" + +@implementation MFNexusImporter + +- (id)init{ + if ( (self = [super init]) ) { + _nolabels = NO; + _dataType = nil; + _contentsEnd = 0; + } + return self; +} + +-(void)dealloc{ + [_dataType release]; + [super dealloc]; +} + +- (MFSequenceSet *)readSequencesFromFile:(NSString *)path{ + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithFile:path]; + + MFSequenceSet *sequences = [self readSequences:reader]; + + [reader release]; + + return sequences; +} + +-(MFSequenceSet*)readSequencesFromURL:(NSURL*)url{ + NSString *content = [ NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil]; + MFSequenceSet *sequences = [self readSequencesFromString:content]; + + return sequences; +} + +-(MFSequenceSet*)readSequencesFromData:(NSData*)data{ + NSString *content = [[NSString alloc] initWithData: data encoding: NSASCIIStringEncoding]; + + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithString:content]; + + MFSequenceSet *sequences = [self readSequences:reader]; + + [reader release]; + [content release]; + + return sequences; +} + +-(MFSequenceSet*)readSequencesFromString:(NSString*)content{ + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithString:content]; + + MFSequenceSet *sequences = [self readSequences:reader]; + + [reader release]; + + return sequences; +} + +-(MFSequenceSet*)readSequences:(MFReaderCluster*)reader{ + NSRegularExpression *blockRegex = [NSRegularExpression regularExpressionWithPattern:@"^\\s*begin\\s+((?:data|characters|taxa));" options:NSRegularExpressionCaseInsensitive error:nil]; + + + MFSequenceSet *sequences = nil; + + NSString *line; + + while ( (line = [reader readLine]) ) { + + if ([line isEmpty]) continue; + + NSArray* block = [blockRegex matchesInString:line options:0 range: NSMakeRange(0, [line length])]; + + if( [block count] == 1 ){ + NSTextCheckingResult* match = [block objectAtIndex:0]; + NSString *name = [line substringWithRange:[match rangeAtIndex:1]]; + + if( [ [name uppercaseString] isEqualToString:@"DATA"] ){ + sequences = [self readDataBlock:reader]; + break; + } + else if( [ [name uppercaseString] isEqualToString:@"CHARACTERS"] ){ + sequences = [self readDataBlock:reader]; + break; + } + else if( [ [name uppercaseString] isEqualToString:@"TAXA"] ){ + [self readTaxaBlock:reader]; + } + } + } + if( [sequences size] != _numberOfSequences ){ + NSLog(@"Number of sequences of found: %tu Number of sequences expected from ntax %tu", [sequences size], _numberOfSequences ); + } + + if( _dataType != nil ){ + for (MFSequence *sequence in [sequences sequences]) { + [sequence setDataType:_dataType]; + } + } + + return sequences; +} + + +-(NSString*)nextLineUncommented:(MFReaderCluster*) reader{ + + NSString *line = [reader readLine]; + + NSInteger indexStart = [line rangeOfString:@"["].location; + NSInteger indexStop = [line rangeOfString:@"]"].location; + + //no comment + if( indexStart == NSNotFound && indexStop == NSNotFound ){ + return line; + } + + // if we enter this loop it means we it is a multiline comment + while ( indexStop == NSNotFound && (line = [reader readLine]) ) { + indexStop = [line rangeOfString:@"]"].location; + indexStart = 0; + } + + NSMutableString *mutableLine = [NSMutableString stringWithString:@""]; + if (indexStart > 0 ) { + [mutableLine appendString:[line substringToIndex:indexStart]]; + } + else if( indexStop < [line length]-1 ){ + [mutableLine appendString:[line substringFromIndex:indexStop+1]]; + } + + return mutableLine; +} + +-(MFSequenceSet *)readDataBlock:(MFReaderCluster*) reader{ + NSRegularExpression *ntaxRegex = [NSRegularExpression regularExpressionWithPattern:@"ntax\\s*=\\s*(\\d+)" options:NSRegularExpressionCaseInsensitive error:nil]; + NSRegularExpression *ncharRegex = [NSRegularExpression regularExpressionWithPattern:@"nchar\\s*=\\s*(\\d+)" options:NSRegularExpressionCaseInsensitive error:nil]; + NSRegularExpression *gapRegex = [NSRegularExpression regularExpressionWithPattern:@"gap\\s*=\\s*(\\w)" options:NSRegularExpressionCaseInsensitive error:nil]; + NSRegularExpression *missingRegex = [NSRegularExpression regularExpressionWithPattern:@"missing\\s*=\\s*(\\w)" options:NSRegularExpressionCaseInsensitive error:nil]; + NSRegularExpression *datatypeRegex = [NSRegularExpression regularExpressionWithPattern:@"datatype\\s*=\\s*(\\w+)" options:NSRegularExpressionCaseInsensitive error:nil]; + NSRegularExpression *matchcharRegex = [NSRegularExpression regularExpressionWithPattern:@"matchchar\\s*=\\s*(\\w)" options:NSRegularExpressionCaseInsensitive error:nil]; + + NSString *line; + + // read ntax and nchar and datatype + while ( (line = [self nextLineUncommented:reader]) ) { + + line = [line stringByTrimmingPaddingWhitespace]; + + if ([line isEmpty]) continue; + + if( [[line uppercaseString] hasPrefix:@"MATRIX"] ) { + return [self readMatrix:reader]; + } + + NSArray* matches = [ntaxRegex matchesInString:line options:0 range: NSMakeRange(0, [line length])]; + + if( [line rangeOfString:@"nolabels" options:NSCaseInsensitiveSearch].location != NSNotFound ){ + _nolabels = YES; + } + if( [matches count] == 1 ){ + NSString *ntaxString = [line substringWithRange:[[matches objectAtIndex:0] rangeAtIndex:1]]; + _numberOfSequences = [ntaxString integerValue]; + } + + matches = [ncharRegex matchesInString:line options:0 range: NSMakeRange(0, [line length])]; + if( [matches count] == 1 ){ + NSString *ncharString = [line substringWithRange:[[matches objectAtIndex:0] rangeAtIndex:1]]; + _numberOfSites = [ncharString integerValue]; + } + + if( [line rangeOfString:@"interleave" options:NSCaseInsensitiveSearch].location != NSNotFound ){ + NSRange r = [line rangeOfString:@"interleave" options:NSCaseInsensitiveSearch]; + NSString *temp = [line substringFromIndex:r.length+r.location]; + temp = [temp stringByTrimmingLeadingWhitespace]; + _interleaved = YES; + // some nexus files have the interleave keyword without specifying yes or no so we consider it interleaved + if( ![temp isEmpty] ){ + if( [temp characterAtIndex:0] == '=' ){ + NSUInteger i = 1; + while ( i < [temp length] ) { + if ( [temp characterAtIndex:i] == 'y' || [temp characterAtIndex:i] == 'Y' ) { + _interleaved = YES; + break; + } + else if ( [temp characterAtIndex:i] == 'n' || [temp characterAtIndex:i] == 'N' ) { + _interleaved = NO; + break; + } + } + } + } + } + + matches = [datatypeRegex matchesInString:line options:0 range: NSMakeRange(0, [line length])]; + if( [matches count] == 1 ){ + NSString *datatypeString = [[line substringWithRange:[[matches objectAtIndex:0] rangeAtIndex:1]] uppercaseString]; + if ( [@"NUCLEOTIDE" isEqualToString: datatypeString] || [@"DNA" isEqualToString: datatypeString] || [@"RNA" isEqualToString: datatypeString]) { + _dataType = [[MFNucleotide alloc]init]; + } + else if( [@"PROTEIN" isEqualToString: datatypeString] ){ + _dataType = [[MFProtein alloc]init]; + } + } + + matches = [gapRegex matchesInString:line options:0 range: NSMakeRange(0, [line length])]; + if( [matches count] == 1 ){ + _gap = [line substringWithRange:[[matches objectAtIndex:0] rangeAtIndex:1]]; + } + + matches = [missingRegex matchesInString:line options:0 range: NSMakeRange(0, [line length])]; + if( [matches count] == 1 ){ + _missing = [line substringWithRange:[[matches objectAtIndex:0] rangeAtIndex:1]]; + } + + matches = [matchcharRegex matchesInString:line options:0 range: NSMakeRange(0, [line length])]; + if( [matches count] == 1 ){ + _matchchar = [line substringWithRange:[[matches objectAtIndex:0] rangeAtIndex:1]]; + } + } + + return nil; +} + +-(void)readTaxaBlock:(MFReaderCluster *)reader{ + NSString *line; + + while ( (line = [self nextLineUncommented:reader]) ) { + + + line = [line stringByTrimmingPaddingWhitespace]; + + if( [line isEmpty] ) continue; + + if( [[line uppercaseString] hasPrefix:@"END;"] ){ + break; + } + + if([[line uppercaseString] hasPrefix:@"TAXLABELS"] ){ + NSMutableString *mutableString = [[NSMutableString alloc]init]; + NSRange r = [line rangeOfString:@"taxlabels" options:NSCaseInsensitiveSearch]; + [mutableString appendString:[line substringFromIndex:r.location+r.length]]; + + while( (line = [self nextLineUncommented:reader]) && ![line hasSuffix:@";"] ) { + [mutableString appendFormat:@" %@",line ]; + } + // in case the last taxon ends with the final ; + if ( ![line isEqualToString:@";"] ) { + [mutableString appendString:[line substringToIndex:[line length]-1]]; + } + + _taxa = [NSMutableArray array]; + + NSMutableCharacterSet *delimiters = [[NSCharacterSet whitespaceCharacterSet] mutableCopy]; + [delimiters invert]; + + r = NSMakeRange(0, [mutableString length]); + while ( r.location < [mutableString length]-1 ) { + r = [mutableString rangeOfCharacterFromSet:delimiters options:0 range:NSMakeRange(r.location, [mutableString length] - r.location)]; + + NSRange r2 = NSMakeRange(r.location+1, 1); + + if( [mutableString characterAtIndex:r.location] == ';' ){ + break; + } + else if( [mutableString characterAtIndex:r.location] == '\''){ + r = [mutableString rangeOfString:@"'" options:0 range:NSMakeRange(r2.location, [mutableString length] - r2.location)]; + } + else if( [mutableString characterAtIndex:r.location] == '"'){ + r = [mutableString rangeOfString:@"\"" options:0 range:NSMakeRange(r2.location, [mutableString length] - r2.location)]; + } + else { + r = [mutableString rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet] options:0 range:NSMakeRange(r2.location, [mutableString length] - r2.location-1)]; + } + + r2.length = r.location - r2.location; + [_taxa addObject:[mutableString substringWithRange:r2]]; + r.location = r2.location+r2.length+1; + } + [delimiters release]; + [mutableString release]; + } + else if( [[line uppercaseString] hasPrefix:@"DIMENSIONS"] && [line rangeOfString:@"ntax" options:NSCaseInsensitiveSearch].location != NSNotFound ){ + NSRange r = [line rangeOfString:@"ntax" options:NSCaseInsensitiveSearch]; + r.location += r.length; + r.length = [line length] - r.location; + line = [ line substringWithRange:r]; + r = [line rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet] options:0 range:NSMakeRange(0, [line length]) ]; + NSScanner *scanner = [NSScanner scannerWithString:[line substringFromIndex:r.location] ]; + [scanner scanInteger:&_numberOfSequences]; + } + } +} + +-(MFSequenceSet *)readMatrix:(MFReaderCluster*) reader{ + + MFSequenceSet *sequences = [[MFSequenceSet alloc]init]; + + NSUInteger index = 0; + NSString *line; + + while ( (line = [self nextLineUncommented:reader])) { + + line = [line stringByTrimmingPaddingWhitespace]; + + if( [line isEmpty] ){ + continue; + } + if( [line hasPrefix:@";"] ){ + break; + } + + NSString *taxon; + NSString *seqString; + + if( _nolabels ){ + taxon = [_taxa objectAtIndex:index]; + seqString = line; + } + else{ + if( [line hasPrefix:@"'"] ){ + + NSUInteger i = 1; + while ( [line characterAtIndex:i] != '\'') { + i++; + } + taxon = [line substringWithRange:NSMakeRange(1, i-1)]; + seqString = [line substringFromIndex:i+1]; + } + else if( [line hasPrefix:@"\""] ){ + NSUInteger i = 1; + while ( [line characterAtIndex:i] != '"') { + i++; + } + taxon = [line substringWithRange:NSMakeRange(1, i-1)]; + seqString = [line substringFromIndex:i+1]; + } + else{ + NSUInteger i = 0; + while ( ![[NSCharacterSet whitespaceCharacterSet] characterIsMember:[line characterAtIndex:i]] ) { + i++; + } + taxon = [line substringToIndex:i]; + seqString = [line substringFromIndex:i+1]; + } + } + + NSArray* temp = [[seqString uppercaseString] componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + seqString = [temp componentsJoinedByString:@""]; + + BOOL endOfMatrix = [seqString hasSuffix:@";"]; + + if( endOfMatrix ){ + seqString = [seqString substringToIndex:[seqString length]-1]; + } + if([sequences size] < _numberOfSequences ){ + MFSequence *sequence = [[ MFSequence alloc] initWithString:seqString name:taxon]; + [sequences addSequence:sequence]; + [sequence release]; + + } + else{ + if( index == _numberOfSequences ){ + index = 0; + } + MFSequence *sequence = [sequences sequenceAt:index]; + [sequence concatenateString:seqString]; + } + + index++; + + if(endOfMatrix){ + break; + } + } + return [sequences autorelease]; +} + +-(NSArray *)readTreesBlock:(MFReaderCluster*) reader{ + + NSMutableArray *trees = [[NSMutableArray alloc]init]; + NSMutableDictionary *translate = [[NSMutableDictionary alloc]init];; + + NSString *line; + + while ( (line = [reader readLine])) { + + line = [line stringByTrimmingPaddingWhitespace]; + + if( [line isEmpty] ){ + continue; + } + if( [[line uppercaseString] hasPrefix:@"TREE"] ){ + NSUInteger i = 4; + while ( [line characterAtIndex:i] != '(') { + if( [line characterAtIndex:i] == '['){ + while ( [line characterAtIndex:i] != ']') { + i++; + } + } + i++; + } + NSString *newick = [line substringFromIndex:i]; + MFTree *tree = [[MFTree alloc]initWithNewick:newick]; + [trees addObject:tree]; + [tree release]; + } + else if( [[line uppercaseString] hasPrefix:@"TRANSLATE"] ){ + BOOL done = NO; + while( (line = [self nextLineUncommented:reader]) && !done ) { + line = [line stringByTrimmingPaddingWhitespace]; + + if( [line isEqualToString:@";"] ) break; + if( [line rangeOfString:@";"].location != NSNotFound ){ + line = [line substringToIndex:[line rangeOfString:@";"].location]; + done = YES; + } + + NSArray *t = [line componentsSeparatedByString:@","]; + for ( NSUInteger i = 0; i < [t count]; i++ ) { + NSString *temp = [[t objectAtIndex:i] stringByTrimmingPaddingWhitespace]; + + if( [temp isEqualToString:@""] ) continue; + + NSInteger idx = [temp rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]].location; + NSString *shorthand = [temp substringToIndex:idx]; + NSString *taxonName = [temp substringFromIndex:idx+1]; + taxonName = [[taxonName stringByTrimmingPaddingWhitespace] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"'\""]]; + [translate setValue:taxonName forKey:shorthand]; + } + } + } + } + + if( [translate count] > 0 ){ + for ( NSUInteger i = 0; i < [trees count]; i++ ) { + MFTree *tree = [trees objectAtIndex:i]; + [tree enumerateNodesWithAlgorithm:MFTreeTraverseAlgorithmPostorder usingBlock:^(MFNode *node){ + if( [node isLeaf] ){ + if( [translate objectForKey:[node name]] ){ + node.name = [translate objectForKey:[node name]]; + } + } + }]; + } + } + [translate release]; + return [trees autorelease]; +} + +-(NSArray*)readTreesFromFile:(NSString*)path{ + + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithFile:path]; + + NSArray *trees = [self readTrees:reader]; + + [reader release]; + + return trees; +} + +-(NSArray*)readTreesFromURL:(NSURL*)url{ + NSString *content = [ NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil]; + NSArray *trees = [self readTreesFromString:content]; + return trees; +} + +-(NSArray*)readTreesFromData:(NSData*)data{ + + NSString *content = [[NSString alloc] initWithData: data encoding: NSASCIIStringEncoding]; + NSArray *trees = [self readTreesFromString:content]; + [content release]; + return trees; +} + +-(NSArray*)readTreesFromString:(NSString*)content{ + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithString:content]; + + NSArray *trees = [self readTrees:reader]; + + [reader release]; + + return trees; +} + +-(NSArray*)readTrees:(MFReaderCluster*)reader{ + NSRegularExpression *blockRegex = [NSRegularExpression regularExpressionWithPattern:@"^\\s*begin\\s+((?:trees|taxa));" options:NSRegularExpressionCaseInsensitive error:nil]; + + NSArray *trees = nil; + + NSString *line; + + while ( (line = [reader readLine]) ) { + + if ([line isEmpty]) continue; + + NSArray* block = [blockRegex matchesInString:line options:0 range: NSMakeRange(0, [line length])]; + + if( [block count] == 1 ){ + NSTextCheckingResult* match = [block objectAtIndex:0]; + NSString *name = [line substringWithRange:[match rangeAtIndex:1]]; + + if( [ [name uppercaseString] isEqualToString:@"TREES"] ){ + trees = [self readTreesBlock:reader]; + break; + } + } + } + + return trees; +} + + +@end diff --git a/Seqotron/MFNode.h b/Seqotron/MFNode.h new file mode 100755 index 0000000..2988235 --- /dev/null +++ b/Seqotron/MFNode.h @@ -0,0 +1,64 @@ +// +// MFNode.h +// Seqotron +// +// Created by Mathieu Fourment on 7/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + + +extern NSString *MFNodeDefaultInternalKey; + +@interface MFNode : NSObject{ + NSString *_name; + CGFloat _branchLength; + MFNode *_parent; + NSMutableArray *_child; + NSMutableDictionary *_attributes; +} + +@property (readwrite, copy) NSString *name; +@property (readwrite, retain) NSMutableDictionary *attributes; +@property (readwrite, assign) MFNode *parent; // assign to avoid circular references +@property CGFloat branchLength; + +-(id)initWithName:(NSString*)name; + +- (BOOL)isLeaf; + +- (BOOL)isRoot; + +-(NSUInteger)childCount; + +-(void)addChild:(MFNode*)child; + +- (void)removeChild:(MFNode*)child; + +-(void)removeChildAtIndex:(NSUInteger)index; + +-(MFNode*)childAtIndex:(NSUInteger)index; + +- (void)setAttribute:(id)object forKey:(NSString*)key; + +- (id)attributeForKey:(NSString*)key; + +- (void)rotate; + +@end diff --git a/Seqotron/MFNode.m b/Seqotron/MFNode.m new file mode 100755 index 0000000..564131b --- /dev/null +++ b/Seqotron/MFNode.m @@ -0,0 +1,114 @@ +// +// MFNode.m +// Seqotron +// +// Created by Mathieu Fourment on 7/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFNode.h" + +NSString *MFNodeDefaultInternalKey = @"tk.phylogenetics.MFNode.default.key"; + +@implementation MFNode + +@synthesize attributes = _attributes; +@synthesize parent = _parent; +@synthesize branchLength = _branchLength; +@synthesize name = _name; + +-(id)init{ + if( self = [super init]){ + _child = [[NSMutableArray alloc]init]; + _parent = nil; + _attributes = [[NSMutableDictionary alloc]init];; + _branchLength = 0; + _name = [[NSString alloc]initWithString:@""]; + } + return self; +} + +-(id)initWithName:(NSString*)name{ + if( self = [super init]){ + _child = [[NSMutableArray alloc]init]; + _parent = nil; + _attributes = [[NSMutableDictionary alloc]init]; + _branchLength = 0; + _name = [name copy]; + } + return self; +} + +-(void)dealloc{ + [_child release]; + [_attributes release]; + [_name release]; + _parent = nil; + [super dealloc]; +} + +-(NSString*)description{ + return [NSString stringWithFormat:@"MFNode %@", _name]; +} + +- (BOOL)isRoot{ + return _parent == nil; +} + +- (BOOL)isLeaf{ + return [_child count] == 0; +} + +-(NSUInteger)childCount{ + return [_child count]; +} + +-(void)addChild:(MFNode*)child{ + [_child addObject:child]; + child.parent = self; +} + +- (void)removeChild:(MFNode*)child{ + [_child removeObject:child]; +} + +-(void)removeChildAtIndex:(NSUInteger)index{ + [_child removeObjectAtIndex:index]; +} + +-(MFNode*)childAtIndex:(NSUInteger)index{ + return [_child objectAtIndex:index]; +} + +- (void)setAttribute:(id)object forKey:(NSString*)key{ + [_attributes setObject:object forKey:key]; +} + +- (id)attributeForKey:(NSString*)key{ + return [_attributes objectForKey:key]; +} + +- (void)rotate{ + if( [_child count] > 1 ){ + MFNode *last = [_child lastObject]; + [_child insertObject:last atIndex:0]; + [_child removeObjectAtIndex:[_child count]-1]; + } +} + +@end diff --git a/Seqotron/MFNucleotide.h b/Seqotron/MFNucleotide.h new file mode 100755 index 0000000..034145c --- /dev/null +++ b/Seqotron/MFNucleotide.h @@ -0,0 +1,31 @@ +// +// MFNucleotide.h +// Seqotron +// +// Created by Mathieu Fourment on 8/02/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFDataType.h" + +@interface MFNucleotide : MFDataType + +//+ (id)sharedInstance; +@end diff --git a/Seqotron/MFNucleotide.m b/Seqotron/MFNucleotide.m new file mode 100755 index 0000000..164ed45 --- /dev/null +++ b/Seqotron/MFNucleotide.m @@ -0,0 +1,104 @@ +// +// MFNucleotide.m +// Seqotron +// +// Created by Mathieu Fourment on 8/02/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFNucleotide.h" + +static bool const NUCLEOTIDE_STATES[128] = { + false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false, // 15 + false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false, // 31 + false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false, // 47 + false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false, // 63 + // A C G + false, true,false,true,false,false,false,true,false,false,false,false,false, false,false,false, // 79 + // T U + false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false, // 95 + // a c g + false, true,false,true,false,false,false,true,false,false,false,false,false, false,false,false, // 111 + // t u + false,false,false,false,true,true,false,false,false,false,false,false,false,false,false,false, // 1127 +}; + +@implementation MFNucleotide + +- (NSUInteger)stateCount{ + return 4; +} + +-(NSString*)description{ + return @"Nucleotide"; +} + +-(BOOL)isAmbiguous:(NSString *)character{ + return [character rangeOfCharacterFromSet: [NSCharacterSet characterSetWithCharactersInString: @"KMRSWYBDHVN"]].location != NSNotFound; +} + +-(BOOL)isKnown:(NSString *)character{ + return [character rangeOfCharacterFromSet: [NSCharacterSet characterSetWithCharactersInString: @"ACTGU"]].location != NSNotFound; +} + +- (BOOL)isKnownChar:(char)character{ + return NUCLEOTIDE_STATES[character]; +} + +-(BOOL)isValid:(NSString *)character{ + return [self isKnown:character] || [self isAmbiguous:character] || [self isGap:character] || [self isUnknown:character]; +} + ++ (id)sharedInstance{ + static dispatch_once_t pred = 0; + __strong static id _sharedObject = nil; + dispatch_once(&pred, ^{ + _sharedObject = [[self alloc] init]; // or some other init method + }); + return _sharedObject; +} + ++ (void) logCharacterSet:(NSCharacterSet*)characterSet { + unichar unicharBuffer[20]; + int index = 0; + + for (unichar uc = 0; uc < (0xFFFF); uc ++) + { + if ([characterSet characterIsMember:uc]) + { + unicharBuffer[index] = uc; + + index ++; + + if (index == 20) + { + NSString * characters = [NSString stringWithCharacters:unicharBuffer length:index]; + NSLog(@"%@", characters); + + index = 0; + } + } + } + + if (index != 0) + { + NSString * characters = [NSString stringWithCharacters:unicharBuffer length:index]; + NSLog(@"%@", characters); + } +} +@end diff --git a/Seqotron/MFOperation.h b/Seqotron/MFOperation.h new file mode 100644 index 0000000..75fc630 --- /dev/null +++ b/Seqotron/MFOperation.h @@ -0,0 +1,49 @@ +// +// MFOperation.h +// Seqotron +// +// Created by Mathieu Fourment on 10/03/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFOperationDelegate.h" + +extern NSString *MFOperationDescriptionKey; +extern NSString *MFOperationOutputKey; +extern NSString *MFOperationDocumentClassKey; + +@interface MFOperation : NSOperation{ + NSString *_classType; + NSURL *_outputURL; +} + +- (id)initWithOptions:(NSDictionary*)options; + +- (id)initWithOutputURL:(NSURL*)url classDocument:(NSString*)type; + +@property (nonatomic, readwrite, retain)NSURL *outputURL; +@property (retain,readonly)NSString *classType; +@property (copy,readwrite)NSString *description; + +@property (retain,readonly)NSDictionary *options; + +@property (atomic,assign) id delegate; + +@end diff --git a/Seqotron/MFOperation.m b/Seqotron/MFOperation.m new file mode 100644 index 0000000..eda24e0 --- /dev/null +++ b/Seqotron/MFOperation.m @@ -0,0 +1,87 @@ +// +// MFOperation.m +// Seqotron +// +// Created by Mathieu Fourment on 10/03/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFOperation.h" + +@implementation MFOperation + + +NSString *MFOperationDescriptionKey = @"tk.phylogenetics.operation.description"; +NSString *MFOperationOutputKey = @"tk.phylogenetics.operation.output"; +NSString *MFOperationDocumentClassKey = @"tk.phylogenetics.operation.document.class"; + +@synthesize outputURL = _outputURL; +@synthesize classType = _classType; +@synthesize description,options; +@synthesize delegate; + +- (id)init{ + if( self = [super init]){ + options = nil; + description = nil; + _outputURL = nil; + _classType = nil; + } + return self; +} + +-(id)initWithOutputURL:(NSURL*)url classDocument:(NSString*)type{ + if( self = [super init]){ + _outputURL = [url retain]; + _classType = [type copy]; + description = [@""copy]; + delegate = nil; + } + return self; +} + +- (id)initWithOptions:(NSDictionary *)someOptions{ + if( self = [super init]){ + options = [someOptions retain]; + description = nil; + _outputURL = nil; + _classType = nil; + + if( [options objectForKey:MFOperationDescriptionKey]){ + description = [[options objectForKey:MFOperationDescriptionKey]copy]; + } + if( [options objectForKey:MFOperationOutputKey]){ + _outputURL = [[options objectForKey:MFOperationOutputKey]copy]; + } + if( [options objectForKey:MFOperationDocumentClassKey]){ + _classType = [[options objectForKey:MFOperationDocumentClassKey]copy]; + } + } + return self; +} + + +-(void)dealloc{ + [options release]; + [_classType release]; + [_outputURL release]; + [description release]; + delegate = nil; + [super dealloc]; +} +@end diff --git a/Seqotron/MFOperationBuilder.h b/Seqotron/MFOperationBuilder.h new file mode 100644 index 0000000..4f02177 --- /dev/null +++ b/Seqotron/MFOperationBuilder.h @@ -0,0 +1,32 @@ +// +// MFOperationBuilder.h +// Seqotron +// +// Created by Mathieu Fourment on 8/03/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFExternalOperation.h" + +@protocol MFOperationBuilder + +-(NSArray*)operations; + +@end diff --git a/Seqotron/MFOperationDelegate.h b/Seqotron/MFOperationDelegate.h new file mode 100644 index 0000000..afaf535 --- /dev/null +++ b/Seqotron/MFOperationDelegate.h @@ -0,0 +1,41 @@ +// +// MFExternalProgress.h +// Seqotron +// +// Created by Mathieu Fourment on 17/01/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +@class MFOperation; + +@protocol MFOperationDelegate + +@optional + +-(void)operation:(MFOperation*)operation setProgress:(double)value; + +-(void)operation:(MFOperation*)operation setMinValue:(double)value; + +-(void)operation:(MFOperation*)operation setMaxValue:(double)value; + +-(void)operation:(MFOperation*)operation setDescription:(NSString*)desc; + +-(void)operation:(MFOperation*)operation setDescription2:(NSString*)desc; +@end diff --git a/Seqotron/MFOperationTransalign.h b/Seqotron/MFOperationTransalign.h new file mode 100644 index 0000000..ee1cad5 --- /dev/null +++ b/Seqotron/MFOperationTransalign.h @@ -0,0 +1,37 @@ +// +// MFOperationTransalign.h +// Seqotron +// +// Created by Mathieu Fourment on 9/03/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFOperation.h" + +@interface MFOperationTransalign : MFOperation{ + NSString *_inputNucleotide; + NSString *_inputAminoAcid; +} + +@property NSUInteger position; + +-(id)initWithNucleotideFile:(NSString*)inputNucleotide AminoacidFile:(NSString*)inputAminoAcid outputULR:(NSURL*)output; + +@end diff --git a/Seqotron/MFOperationTransalign.m b/Seqotron/MFOperationTransalign.m new file mode 100644 index 0000000..674ac0d --- /dev/null +++ b/Seqotron/MFOperationTransalign.m @@ -0,0 +1,97 @@ +// +// MFOperationTransalign.m +// Seqotron +// +// Created by Mathieu Fourment on 9/03/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFOperationTransalign.h" + +#import "MFSequence.h" +#import "MFSequenceSet.h" +#import "MFSequenceReader.h" +#import "MFSequenceWriter.h" + +@implementation MFOperationTransalign + +@synthesize position; + +-(id)initWithNucleotideFile:(NSString*)inputNucleotide AminoacidFile:(NSString*)inputAminoAcid outputULR:(NSURL*)output{ + //if( self = [super initWithOutputURL:output classDocument:@"MFDocument"] ){ + if( self = [super initWithOptions:[NSDictionary dictionaryWithObjectsAndKeys:output, MFOperationOutputKey,@"MFDocument", MFOperationDocumentClassKey, nil]] ){ + _inputNucleotide = [inputNucleotide retain]; + _inputAminoAcid = [inputAminoAcid retain]; + position = 0; + } + return self; +} + +-(void)dealloc{ + [_inputNucleotide release]; + [_inputAminoAcid release]; + [super dealloc]; +} + +-(void)main{ + NSLog(@"Operation transalign running"); +// dispatch_sync(dispatch_get_main_queue(), ^{ +// [self.delegate operation:self setDescription:self.description]; +// }); + + MFSequenceSet *sequenceSetNuc = [MFSequenceReader readSequencesFromFile:_inputNucleotide]; + MFSequenceSet *sequenceSetAA = [MFSequenceReader readSequencesFromFile:_inputAminoAcid]; + + NSUInteger offset = (self.position == 0 ? 0 : 3); + + for ( NSUInteger i = 0; i < [sequenceSetNuc count]; i++) { + if (self.isCancelled) break; + + MFSequence *nuc = [sequenceSetNuc sequenceAt:i]; + NSUInteger nGap = 0; + if ( self.position ) { + nGap++; + if( [[nuc subSequenceWithRange:NSMakeRange(0, 2)] isEqualToString:@"--"]){ + nGap++; + if( [[nuc subSequenceWithRange:NSMakeRange(0, 3)] isEqualToString:@"---"]){ + nGap++; + } + } + } + [nuc removeAllGaps]; + if(self.position) [nuc insertGaps:nGap AtIndex:0]; + NSEnumerator *enumerator = [[sequenceSetAA sequences] objectEnumerator]; + MFSequence *aa; + while ( aa = [enumerator nextObject]) { + if( [[aa name] isEqualToString:[nuc name] ]) break; + } + for ( NSUInteger j = 0; j < [aa length]; j++ ) { + if( [aa residueAt:j] == '-' ){ + [nuc insertGaps:3 AtIndex:j*3+offset]; + } + } + } + + [MFSequenceWriter writeFasta:sequenceSetNuc toFile:_outputURL.path attributes:nil]; + + NSLog(@"Operation transalign finished"); +} + + + +@end diff --git a/Seqotron/MFPhylipImporter.h b/Seqotron/MFPhylipImporter.h new file mode 100755 index 0000000..1839070 --- /dev/null +++ b/Seqotron/MFPhylipImporter.h @@ -0,0 +1,36 @@ +// +// MFPhylipImporter.h +// Seqotron +// +// Created by Mathieu Fourment on 7/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFSequenceImporter.h" +#import "MFReaderCluster.h" + +@interface MFPhylipImporter : NSObject + + - (MFSequenceSet *)readInterleavedSequences:(MFReaderCluster *)reader; + + - (MFSequenceSet *)readSequentialSequences:(MFReaderCluster *)reader; + + +@end diff --git a/Seqotron/MFPhylipImporter.m b/Seqotron/MFPhylipImporter.m new file mode 100755 index 0000000..76a8ec7 --- /dev/null +++ b/Seqotron/MFPhylipImporter.m @@ -0,0 +1,227 @@ +// +// MFPhylipImporter.m +// Seqotron +// +// Created by Mathieu Fourment on 7/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFPhylipImporter.h" + +#import "MFSequence.h" +#import "MFString.h" + +@implementation MFPhylipImporter + +- (MFSequenceSet *)readSequencesFromFile:(NSString *)path{ + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithFile:path]; + + MFSequenceSet *sequences = [self readSequences:reader]; + + [reader release]; + + return sequences; +} + +-(MFSequenceSet*)readSequencesFromURL:(NSURL*)url{ + NSString *content = [ NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil]; + MFSequenceSet *sequences = [self readSequencesFromString:content]; + + return sequences; +} + +-(MFSequenceSet*)readSequencesFromData:(NSData*)data{ + NSString *content = [[NSString alloc] initWithData: data encoding: NSASCIIStringEncoding]; + + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithString:content]; + + MFSequenceSet *sequences = [self readSequences:reader]; + + [reader release]; + [content release]; + + return sequences; +} + +-(MFSequenceSet*)readSequencesFromString:(NSString*)content{ + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithString:content]; + + MFSequenceSet *sequences = [self readSequences:reader]; + + [reader release]; + + return sequences; +} + +//TODO: only read interleaved +- (MFSequenceSet *)readSequences:(MFReaderCluster *)reader{ + NSString *line = [reader readLine]; + + NSInteger numberOfSequences = 0; + NSInteger numberOfSites = 0; + NSString *type; + NSScanner *scanner = [NSScanner scannerWithString:line]; + [scanner scanInteger:&numberOfSequences]; + [scanner scanInteger:&numberOfSites]; + + [reader rewind]; + + if( [scanner scanCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"iIsS"] intoString:&type] ){ + if( [[type uppercaseString] isEqualToString:@"I"]){ + return [self readInterleavedSequences:reader]; + } + else if( [[type uppercaseString] isEqualToString:@"S"]){ + return [self readSequentialSequences:reader]; + } + else { + return nil; + } + } + + return [self readInterleavedSequences:reader]; +} + +- (MFSequenceSet *)readInterleavedSequences:(MFReaderCluster *)reader{ + NSString *line = [reader readLine]; + + NSInteger numberOfSequences = 0; + NSInteger numberOfSites = 0; + NSScanner *scanner = [NSScanner scannerWithString:line]; + [scanner scanInteger:&numberOfSequences]; + [scanner scanInteger:&numberOfSites]; + + MFSequenceSet *sequences = nil; + NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:numberOfSequences]; + + // read lines containing name + sequence + while ( (line = [reader readLine]) ) { + if (![line isEmpty]){ + NSMutableString *mutableString = [line mutableCopy]; + [array addObject:mutableString]; + [mutableString release]; + } + + if( [array count] == numberOfSequences ) break; + } + + // read rest of sequences and concatenate (if any) + NSUInteger index = 0; + while ( (line = [reader readLine]) ) { + if ( ![line isEmpty] ){ + if( index == numberOfSequences){ + index = 0; + } + [[array objectAtIndex:index] appendString:line]; + index++; + } + } + + // find the boundary between name and sequence using the length of the sequence + if( [array count] == numberOfSequences ){ + sequences = [[MFSequenceSet alloc] initWithCapacity:numberOfSequences]; + for ( NSMutableString *line in array) { + NSUInteger len = 0; + NSInteger i = [line length]-1; + while ( len != numberOfSites && i != 0 ) { + if ( ![[NSCharacterSet whitespaceCharacterSet] characterIsMember:[line characterAtIndex:i]]) { + len++; + } + i--; + } + + NSArray* temp = [[[line substringFromIndex:i+1] uppercaseString] componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + NSString *seq = [temp componentsJoinedByString:@""]; + + if( [seq length] != numberOfSites ){ + [array release]; + [sequences release]; + return nil; + } + + NSString *name = [[line substringToIndex:i+1] stringByTrimmingPaddingWhitespace]; + MFSequence *sequence = [[ MFSequence alloc] initWithString:seq name:name]; + [sequences addSequence:sequence]; + [sequence release]; + } + } + [array release]; + + if( [sequences count] != numberOfSequences ){ + [sequences release]; + return nil; + } + + return [sequences autorelease]; +} + +- (MFSequenceSet *)readSequentialSequences:(MFReaderCluster *)reader{ + + NSString *line = [reader readLine]; + + NSInteger numberOfSequences = 0; + NSInteger numberOfSites = 0; + NSScanner *scanner = [NSScanner scannerWithString:line]; + [scanner scanInteger:&numberOfSequences]; + [scanner scanInteger:&numberOfSites]; + + MFSequenceSet *sequences = [[MFSequenceSet alloc] initWithCapacity:numberOfSequences];; + + NSMutableString *mutableString = [NSMutableString string]; + + while ( (line = [reader readLine]) ) { + if (![line isEmpty]){ + [mutableString appendString:line]; + + if( [mutableString length] > numberOfSites ){ + NSUInteger len = 0; + NSInteger i = [mutableString length]-1; + + while ( len != numberOfSites && i != 0 ) { + if ( ![[NSCharacterSet whitespaceCharacterSet] characterIsMember:[mutableString characterAtIndex:i]]) { + len++; + } + i--; + } + + if( i != 0 ){ + NSArray* temp = [[[mutableString substringFromIndex:i+1] uppercaseString] componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + NSString *seq = [temp componentsJoinedByString:@""]; + NSString *name = [[mutableString substringToIndex:i+1] stringByTrimmingPaddingWhitespace]; + + if( [seq length] != numberOfSites ){ + [sequences release]; + return nil; + } + MFSequence *sequence = [[ MFSequence alloc] initWithString:seq name:name]; + [sequences addSequence:sequence]; + [sequence release]; + [mutableString setString:@""]; + } + } + + } + } + + if( [sequences count] != numberOfSequences ){ + [sequences release]; + return nil; + } + return [sequences autorelease]; +} + +@end diff --git a/Seqotron/MFPrefs.xib b/Seqotron/MFPrefs.xib new file mode 100644 index 0000000..3c0c33a --- /dev/null +++ b/Seqotron/MFPrefs.xib @@ -0,0 +1,266 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Seqotron/MFPrefsWindowController.h b/Seqotron/MFPrefsWindowController.h new file mode 100644 index 0000000..9a7ba8f --- /dev/null +++ b/Seqotron/MFPrefsWindowController.h @@ -0,0 +1,54 @@ +// +// MFPrefsWindowController.h +// Seqotron +// +// Created by Mathieu Fourment on 3/03/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import +#import "DBPrefsWindowController.h" +#import "MFSimpleAlignmentView.h" + +@interface MFPrefsWindowController : DBPrefsWindowController { + IBOutlet NSView *_colorView; + IBOutlet NSMatrix *_matrix; + IBOutlet MFSimpleAlignmentView *_alignmentView; + IBOutlet NSSegmentedControl *_segmentedDataType; + IBOutlet NSSegmentedControl *_segmented; + IBOutlet NSTableView *_tableView; + IBOutlet NSArrayController *_schemeController; + + NSMutableArray *_schemes; + NSMutableDictionary *_colors; + NSUInteger _firstUserIndex; + + BOOL _editingForeground; + BOOL _newScheme; + NSString *_newSchemeFilename; + NSUInteger _selectedDatatypeSegment; + + NSString *_currentScheme; + + NSMutableArray *_sequences; +} + +@property (retain, readwrite) NSMutableArray *schemes; +@property (retain, readwrite) NSMutableArray *sequences; + +@end diff --git a/Seqotron/MFPrefsWindowController.m b/Seqotron/MFPrefsWindowController.m new file mode 100644 index 0000000..0bda22b --- /dev/null +++ b/Seqotron/MFPrefsWindowController.m @@ -0,0 +1,630 @@ +// +// MFPrefsWindowController.m +// Seqotron +// +// Created by Mathieu Fourment on 3/03/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFPrefsWindowController.h" + +#import "MFColorManager.h" +#import "MFString.h" +#import "MFSequence.h" + +@interface MFPrefsWindowController () + +@end + +@implementation MFPrefsWindowController + +@synthesize schemes = _schemes; +@synthesize sequences = _sequences; + +unsigned long row_index( unsigned long i, unsigned long M ){ + double m = M; + double row = (-2*m - 1 + sqrt( (4*m*(m+1) - 8*(double)i - 7) )) / -2; + if( row == (double)(long) row ) row -= 1; + return (unsigned long) row; +} + + +unsigned long column_index( unsigned long i, unsigned long M ){ + unsigned long row = row_index( i, M); + return i - M * row + row*(row+1) / 2; +} + +- (id)init { + if (self = [super initWithWindowNibName:@"MFPrefs"]) { + _schemes = [[NSMutableArray alloc]init]; + _colors = [[NSMutableDictionary alloc]init]; + _selectedDatatypeSegment = 0; + [_segmentedDataType setSelectedSegment:_selectedDatatypeSegment]; + _editingForeground = NO; + _newScheme = NO; + _newSchemeFilename = nil; + _currentScheme = nil; + _sequences = [[NSMutableArray alloc]init]; + } + return self; +} + +-(void)dealloc{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [_alignmentView unbind:@"sequences"]; + [_schemes release]; + [_colors release]; + [_sequences release]; + [_newSchemeFilename release]; + [_currentScheme release]; + [super dealloc]; +} + +- (void)windowDidLoad { + [super windowDidLoad]; + NSLog(@"MFPrefsWindowController windowDidLoad"); + + NSString *type = [_segmentedDataType labelForSegment:[_segmentedDataType selectedSegment]]; + [self setUpSchemes:type]; + [self setUpColoring:type]; + [self setUpMatrix]; + + //NSRect frame = [_alignmentView frame]; + //NSUInteger count = frame.size.height/_alignmentView.residueHeight; + MFSequence *seq; + seq = [[MFSequence alloc]initWithString:@"ATGACTGACTGTCTGCTTAAGCGAAU-GACGATCGTTATCCTGAATTCATGACTGACTGTCTGCTTAAGCGAACAGACGATTAA" name:@"1"]; [_sequences addObject:seq]; [seq release]; + seq = [[MFSequence alloc]initWithString:@"ATGA-TCACTGCGCTAGCTGTCATCU-AAGCTTCGTTATCCTGAATTCATGA-TCACTGCGCTAGCTGTCATCGAAAGCTTTAA" name:@"2"]; [_sequences addObject:seq]; [seq release]; + seq = [[MFSequence alloc]initWithString:@"ATGACTAAGTGCGTAGCTAGCCAGCU-AACGTTCGTTATCCTGAATTCATGACTAAGTGCGTAGCTAGCCAGCTAAACGTTTAA" name:@"3"]; [_sequences addObject:seq]; [seq release]; + seq = [[MFSequence alloc]initWithString:@"ATGA?TAAGTGGCTAGCAGCTCGCTU-CAGATTCGTTATCCTGAATTCATGA?TAAGTGGCTAGCAGCTCGCCAACAGATTTAA" name:@"4"]; [_sequences addObject:seq]; [seq release]; + seq = [[MFSequence alloc]initWithString:@"ATGACTAACTCACACACGTGCCGTCU-GATGCTCGTTATCCTGAATTCATGACTAACTCACACACGTGCCGTCGAGATGCTTAA" name:@"5"]; [_sequences addObject:seq]; [seq release]; + seq = [[MFSequence alloc]initWithString:@"ATGACTGACTGGTCGTGCTAGCTGAU-GAGCATCGTTATCCTGAATTCATGACTGACTGGTCGTGCTAGCTGATAGAGCATTAA" name:@"6"]; [_sequences addObject:seq]; [seq release]; + + NSDictionary *geneticCode = [self geneticTable]; + for (MFSequence *sequence in _sequences) { + [sequence setGeneticTable:geneticCode]; + } + [_alignmentView bind:@"sequences" toObject:self withKeyPath:@"sequences" options:nil]; + [self setUpAlignment]; + + + _currentScheme = [[[[_schemeController selectedObjects] objectAtIndex:0] objectForKey:@"desc"]copy]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tableViewEditingDidEnd:) + name:NSControlTextDidEndEditingNotification object:nil]; +} + +-(NSDictionary*)geneticTable{ + NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"GeneticCodeTables"]; + NSArray *dirFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil]; + NSDictionary *geneticTable = nil; + + for ( NSString *file in dirFiles) { + if ( [file hasSuffix:@"plist"] ) { + NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:[path stringByAppendingPathComponent:file]]; + if( [[dict objectForKey:@"Description"] isEqualToString:@"Standard"]){ + geneticTable = [[NSDictionary alloc]initWithDictionary:[dict objectForKey:@"Table"]]; + [dict release]; + break; + } + [dict release]; + } + } + return [geneticTable autorelease]; +} + +- (void)setupToolbar{ + [self addView:_colorView label:@"Coloring" image:[NSImage imageNamed:NSImageNameColorPanel]]; +} + +#pragma mark *** Action Methods *** + +// NSMatrix action +-(IBAction)click:(id)sender{ + + // If we try to edit a scheme from the bundle then create a copy of it + if ( [_schemeController selectionIndex] < _firstUserIndex ) { + NSString *type = [_segmentedDataType labelForSegment:[_segmentedDataType selectedSegment]]; + NSString *scheme = [[[_schemeController selectedObjects] objectAtIndex:0] objectForKey:@"desc"]; + NSString *path = [[MFColorManager applicationColorDirectory] stringByAppendingPathComponent:type]; + NSString *newScheme = [self duplicateScheme:scheme fromDirectory:path]; + + [self setUpSchemes:type]; + + NSArray *schemesDesc = [self schemesDescription]; + NSUInteger newIndex = [schemesDesc indexOfObject:newScheme]; + [_tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:newIndex] byExtendingSelection:NO]; + } + + NSButtonCell *cell = [sender selectedCell]; + NSColorPanel *cp = [NSColorPanel sharedColorPanel]; + + // colorUpdate would be called wehn assigning the color to cp.color + if( [cp isVisible] ){ + [cp setAction:nil]; + } + + NSAttributedString *attTitle = [cell attributedTitle]; + NSRange effectiveRange = NSMakeRange(0, 0); + if (([[NSApp currentEvent] modifierFlags] & NSCommandKeyMask) != 0 ){ + cp.color = [attTitle attribute:NSBackgroundColorAttributeName atIndex:0 effectiveRange:&effectiveRange]; + _editingForeground = NO; + } + else{ + _editingForeground = YES; + cp.color = [attTitle attribute:NSForegroundColorAttributeName atIndex:0 effectiveRange:&effectiveRange]; + } + + [cp setDelegate:self]; + [cp orderFront:nil]; + [cp setTarget:self]; + [cp setAction:@selector(colorUpdate:)]; + +} + +-(IBAction)datatypeSelector:(id)sender{ + if( _selectedDatatypeSegment != [_segmentedDataType selectedSegment] ){ + NSString *type = [_segmentedDataType labelForSegment:[_segmentedDataType selectedSegment]]; + for (MFSequence *seq in _sequences) { + [seq setTranslated:!seq.translated]; + } + [self setUpSchemes:type]; + [self setUpColoring:type]; + [self setUpMatrix]; + [self setUpAlignment]; + + _selectedDatatypeSegment = [_segmentedDataType selectedSegment]; + } +} + + +-(IBAction)createOrDeleteAction:(id)sender{ + if( [_tableView selectedRow] == -1 )return; + + NSSegmentedControl *control = sender; + NSString *type = [_segmentedDataType labelForSegment:[_segmentedDataType selectedSegment]]; + + // Duplicate + if( [control selectedSegment] == 0 ){ + + NSString *path; + NSString *scheme =[[[_schemeController selectedObjects] objectAtIndex:0] objectForKey:@"desc"]; + + if( [_schemeController selectionIndex] < _firstUserIndex){ + path = [MFColorManager applicationColorDirectory]; + } + else { + path = [MFColorManager userColorDirectory]; + } + + [path stringByAppendingPathComponent:type]; + + + NSString *newScheme = [self duplicateScheme:scheme fromDirectory:path]; + + [self setUpSchemes:type]; + + NSArray *schemesDesc = [self schemesDescription]; + NSUInteger newIndex = [schemesDesc indexOfObject:newScheme]; + [_tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:newIndex] byExtendingSelection:NO]; + + NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:type, @"datatype",@"add",@"type",newScheme,@"file", nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"MFColoringDidChange" object:self userInfo:info]; + } + // Remove + else { + NSAlert *alert = [[[NSAlert alloc] init]autorelease]; + NSString *scheme = [[[_schemeController selectedObjects] objectAtIndex:0] objectForKey:@"desc"]; + [alert setInformativeText:[NSString stringWithFormat:@"Do you want to delete %@ color scheme", scheme]]; + [alert setMessageText:@"Delete scheme"]; + [alert addButtonWithTitle:@"Cancel"]; + [alert addButtonWithTitle:@"Delete"]; + + + [alert beginSheetModalForWindow:[NSApp mainWindow] completionHandler:^(NSInteger result) { + + // Cancel + if (result == NSAlertFirstButtonReturn) { + + } + // Delete + else if (result == NSAlertSecondButtonReturn) { + NSString *path = [[MFColorManager userColorDirectory] stringByAppendingPathComponent:type]; + + [self removeScheme: scheme fromDirectory:path]; + + [self setUpSchemes:type]; + [self setUpColoring:type]; + [self setUpMatrix]; + [self setUpAlignment]; + + NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:type,@"datatype", @"delete",@"type", scheme,@"file", nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"MFColoringDidChange" object:self userInfo:info]; + } + }]; + + } +} + +#pragma mark *** Methods *** + +-(void)setUpAlignment{ + [_alignmentView setForegroundColor:[_colors objectForKey:NSForegroundColorAttributeName]]; + if ( [_colors objectForKey:NSBackgroundColorAttributeName] ) { + [_alignmentView setBackgroundColor:[_colors objectForKey:NSBackgroundColorAttributeName]]; + } + else{ + [_alignmentView setBackgroundColor:[NSMutableDictionary dictionary]]; + } + [_alignmentView setNeedsDisplay:YES]; +} + +-(void)setUpMatrix{ + NSUInteger i = 0; + NSUInteger row = 0; + NSUInteger col = 0; + + NSDictionary *foregroundColor = [_colors objectForKey:NSForegroundColorAttributeName]; + NSDictionary *backgroundColor = nil; + //NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:[NSFont fontWithName:@"Courier" size:12], NSFontAttributeName, nil]; + if( [_colors objectForKey:NSBackgroundColorAttributeName] ){ + backgroundColor = [_colors objectForKey:NSBackgroundColorAttributeName]; + } + + for ( NSString *residue in foregroundColor ) { + row = row_index(i, [_matrix numberOfColumns]*[_matrix numberOfRows]); + col = column_index(i, [_matrix numberOfColumns]*[_matrix numberOfRows]); + + NSButtonCell *cell = [_matrix cellAtRow:row column:col]; + + // Foreground + NSColor *color = [foregroundColor objectForKey:residue]; + //NSString *r = [NSString stringWithFormat:@" %@ ",residue]; + //NSMutableAttributedString *colorTitle = [[NSMutableAttributedString alloc] initWithString:r attributes:attrs]; + NSMutableAttributedString *colorTitle = [[NSMutableAttributedString alloc] initWithString:residue]; + + NSRange titleRange = NSMakeRange(0, [colorTitle length]); + + [colorTitle addAttribute:NSForegroundColorAttributeName value:color range:titleRange]; + + + // Background + if ( backgroundColor != nil && [backgroundColor objectForKey:residue] ) { + color = [backgroundColor objectForKey:residue]; + } + else { + color = [NSColor whiteColor]; + } + [colorTitle addAttribute:NSBackgroundColorAttributeName value:color range:titleRange]; + //[cell setBackgroundColor:color]; + [cell setAttributedTitle:colorTitle]; + [colorTitle release]; + + [cell setTransparent:NO]; + [cell setEnabled:YES]; + + i++; + } + + for ( ; i < [_matrix numberOfColumns]*[_matrix numberOfRows]; i++ ) { + row = row_index(i, [_matrix numberOfColumns]*[_matrix numberOfRows]); + col = column_index(i, [_matrix numberOfColumns]*[_matrix numberOfRows]); + + NSButtonCell *cell = [_matrix cellAtRow:row column:col]; + [cell setTransparent:YES]; + [cell setEnabled:NO]; + } +} + +-(void)setUpSchemes:(NSString*)type{ + + _firstUserIndex = 0; + + [self willChangeValueForKey:@"schemes"]; + [self.schemes removeAllObjects]; + + NSString *path = [[MFColorManager applicationColorDirectory]stringByAppendingPathComponent:type]; + NSArray *scs = [MFColorManager colorSchemesWithInfoAtPath:path]; + [self.schemes addObjectsFromArray:scs]; + + if( scs != nil && [scs count] > 0 ){ + _firstUserIndex = [scs count]; + } + + path = [[MFColorManager userColorDirectory]stringByAppendingPathComponent:type]; + scs = [MFColorManager colorSchemesWithInfoAtPath:path]; + [self.schemes addObjectsFromArray:scs]; + + [self didChangeValueForKey:@"schemes"]; + +} + +-(void)setUpColoring:(NSString*)type{ + [_colors removeAllObjects]; + + NSString *scheme = [[[_schemeController selectedObjects] objectAtIndex:0] objectForKey:@"desc"]; + + NSString *path = [[MFColorManager applicationColorDirectory]stringByAppendingPathComponent:type]; + + if( _firstUserIndex > 0 ){ + NSDictionary *dict = [MFColorManager coloring:scheme fromPath:path]; + [_colors addEntriesFromDictionary:dict]; + } + + path = [[MFColorManager userColorDirectory]stringByAppendingPathComponent:type]; + NSDictionary *dict = [MFColorManager coloring:scheme fromPath:path]; + if( dict != nil){ + [_colors addEntriesFromDictionary:dict]; + } +} + +-(void)removeScheme:(NSString*)scheme fromDirectory:(NSString*)dirPath{ + + NSError *error = nil; + NSArray *dirFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dirPath error:&error]; + if( !error ){ + for ( NSString *file in dirFiles) { + if ( [file hasSuffix:@"plist"] ) { + NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:[dirPath stringByAppendingPathComponent:file]]; + + if( [[dict objectForKey:@"Description"] isEqualToString:scheme] ){ + error = nil; + + [[NSFileManager defaultManager] removeItemAtPath:[dirPath stringByAppendingPathComponent:file] error:&error]; + if ( !error ) { + [self willChangeValueForKey:@"schemes"]; + [self.schemes removeObject: scheme]; + [self didChangeValueForKey:@"schemes"]; + } + [dict release]; + return; + } + + [dict release]; + } + } + } + +} + +- (NSString*)duplicateScheme:(NSString*)scheme fromDirectory:(NSString*)dir{ + NSError *error = nil; + NSString *dirPath = [MFColorManager userColorDirectory]; + + NSUInteger counter = 0; + NSArray *schemesDesc = [self schemesDescription]; + while([schemesDesc indexOfObject:[scheme stringByAppendingFormat:@" %lu", counter] ] != NSNotFound ){ + counter++; + } + NSString *newScheme = nil; + + [[NSFileManager defaultManager]createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:&error]; + + if(!error){ + newScheme = [scheme stringByAppendingFormat:@" %lu", counter]; + NSString *path = [MFColorManager userColorDirectory]; + NSString *type = [_segmentedDataType labelForSegment:[_segmentedDataType selectedSegment]]; + path = [path stringByAppendingPathComponent:type]; + + NSString *saveFileName = [NSString stringRandomWithLength:10]; + NSString *saveFileFullPath = [path stringByAppendingPathComponent:saveFileName]; + while ( [[NSFileManager defaultManager]fileExistsAtPath:saveFileFullPath] ) { + saveFileName = [NSString stringRandomWithLength:10]; + saveFileFullPath = [path stringByAppendingPathComponent:saveFileName]; + } + [self saveCurrentScheme: newScheme toDirectory:path fileName:saveFileName]; + + _newSchemeFilename = [saveFileName copy]; + } + return newScheme; +} + +-(void)saveCurrentScheme:(NSString*)schemeDesc toDirectory:(NSString*)dirPath fileName:(NSString*)fileName{ + NSError *error = nil; + [[NSFileManager defaultManager]createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:&error]; + + if( !error ){ + NSString *file = [NSString stringWithFormat:@"%@.plist",fileName]; + NSString *plistPath = [dirPath stringByAppendingPathComponent:file]; + + NSMutableDictionary *plistDict = [[NSMutableDictionary alloc]init]; + [plistDict setObject:schemeDesc forKey:@"Description"]; + + NSMutableDictionary *fg = [[NSMutableDictionary alloc]init]; + NSDictionary *colors = [_colors objectForKey:NSForegroundColorAttributeName]; + for (NSString *residue in colors) { + NSColor *col = [colors objectForKey:residue]; + // https://developer.apple.com/library/mac/documentation/cocoa/Conceptual/DrawColor/Tasks/UsingColorSpaces.html#//apple_ref/doc/uid/TP40001807-97360-BCIHDDFF + NSColor *rgbColor = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; + CGFloat red; + CGFloat green; + CGFloat blue; + CGFloat alpha; + [rgbColor getRed:&red green:&green blue:&blue alpha:&alpha]; + + [fg setObject:[NSArray arrayWithObjects:[NSNumber numberWithFloat:red], [NSNumber numberWithFloat:green], [NSNumber numberWithFloat:blue], [NSNumber numberWithFloat:alpha], nil] forKey:residue]; + } + [plistDict setObject:fg forKey:@"Foreground"]; + [fg release]; + + if( [_colors objectForKey:NSBackgroundColorAttributeName] != nil ){ + NSDictionary *colors = [_colors objectForKey:NSBackgroundColorAttributeName]; + NSMutableDictionary *bg = [[NSMutableDictionary alloc]init]; + for (NSString *residue in colors) { + NSColor *col = [colors objectForKey:residue]; + NSColor *rgbColor = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; + CGFloat red; + CGFloat green; + CGFloat blue; + CGFloat alpha; + [rgbColor getRed:&red green:&green blue:&blue alpha:&alpha]; + + [bg setObject:[NSArray arrayWithObjects:[NSNumber numberWithFloat:red], [NSNumber numberWithFloat:green], [NSNumber numberWithFloat:blue], [NSNumber numberWithFloat:alpha], nil] forKey:residue]; + } + [plistDict setObject:bg forKey:@"Background"]; + [bg release]; + } + + NSError *err = nil; + NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:plistDict format:NSPropertyListXMLFormat_v1_0 options:0 error:&err]; + + [plistData writeToFile:plistPath atomically:YES]; + [plistDict release]; + } +} + +-(void)colorUpdate:(NSColorPanel*)colorPanel{ + NSColor* theColor = colorPanel.color; + NSButtonCell * cell = [_matrix selectedCell]; + NSMutableAttributedString *colorTitle = [[NSMutableAttributedString alloc] initWithAttributedString:[cell attributedTitle]]; + NSString *title = [cell title]; + + if ( !_editingForeground ){ + + if( [_colors objectForKey:NSBackgroundColorAttributeName] == nil ){ + NSMutableDictionary *bg = [[NSMutableDictionary alloc]init]; + + NSUInteger i = 0; + NSUInteger row = 0; + NSUInteger col = 0; + + for ( NSString *residue in [_colors objectForKey:NSForegroundColorAttributeName] ) { + + row = row_index(i, [_matrix numberOfColumns]*[_matrix numberOfRows]); + col = column_index(i, [_matrix numberOfColumns]*[_matrix numberOfRows]); + + cell = [_matrix cellAtRow:row column:col]; + + NSColor *color; + if( [residue isEqualToString:title] ){ + color = theColor; + } + else { + color = [NSColor whiteColor]; + } + [[colorTitle mutableString]setString:residue]; + [colorTitle addAttribute:NSBackgroundColorAttributeName value:color range:NSMakeRange(0, [colorTitle length])]; + [colorTitle addAttribute:NSForegroundColorAttributeName value:[[_colors objectForKey:NSForegroundColorAttributeName]objectForKey:residue] range:NSMakeRange(0, [colorTitle length])]; + [cell setAttributedTitle:colorTitle]; + + [bg setObject:color forKey:residue]; + + i++; + } + [_colors setObject:bg forKey:NSBackgroundColorAttributeName]; + [bg release]; + } + else { + [colorTitle addAttribute:NSBackgroundColorAttributeName value:theColor range:NSMakeRange(0, [colorTitle length])]; + [cell setAttributedTitle:colorTitle]; + + [[_colors objectForKey:NSBackgroundColorAttributeName]setObject:theColor forKey:[cell title]]; + } + } + else{ + [colorTitle addAttribute:NSForegroundColorAttributeName value:theColor range:NSMakeRange(0, [colorTitle length])]; + [cell setAttributedTitle:colorTitle]; + + [[_colors objectForKey:NSForegroundColorAttributeName]setObject:theColor forKey:[cell title]]; + } + [self setUpAlignment]; + [colorTitle release]; + _newScheme = YES; + +} + +-(NSArray*)schemesDescription{ + NSMutableArray *schemesDesc = [[NSMutableArray alloc]init]; + for (NSDictionary *dict in self.schemes) { + [schemesDesc addObject:[dict objectForKey:@"desc"]]; + } + return [schemesDesc autorelease]; +} + +#pragma mark *** NSWindowDelegate Delegate Methods *** + +- (void)windowWillClose:(NSNotification *)notification { + + [[NSColorPanel sharedColorPanel] setAction:nil]; + + if ([notification.object isEqual:[NSColorPanel sharedColorPanel]]) { + if(_newScheme){ + NSString *scheme = [[[_schemeController selectedObjects] objectAtIndex:0] objectForKey:@"desc"]; + NSString *type = [_segmentedDataType labelForSegment:[_segmentedDataType selectedSegment]]; + NSString *path = [[MFColorManager userColorDirectory] stringByAppendingPathComponent:type]; + [self saveCurrentScheme:scheme toDirectory:path fileName:_newSchemeFilename]; + _newScheme = NO; + NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:type,@"datatype", @"modify",@"type", scheme,@"file", nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"MFColoringDidChange" object:self userInfo:info]; + } + } + else { + [[NSColorPanel sharedColorPanel] close]; + } +} + +#pragma mark *** NSTableView Delegate Methods *** + +- (void)tableViewSelectionDidChange:(NSNotification *)notification{ + NSInteger index = [_tableView selectedRow]; + + if( index >= 0){ + NSString *type = [_segmentedDataType labelForSegment:[_segmentedDataType selectedSegment]]; + [self setUpSchemes:type]; + [self setUpColoring:type]; + [self setUpMatrix]; + [self setUpAlignment]; + + if ( index < _firstUserIndex ) { + [_segmented setEnabled:NO forSegment:1]; + } + else { + [_segmented setEnabled:YES forSegment:1]; + } + NSString *scheme = [[[_schemeController selectedObjects] objectAtIndex:0] objectForKey:@"desc"]; + [_currentScheme release]; + _currentScheme = [scheme copy]; + } + +} + +// Does not allow row selection to be modified if the color panel is visible +- (BOOL)selectionShouldChangeInTableView:(NSTableView *)aTableView{ + return ![[NSColorPanel sharedColorPanel]isVisible]; +} + + +- (BOOL)tableView:(NSTableView *)aTableView shouldEditTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex{ + return (rowIndex >= _firstUserIndex); +} + + +- (void)tableViewEditingDidEnd:(NSNotification *)notification { + NSString *type = [_segmentedDataType labelForSegment:[_segmentedDataType selectedSegment]]; + NSString *path = [[MFColorManager userColorDirectory] stringByAppendingPathComponent:type]; + NSDictionary *scheme = [[_schemeController selectedObjects] objectAtIndex:0]; + [self saveCurrentScheme:[scheme objectForKey:@"desc"] toDirectory:path fileName:[scheme objectForKey:@"file"]]; + + NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:type,@"datatype", @"rename",@"type", [scheme objectForKey:@"desc"],@"file", nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"MFColoringDidChange" object:self userInfo:info]; +} + +- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor{ + return [[fieldEditor string]length] != 0; +} + +@end diff --git a/Seqotron/MFProgressController.h b/Seqotron/MFProgressController.h new file mode 100644 index 0000000..cc01020 --- /dev/null +++ b/Seqotron/MFProgressController.h @@ -0,0 +1,43 @@ +// +// MFProgressController.h +// Seqotron +// +// Created by Mathieu Fourment on 17/01/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFOperationDelegate.h" +#import "MFOperation.h" + +@interface MFProgressController : NSWindowController { + + NSArray *_operations; +} + +@property (nonatomic, retain) IBOutlet NSProgressIndicator *progressIndicator; + +@property (nonatomic, copy) NSString *primaryDescription; +@property (nonatomic, copy) NSString *secondaryDescription; + +-(id)initWithOperation:(MFOperation*)op; + +-(id)initWithOperations:(NSArray*)operations; + +@end diff --git a/Seqotron/MFProgressController.m b/Seqotron/MFProgressController.m new file mode 100644 index 0000000..d063d68 --- /dev/null +++ b/Seqotron/MFProgressController.m @@ -0,0 +1,81 @@ +// +// MFProgressController.m +// Seqotron +// +// Created by Mathieu Fourment on 17/01/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFProgressController.h" + + +@implementation MFProgressController + +@synthesize progressIndicator ,primaryDescription, secondaryDescription; + +-(id)initWithOperation:(NSOperation*)op{ + if(self = [super initWithWindowNibName:@"MFProgressWindow"]){ + _operations = [[NSArray alloc]initWithObjects:op, nil]; + primaryDescription = [[NSString alloc]init]; + secondaryDescription = [[NSString alloc]init]; + } + return self; +} + +-(id)initWithOperations:(NSArray*)operations{ + if(self = [super initWithWindowNibName:@"MFProgressWindow"]){ + _operations = [operations retain]; + primaryDescription = [[NSString alloc]init]; + secondaryDescription = [[NSString alloc]init]; + } + return self; +} + +-(void)dealloc{ + [_operations release]; + [progressIndicator release]; + [primaryDescription release]; + [secondaryDescription release]; + [super dealloc]; +} + +- (void)windowDidLoad { + [super windowDidLoad]; + MFOperation *op = [_operations objectAtIndex:0]; + if( op.description ){ + self.window.title = op.description; + } +} + +- (IBAction)cancel:(id)sender{ + NSLog(@"Cancel operation"); + for ( NSOperation *op in _operations) { + [op cancel]; + } + [self close]; +} + +-(void)operation:(MFOperation*)operation setDescription:(NSString*)desc{ + self.primaryDescription = desc; +} + +-(void)operation:(MFOperation*)operation setDescription2:(NSString*)desc{ + self.secondaryDescription = desc; +} + +@end diff --git a/Seqotron/MFProgressWindow.xib b/Seqotron/MFProgressWindow.xib new file mode 100644 index 0000000..540ef70 --- /dev/null +++ b/Seqotron/MFProgressWindow.xib @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Seqotron/MFProtein.h b/Seqotron/MFProtein.h new file mode 100755 index 0000000..4b7bd7d --- /dev/null +++ b/Seqotron/MFProtein.h @@ -0,0 +1,29 @@ +// +// MFProtein.h +// Seqotron +// +// Created by Mathieu Fourment on 8/02/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFDataType.h" + +@interface MFProtein : MFDataType + + +@end diff --git a/Seqotron/MFProtein.m b/Seqotron/MFProtein.m new file mode 100755 index 0000000..ce2857b --- /dev/null +++ b/Seqotron/MFProtein.m @@ -0,0 +1,76 @@ +// +// MFProtein.m +// Seqotron +// +// Created by Mathieu Fourment on 8/02/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFProtein.h" + +bool const AMINOACID_STATES[128] = { + false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false, // 0-15 + false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false, // 16-31 + false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false, // 32-47 + false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false, // 48-63 + // A B C D E F G H I K L M N + false,true,false,true,true,true,true,true,true,true,false,true,true,true,true,false, // 64-79 + // P Q R S T V W X Y + true,true,true,true,true,false,true,true,false,true,false,false,false,false,false,false, // 80-95 + // a c d e f g h i k l m n + false,true,false,true,true,true,true,true,true,true,false,true,true,true,true,false, // 96-111 + // p q r s t v w y + true,true,true,true,true,false,true,true,false,true,false,false,false,false,false,false // 112-127 +}; + +@implementation MFProtein + +- (NSUInteger)stateCount{ + return 20; +} + +-(NSString*)description{ + return @"Protein"; +} + +-(BOOL)isAmbiguous:(NSString *)character{ + return NO; +} + +-(BOOL)isKnown:(NSString *)character{ + return [character rangeOfCharacterFromSet: [NSCharacterSet characterSetWithCharactersInString: @"ACDEFGHIKLMNPQRSTVWY"]].location != NSNotFound; +} + +- (BOOL)isKnownChar:(char)character{ + return AMINOACID_STATES[character]; +} + +-(BOOL)isValid:(NSString *)character{ + return [self isKnown:character] || [self isGap:character] || [self isUnknown:character]; +} + ++ (id)sharedInstance{ + static dispatch_once_t pred = 0; + __strong static id _sharedObject = nil; + dispatch_once(&pred, ^{ + _sharedObject = [[self alloc] init]; // or some other init method + }); + return _sharedObject; +} + +@end diff --git a/Seqotron/MFReaderCluster.h b/Seqotron/MFReaderCluster.h new file mode 100755 index 0000000..fe566d3 --- /dev/null +++ b/Seqotron/MFReaderCluster.h @@ -0,0 +1,42 @@ +// +// MFReaderCluster.h +// Seqotron +// +// Created by Mathieu Fourment on 7/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +@interface MFReaderCluster : NSObject{ + +} + +-(id)initWithData:(NSData*)data; + +-(id)initWithURL:(NSURL*)url; + +-(id)initWithFile:(NSString*)filepath; + +-(id)initWithString:(NSString*)content; + +-(NSString*)readLine; + +-(void)rewind; + +@end diff --git a/Seqotron/MFReaderCluster.m b/Seqotron/MFReaderCluster.m new file mode 100755 index 0000000..4cff09c --- /dev/null +++ b/Seqotron/MFReaderCluster.m @@ -0,0 +1,65 @@ +// +// MFReaderCluster.m +// Seqotron +// +// Created by Mathieu Fourment on 7/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFReaderCluster.h" + +#import "MFFileReader.h" +#import "MFStringReader.h" + +@implementation MFReaderCluster + +-(id)initWithData:(NSData*)data{ + [self release]; + + return self; +} + +-(id)initWithURL:(NSURL*)url{ + [self release]; + + return self; +} + +-(id)initWithFile:(NSString*)filepath{ + [self release]; + + return [[MFFileReader alloc] initWithFile:filepath]; +} + +-(id)initWithString:(NSString*)content{ + [self release]; + + return [[MFStringReader alloc]initWithString:content]; +} + +-(void)dealloc{ + [super dealloc]; +} + +-(NSString*)readLine{ + return @""; +} + +-(void)rewind{ + +} + +@end diff --git a/Seqotron/MFRulerView.h b/Seqotron/MFRulerView.h new file mode 100755 index 0000000..850e581 --- /dev/null +++ b/Seqotron/MFRulerView.h @@ -0,0 +1,44 @@ +// +// MFRulerView.h +// Seqotron +// +// Created by Mathieu Fourment on 30/01/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + + +#import "MFAbstractSequencesView.h" + +@interface MFRulerView : NSView{ + + NSRect _visibleRect; + NSScrollView *_synchronizedScrollView; + + CGFloat _residueWidth; + CGFloat _colSpacing; + NSString *_fontName; + CGFloat _fontSize; +} + +- (void)setSynchronizedScrollView:(NSScrollView*)scrollview; +- (void)synchronizedViewContentBoundsDidChange:(NSNotification *)notification; +- (void)stopSynchronizing; + +@end diff --git a/Seqotron/MFRulerView.m b/Seqotron/MFRulerView.m new file mode 100755 index 0000000..b912ba6 --- /dev/null +++ b/Seqotron/MFRulerView.m @@ -0,0 +1,253 @@ +// +// MFRulerView.m +// Seqotron +// +// Created by Mathieu Fourment on 30/01/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFRulerView.h" + + +@implementation MFRulerView + +- (id)initWithFrame:(NSRect)frame +{ + NSLog(@"MFRuler init"); + self = [super initWithFrame:frame]; + if (self) { + _synchronizedScrollView = nil; + _colSpacing = [[[NSUserDefaults standardUserDefaults] objectForKey:@"MFDefaultSequenceColumnSpacing"]floatValue]; + _fontSize = [[[NSUserDefaults standardUserDefaults] objectForKey:@"MFDefaultSequenceFontSize"]floatValue]; + _fontName = [[[NSUserDefaults standardUserDefaults] objectForKey:@"MFDefaultSequenceFontName"]copy]; + [self initResidueWidthWithFontSize:_fontSize name:_fontName]; + } + return self; +} + +-(void) dealloc{ + NSLog(@"MFRulerView dealloc"); + [_fontName release]; + [super dealloc]; +} + +- (void)drawRect:(NSRect)dirtyRect{ + + [[NSColor whiteColor] set]; + NSRectFill(dirtyRect); + + // We always draw the whole ruler + _visibleRect.size.width = [self bounds].size.width; + + NSUInteger nCol = ceil(_visibleRect.size.width/_residueWidth); + NSUInteger sitePos = _visibleRect.origin.x/_residueWidth; + + NSPoint startMajorPoint = NSMakePoint(sitePos*_residueWidth-_visibleRect.origin.x + (_residueWidth/2), 0.0); + NSPoint endMajorPoint = NSMakePoint(sitePos*_residueWidth-_visibleRect.origin.x + (_residueWidth/2), 6); + + NSPoint startMinorPoint = NSMakePoint(sitePos*_residueWidth-_visibleRect.origin.x + (_residueWidth/2), 0.0); + NSPoint endMinorPoint = NSMakePoint(sitePos*_residueWidth-_visibleRect.origin.x + (_residueWidth/2), 3); + + startMajorPoint.x -= ((sitePos%5)+1)*_residueWidth; + endMajorPoint.x -= ((sitePos%5)+1)*_residueWidth; + + NSBezierPath* aPath = [NSBezierPath bezierPath]; + NSUInteger majorLabel = sitePos - sitePos%5; + + for ( int j = 0; j <= nCol+2; j++ ) { + if( j%5 == 0 ){ + + if(majorLabel == 0){ + NSPoint p = startMajorPoint; + p.x += _residueWidth; + [aPath moveToPoint:p]; + p = endMajorPoint; + p.x += _residueWidth; + [aPath lineToPoint:p]; + [aPath stroke]; + + NSString *index = [NSString stringWithFormat:@"%d", 1]; + [index drawAtPoint:NSMakePoint(startMajorPoint.x+_residueWidth, 6) withAttributes:nil]; + } + else { + [aPath moveToPoint:startMajorPoint]; + [aPath lineToPoint:endMajorPoint]; + [aPath stroke]; + + NSString *index = [NSString stringWithFormat:@"%lu", majorLabel]; + [index drawAtPoint:NSMakePoint(startMajorPoint.x, 6) withAttributes:nil]; + } + majorLabel += 5; + } + + [aPath moveToPoint:startMinorPoint]; + [aPath lineToPoint:endMinorPoint]; + [aPath stroke]; + + startMajorPoint.x += _residueWidth; + endMajorPoint.x += _residueWidth; + + startMinorPoint.x += _residueWidth; + endMinorPoint.x += _residueWidth; + + } +} + +- (void)drawRect2:(NSRect)dirtyRect{ + + [[NSColor whiteColor] set]; + NSRectFill(dirtyRect); + + // Draw the ruler when we load since the scrollview has not moved yet and no notifications + // have been received yet + if( NSIsEmptyRect(_visibleRect) ){ + _visibleRect.size.width = [self bounds].size.width; + } + + + + NSUInteger nCol = ceil(_visibleRect.size.width/(_residueWidth+_colSpacing)); // number of visible residues + NSUInteger sitePos = _visibleRect.origin.x/(_residueWidth+_colSpacing); // first visible residue + + NSPoint startMajorPoint = NSMakePoint(sitePos*(_residueWidth+_colSpacing)-_visibleRect.origin.x + (_residueWidth/2), 0.0); + NSPoint endMajorPoint = NSMakePoint(startMajorPoint.x, 6); + + NSPoint startMinorPoint = NSMakePoint(sitePos*(_residueWidth+_colSpacing)-_visibleRect.origin.x + (_residueWidth/2), 0.0); + NSPoint endMinorPoint = NSMakePoint(startMinorPoint.x, 3); + + startMajorPoint.x -= ((sitePos%5)+1)*(_residueWidth+_colSpacing); + endMajorPoint.x -= ((sitePos%5)+1)*(_residueWidth+_colSpacing); + + NSBezierPath* aPath = [NSBezierPath bezierPath]; + NSUInteger majorLabel = sitePos - sitePos%5; + + for ( NSUInteger j = 0; j <= nCol+2; j++ ) { + if( j%5 == 0 ){ + + if(majorLabel == 0){ + NSPoint p = startMajorPoint; + p.x += (_residueWidth+_colSpacing); + [aPath moveToPoint:p]; + p.y = 3; + [aPath lineToPoint:p]; + [aPath stroke]; + + NSString *index = [NSString stringWithFormat:@"%d", 1]; + [index drawAtPoint:NSMakePoint(startMajorPoint.x+_residueWidth, 6) withAttributes:nil]; + } + else { + [aPath moveToPoint:startMajorPoint]; + [aPath lineToPoint:endMajorPoint]; + [aPath stroke]; + + NSString *index = [NSString stringWithFormat:@"%lu", majorLabel]; + [index drawAtPoint:NSMakePoint(startMajorPoint.x, 6) withAttributes:nil]; + } + majorLabel += 5; + } + NSBezierPath* aPath = [NSBezierPath bezierPath]; + [aPath moveToPoint:startMinorPoint]; + [aPath lineToPoint:endMinorPoint]; + [aPath stroke]; + + startMajorPoint.x += (_residueWidth+_colSpacing); + endMajorPoint.x += (_residueWidth+_colSpacing); + + startMinorPoint.x += (_residueWidth+_colSpacing); + endMinorPoint.x += (_residueWidth+_colSpacing); + + } +} + +- (void)setSynchronizedScrollView:(NSScrollView*)scrollview{ + NSView *synchronizedContentView; + + // stop an existing scroll view synchronizing + [self stopSynchronizing]; + + // don't retain the watched view, because we assume that it will + // be retained by the view hierarchy for as long as we're around. + _synchronizedScrollView = scrollview; + + // get the content view of the + synchronizedContentView = [_synchronizedScrollView contentView]; + + // Make sure the watched view is sending bounds changed + // notifications (which is probably does anyway, but calling + // this again won't hurt). + [synchronizedContentView setPostsBoundsChangedNotifications:YES]; + + // a register for those notifications on the synchronized content view. + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(synchronizedViewContentBoundsDidChange:) + name:NSViewBoundsDidChangeNotification + object:synchronizedContentView]; +} + +- (void)synchronizedViewContentBoundsDidChange:(NSNotification *)notification{ + // get the changed content view from the notification + NSClipView *changedContentView = [notification object]; + + if( !NSEqualRects(_visibleRect, [changedContentView documentVisibleRect]) ){ + _visibleRect = [changedContentView documentVisibleRect]; + [self setNeedsDisplay:YES]; + } +} + +- (void)stopSynchronizing{ + if (_synchronizedScrollView != nil) { + NSView* synchronizedContentView = [_synchronizedScrollView contentView]; + + // remove any existing notification registration + [[NSNotificationCenter defaultCenter] removeObserver:self + name:NSViewBoundsDidChangeNotification + object:synchronizedContentView]; + + // set synchronizedScrollView to nil + _synchronizedScrollView=nil; + } +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(NSObject *)observedObject change:(NSDictionary *)change context:(void *)context { + if ( [keyPath isEqualToString:@"residueWidth"] ) { + //_fontSize = [[change objectForKey:NSKeyValueChangeNewKey]floatValue]; + //[self initSize]; + _residueWidth = [[change objectForKey:NSKeyValueChangeNewKey]floatValue]; + [self setNeedsDisplay:YES]; + } + else { + [super observeValueForKeyPath:keyPath ofObject:observedObject change:change context:context]; + } +} + +-(void)initResidueWidthWithFontSize:(CGFloat)fontSize name:(NSString*)fontName{ + NSMutableDictionary *attsDict = [[NSMutableDictionary alloc] init]; + NSFont *font = [NSFont fontWithName:fontName size:fontSize]; + [attsDict setObject:font forKey:NSFontAttributeName]; + + NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:@"A" attributes:attsDict]; + _residueWidth = [string size].width; + [string release]; + [attsDict release]; +} + +- (BOOL)isFlipped{ + return NO; +} + +@end diff --git a/Seqotron/MFScaleBar.h b/Seqotron/MFScaleBar.h new file mode 100644 index 0000000..c9cc1de --- /dev/null +++ b/Seqotron/MFScaleBar.h @@ -0,0 +1,39 @@ +// +// MFScaleBar.h +// Seqotron +// +// Created by Mathieu Fourment on 17/02/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFGraphic.h" + +#import "MFTextGraphic.h" +#import "MFLineGraphic.h" + +@interface MFScaleBar : MFGraphic{ + MFLineGraphic *_bar; + MFTextGraphic *_legend; + + CGFloat _scaleBarSpace; // space between text and bar in pixels +} + +@property (retain)MFLineGraphic *bar; +@property (retain)MFTextGraphic *legend; + +@end diff --git a/Seqotron/MFScaleBar.m b/Seqotron/MFScaleBar.m new file mode 100644 index 0000000..8056940 --- /dev/null +++ b/Seqotron/MFScaleBar.m @@ -0,0 +1,39 @@ +// +// MFScaleBar.m +// Seqotron +// +// Created by Mathieu Fourment on 17/02/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFScaleBar.h" + + +@implementation MFScaleBar + +@synthesize bar = _bar; +@synthesize legend = _legend; + + +- (NSRect)drawingBounds { + NSRect bounds = NSUnionRect([_bar drawingBounds], [_legend drawingBounds]); + bounds.size.height += _scaleBarSpace; + return bounds; +} + +@end diff --git a/Seqotron/MFSearchController.h b/Seqotron/MFSearchController.h new file mode 100644 index 0000000..40e58cd --- /dev/null +++ b/Seqotron/MFSearchController.h @@ -0,0 +1,21 @@ +// +// MFSearchController.h +// Seqotron +// +// Created by Mathieu on 14/05/2015. +// Copyright (c) 2015 University of Sydney. All rights reserved. +// + +#import + +@interface MFSearchController : NSWindowController{ + +} + + +@property (readwrite) NSInteger selectionMode; +@property (readwrite,getter=isCaseSensitive) BOOL caseSensitive; +@property (readwrite) BOOL regex; +@property (readwrite) BOOL wrapAround; + +@end diff --git a/Seqotron/MFSearchController.m b/Seqotron/MFSearchController.m new file mode 100644 index 0000000..fbce5a9 --- /dev/null +++ b/Seqotron/MFSearchController.m @@ -0,0 +1,63 @@ +// +// MFSearchController.m +// Seqotron +// +// Created by Mathieu on 14/05/2015. +// Copyright (c) 2015 University of Sydney. All rights reserved. +// + +#import "MFSearchController.h" + +@interface MFSearchController () + +@end + +@implementation MFSearchController + +@synthesize regex,wrapAround,caseSensitive,selectionMode; + +- (id)init{ + if(self = [super initWithWindowNibName:@"MFSearchController"]){ + self.selectionMode = 0; // Name + self.regex = NO; + self.caseSensitive = NO; + self.wrapAround = NO; + } + return self; +} + +- (void)dealloc{ + [super dealloc]; +} + +- (void)windowDidLoad { + [super windowDidLoad]; + + // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file. +} + +- (IBAction)findNext:(id)sender{ + +} + +- (IBAction)findPrevious:(id)sender{ + +} + +- (IBAction)findAll:(id)sender{ + +} + +- (IBAction)replace:(id)sender{ + +} + +- (IBAction)replaceAll:(id)sender{ + +} + +- (IBAction)findAndReplace:(id)sender{ + +} + +@end diff --git a/Seqotron/MFSearchController.xib b/Seqotron/MFSearchController.xib new file mode 100644 index 0000000..3817df6 --- /dev/null +++ b/Seqotron/MFSearchController.xib @@ -0,0 +1,211 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Seqotron/MFSequence+MFSequenceDrawing.h b/Seqotron/MFSequence+MFSequenceDrawing.h new file mode 100755 index 0000000..1c80e77 --- /dev/null +++ b/Seqotron/MFSequence+MFSequenceDrawing.h @@ -0,0 +1,35 @@ +// +// MFSequence+MFSequenceDrawing.h +// Seqotron +// +// Created by Mathieu Fourment on 6/08/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFSequence.h" + + +extern NSString *MFDrawableSequenceIsTranslated; +extern NSString *MFSpacingAttributeName; + +@interface MFSequence (MFSequenceDrawing) + + +- (void) drawAtPoint: (NSPoint)point withRange:(NSRange)range withAttributes: (NSDictionary*)attrs; + +@end diff --git a/Seqotron/MFSequence+MFSequenceDrawing.m b/Seqotron/MFSequence+MFSequenceDrawing.m new file mode 100755 index 0000000..c5c21b4 --- /dev/null +++ b/Seqotron/MFSequence+MFSequenceDrawing.m @@ -0,0 +1,120 @@ +// +// MFSequence+MFSequenceDrawing.m +// Seqotron +// +// Created by Mathieu Fourment on 6/08/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFSequence+MFSequenceDrawing.h" + + +NSString *MFDrawableSequenceIsTranslated = @"MFDrawableSequenceIsTranslated"; +NSString *MFSpacingAttributeName = @"MFSpacingAttributeName"; + +@implementation MFSequence (MFSequenceDrawing) + + +- (void) drawAtPoint: (NSPoint)point withRange:(NSRange)range withAttributes: (NSDictionary*)attrs{ + NSString *sequence = [self subSequenceWithRange:range]; + + NSFont *font = [attrs objectForKey: NSFontAttributeName]; + NSDictionary *foregroundDict = [attrs objectForKey: NSForegroundColorAttributeName]; + + NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] initWithString:sequence]; + [mutableAttributedString addAttribute:NSFontAttributeName + value:font + range:NSMakeRange(0, [sequence length])]; + +// [mutableAttributedString addAttribute:NSKernAttributeName +// value:[NSNumber numberWithFloat:1] +// range:NSMakeRange(0, [sequence length])]; + + if(foregroundDict != nil && [foregroundDict count] != 0 ){ + + for ( int j = 0; j < [sequence length]; j++ ) { + + NSString *residue = [sequence substringWithRange:NSMakeRange(j, 1)]; + NSColor *foreground; + + if( [foregroundDict objectForKey:[residue uppercaseString]] != nil ){ + foreground = [foregroundDict objectForKey: [residue uppercaseString] ]; + } + else if( [foregroundDict objectForKey:@"?"] != nil ){ + foreground = [foregroundDict objectForKey: @"?" ]; + } + else{ + foreground = [NSColor blackColor]; + } + + [mutableAttributedString addAttribute:NSForegroundColorAttributeName value:foreground range:NSMakeRange(j, 1)]; + } + } + + [mutableAttributedString drawAtPoint:point]; + + [mutableAttributedString release]; + +} + +// draw one residue at a time +- (void) drawAtPoint2: (NSPoint)point withRange:(NSRange)range withAttributes: (NSDictionary*)attrs{ + NSString *sequence = [self subSequenceWithRange:range]; + + NSFont *font = [attrs objectForKey: NSFontAttributeName]; + NSDictionary *foregroundDict = [attrs objectForKey: NSForegroundColorAttributeName]; + + NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] initWithString:@"A"]; + [mutableAttributedString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, 1)]; + CGFloat resWidth = [mutableAttributedString size].width; + + CGFloat spacing = 4; + if( [attrs objectForKey: MFSpacingAttributeName] ){ + spacing = [[attrs objectForKey: MFSpacingAttributeName]floatValue]; + } + + + NSPoint p = point; + + for ( int j = 0; j < [sequence length]; j++ ) { + + NSString *residue = [sequence substringWithRange:NSMakeRange(j, 1)]; + [[mutableAttributedString mutableString]setString:residue]; + NSColor *foreground; + + if( [foregroundDict objectForKey:[residue uppercaseString]] != nil ){ + foreground = [foregroundDict objectForKey: [residue uppercaseString] ]; + } + else if( [foregroundDict objectForKey:@"?"] != nil ){ + foreground = [foregroundDict objectForKey: @"?" ]; + } + else{ + foreground = [NSColor blackColor]; + } + + [mutableAttributedString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, 1)]; + [mutableAttributedString addAttribute:NSForegroundColorAttributeName value:foreground range:NSMakeRange(0, 1)]; + [mutableAttributedString drawAtPoint:p]; + p.x += resWidth + spacing; + } + + [mutableAttributedString release]; + +} + +@end diff --git a/Seqotron/MFSequence.h b/Seqotron/MFSequence.h new file mode 100755 index 0000000..506e507 --- /dev/null +++ b/Seqotron/MFSequence.h @@ -0,0 +1,102 @@ +// +// MFSequence.h +// Seqotron +// +// Created by Mathieu Fourment on 25/01/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFDataType.h" + +@interface MFSequence : NSObject { + NSMutableString *_sequence; + NSString *_name; + MFDataType *_dataType; + + NSDictionary *_genetictable; + BOOL _translated; +} + +@property (readonly) NSMutableString *sequence; +@property (copy) NSString *name; +@property (retain) MFDataType *dataType; +@property BOOL translated; + +- (id)initWithString:(NSString *)aSequence name:(NSString *)aName dataType:(MFDataType*)aDataType; + +- (id)initWithString:(NSString *)aSequence name:(NSString *)aName; + +- (id)initWithName:(NSString *)aName; + +- (NSString*)sequenceString; + +- (NSUInteger)length; + +- (NSString *)subCodonSequenceWithRange:(NSRange)range; + +- (NSString *)subSequenceWithRange:(NSRange)range; + +- (void)insertResidues:(NSString*)residues AtIndex:(NSUInteger)index; + +- (void)insertGaps:(NSUInteger)ngaps AtIndex:(NSUInteger)index; + +- (void)appendGaps:(NSUInteger)ngaps; + +- (void)trimEndGaps; + +-(void)removeAllGaps; + +- (unichar)residueAt:(NSUInteger)index; + +- (NSString*)residueStringAt:(NSUInteger)index; + +- (void)deleteResiduesInRange:(NSRange)range; + +- (void)deleteResiduesAtIndexes:(NSIndexSet*)indexes; + +- (void)replaceCharactersInRange:(NSRange)range withString:(NSString*)residues; + +-(void)replaceOccurencesOfString:(NSString*)target withString:(NSString*)replacement options:(NSStringCompareOptions)options range:(NSRange)range; + +- (void)setGeneticTable: (NSDictionary*)geneticTable; + + +- (void)reverse; + +- (void)complement; + +-(void)translateFinal; + +- (NSRange)rangeOfSequence:(NSString*)string options:(NSStringCompareOptions)mask range:(NSRange)range; + + +- (void)concatenateString:(NSString*)aString; + +- (void)concatenate:(MFSequence*)aSequence; + +- (void)insert:(NSString*)string atIndex:(NSUInteger)index; + + +- (void)remove:(NSRange)range; + + +//- (void)removeAt:(NSUInteger)index; + +@end \ No newline at end of file diff --git a/Seqotron/MFSequence.m b/Seqotron/MFSequence.m new file mode 100755 index 0000000..4369bcd --- /dev/null +++ b/Seqotron/MFSequence.m @@ -0,0 +1,492 @@ +// +// MFSequence.m +// Seqotron +// +// Created by Mathieu Fourment on 25/01/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFSequence.h" +#import "MFNucleotide.h" + +@class MFDataType; + +@implementation MFSequence + +@synthesize sequence = _sequence; +@synthesize name = _name; +@synthesize dataType = _dataType; +@synthesize translated = _translated; + +- (id)initWithString:(NSString *)aSequence name:(NSString *)aName dataType:(MFDataType*)aDataType{ + if ( (self = [super init]) ) { + _name = [aName copy]; + _sequence = [aSequence mutableCopy]; + _dataType = [aDataType retain]; + _genetictable = nil; + _translated = NO; + } + return self; +} + +- (id)initWithString:(NSString *)aSequence name:(NSString *)aName{ + if ( (self = [super init]) ) { + _name = [aName copy]; + _sequence = [aSequence mutableCopy]; + _dataType = [[MFNucleotide alloc]init]; + _genetictable = nil; + _translated = NO; + } + return self; +} + +- (id)initWithName:(NSString *)aName{ + if ( (self = [super init]) ) { + _name = [aName copy]; + _sequence = [[NSMutableString alloc] initWithCapacity:10]; + _dataType = [[MFNucleotide alloc]init]; + _genetictable = nil; + _translated = NO; + } + return self; +} + +- (id)copyWithZone:(NSZone *)zone { + MFSequence *copy = [[MFSequence alloc]initWithString:_sequence name:_name dataType:_dataType]; + [copy setTranslated:_translated]; + NSDictionary *dict = [_genetictable copy]; + [copy setGeneticTable:dict]; + [dict release]; + return copy; +} + +- (void)dealloc{ + [_name release]; + [_sequence release]; + [_genetictable release]; + [_dataType release]; + [super dealloc]; +} + +-(NSString*)sequenceString{ + return [self subSequenceWithRange:NSMakeRange(0, [self length])]; +} + +- (NSUInteger)length{ + if( _translated){ + return ([_sequence length]+2)/3; + } + return [_sequence length]; +} + +- (NSString *)subSequenceWithRange:(NSRange)range{ + if(range.location >= [_sequence length] ){ + return nil; + } + if( _translated){ + NSMutableString *aa = [[NSMutableString alloc] initWithCapacity:range.length]; + range.length *= 3; + range.location *= 3; + + // 0 1 + // K X + // AAAGG + // 01234 + + NSUInteger len = range.location+range.length; + if( len > [_sequence length] ){ + len = [_sequence length] - [_sequence length]%3; + } + + for( NSUInteger i = range.location; i < len; i += 3){ + NSString *codon = [[_sequence substringWithRange:NSMakeRange(i, 3)] uppercaseString]; + + if( [codon isEqualTo:@"---"] ){ + [aa appendString:@"-"]; + } + else if( [[_genetictable objectForKey:codon]objectForKey: @"OLC"] == nil ){ + [aa appendString:@"X"]; + } + else { + [aa appendString: [[_genetictable objectForKey:codon]objectForKey: @"OLC"]]; + } + } + + //if( [_sequence length]%3 != 0 ){ + if( range.location+range.length > [_sequence length] ){ + [aa appendString:@"X"]; + } + return [aa autorelease]; + } + + return [_sequence substringWithRange:range]; +} + +- (NSString *)subCodonSequenceWithRange:(NSRange)range{ + if(range.location >= [_sequence length] ){ + return nil; + } + if( _translated){ + range.length *= 3; + range.location *= 3; + + if( range.length+range.location > [_sequence length] ){ + range.length = [_sequence length] - range.location; + } + + return [_sequence substringWithRange:range]; + } + + return [_sequence substringWithRange:range]; +} + + +- (void)insertResidues:(NSString*)residues AtIndex:(NSUInteger)index{ + + if ( _translated ) { + [_sequence insertString:residues atIndex:index*3]; + } + else{ + [_sequence insertString:residues atIndex:index]; + } + +} + +- (void)insertGaps:(NSUInteger)ngaps AtIndex:(NSUInteger)index{ + + if ( _translated ) { + NSMutableString *gaps = [NSMutableString stringWithCapacity:ngaps*3]; + for ( NSUInteger i = 0; i < ngaps; i++ ) { + [gaps appendString:@"---"]; + } + + [_sequence insertString:gaps atIndex:index*3]; + + } + else{ + NSMutableString *gaps = [NSMutableString stringWithCapacity:ngaps]; + for ( NSUInteger i = 0; i < ngaps; i++ ) { + [gaps appendString:@"-"]; + } + [_sequence insertString:gaps atIndex:index]; + } + +} + +- (void)appendGaps:(NSUInteger)ngaps { + + if ( _translated ) { + NSMutableString *gaps = [NSMutableString stringWithCapacity:ngaps*3]; + // We need to add gaps + if( [_sequence length]%3 != 0 ){ + if( [_sequence length]%3 == 1 ){ + [gaps appendString:@"-"]; + } + [gaps appendString:@"-"]; + } + for ( NSUInteger i = 0; i < ngaps; i++ ) { + [gaps appendString:@"---"]; + } + [_sequence appendString:gaps]; + } + else{ + NSMutableString *gaps = [NSMutableString stringWithCapacity:ngaps]; + for ( NSUInteger i = 0; i < ngaps; i++ ) { + [gaps appendString:@"-"]; + } + + [_sequence appendString:gaps]; + } +} + +- (void)deleteResiduesInRange:(NSRange)range{ + if ( _translated ) { + range.location *= 3; + range.length *= 3; + + if( range.length+range.location > [_sequence length] ){ + range.length = [_sequence length] - range.location; + } + } + [_sequence deleteCharactersInRange:range ]; + +} + + +// indexes are very likely to be consecutive if we block edit +- (void)deleteResiduesAtIndexes:(NSIndexSet*)indexes{ + if ( _translated ) { + [indexes enumerateIndexesWithOptions:NSEnumerationReverse usingBlock:^(NSUInteger idx, BOOL *stop) { + NSRange range = NSMakeRange(idx*3, 3); + [_sequence deleteCharactersInRange: range]; + }]; + } + else{ + [indexes enumerateIndexesWithOptions:NSEnumerationReverse usingBlock:^(NSUInteger idx, BOOL *stop) { + NSRange range = NSMakeRange(idx, 1); + [_sequence deleteCharactersInRange: range ]; + }]; + } +} + + +- (unichar)residueAt:(NSUInteger)index{ + if ( _translated ) { + index *= 3; + // K..X + // AAAA + // * + if( [_sequence length] - index < 3 ){ + return 'X'; + } + else { + NSString *codon = [[_sequence substringWithRange:NSMakeRange(index, 3)] uppercaseString]; + if( [codon isEqualToString:@"---"]){ + return '-'; + } + else if( [[_genetictable objectForKey:codon]objectForKey: @"OLC"] == nil ){ + return 'X'; + } + NSString *res = [[_genetictable objectForKey:codon]objectForKey: @"OLC"]; + return [res UTF8String][0]; + } + + } + return [_sequence characterAtIndex: index]; +} + +- (NSString*)residueStringAt:(NSUInteger)index{ + if ( _translated ) { + index *= 3; + // K..X + // AAAA + // * + if( [_sequence length] - index < 3 ){ + return @"X"; + } + else { + NSString *codon = [[_sequence substringWithRange:NSMakeRange(index, 3)] uppercaseString]; + if( [codon isEqualToString:@"---"]){ + return @"-"; + } + else if( [[_genetictable objectForKey:codon]objectForKey: @"OLC"] == nil ){ + return @"X"; + } + return [[_genetictable objectForKey:codon]objectForKey: @"OLC"]; + } + + } + return [_sequence substringWithRange:NSMakeRange(index, 1)]; +} + +// If it is translated we could replace a codon +-(void)replaceCharactersInRange:(NSRange)range withString:(NSString*)residues{ + if(_translated){ + NSLog(@"Replacing is not allowed when sequence is translated"); + } + else{ + [_sequence replaceCharactersInRange:range withString:residues]; + } + +} + +-(void)replaceOccurencesOfString:(NSString*)target withString:(NSString*)replacement options:(NSStringCompareOptions)options range:(NSRange)range{ + if(_translated){ + NSLog(@"Replacing is not allowed when sequence is translated"); + } + else{ + [_sequence replaceOccurrencesOfString:target withString:replacement options:options range:range]; + } + +} + +-(void)removeAllGaps{ + if(_translated){ + NSLog(@"removeAllGaps is not allowed when sequence is translated"); + } + else{ + [_sequence setString: [_sequence stringByReplacingOccurrencesOfString:@"-" withString:@""]]; + } +} + +-(NSRange)rangeOfSequence:(NSString*)string options:(NSStringCompareOptions)mask range:(NSRange)range{ + if(_translated){ + NSString *str = [self subSequenceWithRange:range]; + NSRange r = [str rangeOfString:string options:mask range:range]; + r.length /= 3; + r.location /= 3; + return r; + } + else{ + return [_sequence rangeOfString:string options:mask range:range]; + } +} + +-(void)trimEndGaps{ + NSRange range = NSMakeRange([_sequence length], 0); + for ( NSInteger i = [_sequence length]-1; i >= 0; i-- ) { + if( [_sequence characterAtIndex: i] != '-' ){ + break; + } + range.location--; + range.length++; + } + if( range.length > 0 ){ + [_sequence deleteCharactersInRange:range ]; + } +} + +- (void)reverse{ + NSMutableString *reversedString = [[NSMutableString alloc] initWithCapacity:[_sequence length]]; + + [_sequence enumerateSubstringsInRange:NSMakeRange(0,[_sequence length]) + options:(NSStringEnumerationReverse | NSStringEnumerationByComposedCharacterSequences) + usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { + [reversedString appendString:substring]; + }]; + + [_sequence release]; + _sequence = reversedString; +} + +-(void)translateFinal{ + if( !self.translated ){ + NSMutableString *protein = [[NSMutableString alloc] initWithCapacity:[_sequence length]/3]; + + for ( NSUInteger index = 0; index < [_sequence length]; index+=3 ) { + if( [_sequence length] - index < 3 ){ + [protein appendString:@"X"]; + } + else { + NSString *codon = [[_sequence substringWithRange:NSMakeRange(index, 3)] uppercaseString]; + if( [codon isEqualToString:@"---"]){ + [protein appendString:@"-"]; + } + else if( [[_genetictable objectForKey:codon]objectForKey: @"OLC"] == nil ){ + [protein appendString:@"X"]; + } + else { + [protein appendString:[[_genetictable objectForKey:codon]objectForKey: @"OLC"]]; + } + } + } + [_sequence release]; + _sequence = protein; + } +} + +// should only be used on nucleotide sequences +- (void)complement{ + if(!_translated){ + NSMutableString *complementString = [[NSMutableString alloc] initWithCapacity:[_sequence length]]; + [_sequence enumerateSubstringsInRange:NSMakeRange(0,[_sequence length]) + options:(NSStringEnumerationByComposedCharacterSequences) + usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { + + NSString *upperString = [substring uppercaseString]; + + if( [@"A" isEqualToString:upperString] ){ + [complementString appendString:@"T"]; + } + else if( [@"T" isEqualToString:upperString] || [@"U" isEqualToString:upperString] ){ + [complementString appendString:@"A"]; + } + else if( [@"C" isEqualToString:upperString] ){ + [complementString appendString:@"G"]; + } + else if( [@"G" isEqualToString:upperString] ){ + [complementString appendString:@"C"]; + } + else if( [@"R" isEqualToString:upperString] ){ + [complementString appendString:@"Y"]; + } + else if( [@"Y" isEqualToString:upperString] ){ + [complementString appendString:@"R"]; + } + else if( [@"M" isEqualToString:upperString] ){ + [complementString appendString:@"K"]; + } + else if( [@"K" isEqualToString:upperString] ){ + [complementString appendString:@"M"]; + } + else if( [@"S" isEqualToString:upperString] || [@"W" isEqualToString:upperString] ){ + [complementString appendString:substring]; + } + else if( [@"V" isEqualToString:upperString] ){ + [complementString appendString:@"B"]; + } + else if( [@"B" isEqualToString:upperString] ){ + [complementString appendString:@"V"]; + } + else if( [@"H" isEqualToString:upperString] ){ + [complementString appendString:@"D"]; + } + else if( [@"D" isEqualToString:upperString] ){ + [complementString appendString:@"H"]; + } + // if we don't know what it is then we leave it. We could not get the orginal sequence after a redo + else{ + [complementString appendString:upperString]; + } + }]; + [_sequence release]; + _sequence = complementString; + } +} + +- (void)concatenate:(MFSequence*)aSequence{ + [self concatenateString:[aSequence sequenceString]]; +} + +- (void)concatenateString:(NSString*)aString{ + [_sequence appendString:aString]; +} + + +-(void)setGeneticTable: (NSDictionary*)geneticTable{ + if( ![_genetictable isEqualToDictionary: geneticTable] ){ + [_genetictable release]; + _genetictable = [geneticTable retain]; + } +} + +- (void)insert:(NSString *)string atIndex:(NSUInteger)index{ + [_sequence insertString: string atIndex: index]; +} + +#pragma mark *** Check these methods *** + + + +- (void)remove:(NSRange)range{ + [_sequence deleteCharactersInRange: range ]; +} + + +- (void)removeAt:(NSUInteger)index{ + [_sequence deleteCharactersInRange:NSMakeRange(index, 1) ]; +} + + + + + + + + +@end \ No newline at end of file diff --git a/Seqotron/MFSequenceImporter.h b/Seqotron/MFSequenceImporter.h new file mode 100755 index 0000000..10437a3 --- /dev/null +++ b/Seqotron/MFSequenceImporter.h @@ -0,0 +1,38 @@ +// +// MFSequenceImporter.h +// Seqotron +// +// Created by Mathieu Fourment on 4/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFSequenceSet.h" + +@protocol MFSequenceImporter + +-(MFSequenceSet*)readSequencesFromFile:(NSString*)path; + +-(MFSequenceSet*)readSequencesFromURL:(NSURL*)url; + +-(MFSequenceSet*)readSequencesFromData:(NSData*)data; + +-(MFSequenceSet*)readSequencesFromString:(NSString*)content; + +@end diff --git a/Seqotron/MFSequenceReader.h b/Seqotron/MFSequenceReader.h new file mode 100755 index 0000000..cf807b1 --- /dev/null +++ b/Seqotron/MFSequenceReader.h @@ -0,0 +1,41 @@ +// +// MFSequenceReader.h +// Seqotron +// +// Created by Mathieu Fourment on 25/01/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +@class MFSequenceSet; + + +@interface MFSequenceReader : NSObject{ + +} + ++ (MFSequenceSet *)readSequencesFromData:(NSData *)data; + ++ (MFSequenceSet *)readSequencesFromFile:(NSString *)filePath; + ++ (MFSequenceSet *)readSequencesFromURL:(NSURL*)url; + ++ (MFSequenceSet *)readSequencesFromString:(NSString *)content; + +@end diff --git a/Seqotron/MFSequenceReader.m b/Seqotron/MFSequenceReader.m new file mode 100755 index 0000000..97b1523 --- /dev/null +++ b/Seqotron/MFSequenceReader.m @@ -0,0 +1,277 @@ +// +// MFSequenceReader.m +// Seqotron +// +// Created by Mathieu Fourment on 25/01/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFSequenceReader.h" + + +#import "MFSequence.h" +#import "MFSequenceSet.h" +#import "MFString.h" +#import "MFNucleotide.h" +#import "MFProtein.h" +#import "MFDefines.h" +#import "MFReaderCluster.h" + +#import "MFNexusImporter.h" +#import "MFFASTAImporter.h" +#import "MFPhylipImporter.h" +#import "MFClustalImporter.h" +#import "MFGDEImporter.h" +#import "MFMEGAImporter.h" +#import "MFNBRFImporter.h" +#import "MFStockholmImporter.h" + +@implementation MFSequenceReader + +NSString * const MFSequenceFileFormat = @"tk.phylogenetics.sequence.file.format"; + ++ (MFSequenceSet *)readSequencesFromData:(NSData *)data{ + NSString *content = [[NSString alloc] initWithData: data encoding: NSASCIIStringEncoding]; + + MFSequenceSet *sequences = [MFSequenceReader readSequencesFromString:content]; + + [content release]; + return sequences; +} + ++ (MFSequenceSet *)readSequencesFromURL:(NSURL*)url{ + NSString *content = [ NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil]; + MFSequenceSet *sequences = [MFSequenceReader readSequencesFromString:content]; + + return sequences; +} + ++ (MFSequenceSet *)readSequencesFromString:(NSString *)content{ + + // For Phylip + NSString *pattern = @"^\\s*\\d+\\s+\\d+\\s*[iIsS]?"; + NSError *error = nil; + NSRegularExpression* regex = [NSRegularExpression regularExpressionWithPattern: pattern options:0 error:&error]; + + NSRange range = NSMakeRange(0, 0); + NSUInteger start, end; + NSUInteger contentsEnd = 0; + + NSArray *formats = [[NSArray alloc] initWithObjects:MFSequenceFormatArray]; + + MFSequenceSet *sequenceSet = nil; + + while ( contentsEnd < [content length]) { + [content getLineStart:&start end:&end contentsEnd:&contentsEnd forRange:range]; + NSString *line = [content substringWithRange:NSMakeRange(start,contentsEnd-start)]; + NSString *temp = line; + + + // Allow the first line to be empty + if( [[temp stringByTrimmingPaddingWhitespace] length] == 0 ){ + range.location = end; + range.length = 0; + continue; + } + + // NBRF + if( [line hasPrefix:@">P1;"] || [line hasPrefix:@">F1;"] + || [line hasPrefix:@">D1;"] || [line hasPrefix:@">DC;"] || [line hasPrefix:@">DC;"] + || [line hasPrefix:@">RL;"] || [line hasPrefix:@">F1;"] + || [line hasPrefix:@">N1;"] || [line hasPrefix:@">N3;"] + || [line hasPrefix:@">XX;"] ){ + NSLog(@"Reading NBRF Flat file!"); + MFNBRFImporter *importer = [[MFNBRFImporter alloc]init]; + sequenceSet = [importer readSequencesFromString:content ]; + [importer release]; + [sequenceSet addAnnotation:[formats objectAtIndex:MFSequenceFormatNBRF ] forKey:MFSequenceFileFormat]; + } + // FASTA + else if( [line hasPrefix:@">"] ){ + NSLog(@"Reading FASTA file!"); + MFFASTAImporter *importer = [[MFFASTAImporter alloc]init]; + sequenceSet = [importer readSequencesFromString:content ]; + [importer release]; + [sequenceSet addAnnotation:[formats objectAtIndex:MFSequenceFormatFASTA ] forKey:MFSequenceFileFormat]; + } + // Nexus + else if( [[line uppercaseString] hasPrefix:@"#NEXUS"]){ + NSLog(@"Reading NEXUS file!"); + MFNexusImporter *importer = [[MFNexusImporter alloc]init]; + sequenceSet = [importer readSequencesFromString:content ]; + [importer release]; + [sequenceSet addAnnotation:[formats objectAtIndex:MFSequenceFormatNEXUS ] forKey:MFSequenceFileFormat]; + + } + // CLUSTAL + else if( [[line uppercaseString] hasPrefix:@"CLUSTAL"]){ + NSLog(@"Reading CLUSTAL file!"); + MFClustalImporter *importer = [[MFClustalImporter alloc]init]; + sequenceSet = [importer readSequencesFromString:content ]; + [importer release]; + [sequenceSet addAnnotation:[formats objectAtIndex:MFSequenceFormatCLUSTAL ] forKey:MFSequenceFileFormat]; + + } + // MEGA + else if( [[line uppercaseString] hasPrefix:@"#MEGA"] ){ + NSLog(@"Reading MEGA file!"); + MFMEGAImporter *importer = [[MFMEGAImporter alloc]init]; + sequenceSet = [importer readSequencesFromString:content ]; + [importer release]; + [sequenceSet addAnnotation:[formats objectAtIndex:MFSequenceFormatMEGA ] forKey:MFSequenceFileFormat]; + } + // Stockholm + else if( [[line uppercaseString] hasPrefix:@"# STOCKHOLM"] || [[line uppercaseString] hasPrefix:@"#STOCKHOLM"] ){ + NSLog(@"Reading Stockholm!"); + MFStockholmImporter *importer = [[MFStockholmImporter alloc]init]; + sequenceSet = [importer readSequencesFromString:content ]; + [importer release]; + [sequenceSet addAnnotation:[formats objectAtIndex:MFSequenceFormatSTOCKHOLM ] forKey:MFSequenceFileFormat]; + } + // GDE + else if( [[line uppercaseString] hasPrefix:@"#"] ){ + NSLog(@"Reading GDE Flat file!"); + MFGDEImporter *importer = [[MFGDEImporter alloc]init]; + sequenceSet = [importer readSequencesFromString:content ]; + [importer release]; + [sequenceSet addAnnotation:[formats objectAtIndex:MFSequenceFormatGDE ] forKey:MFSequenceFileFormat]; + } + else { + NSRange searchedRange = NSMakeRange(0, [line length]); + NSArray* matches = [regex matchesInString:line options:0 range: searchedRange]; + // Phylip + if( [matches count] == 1 ){ + NSLog(@"Reading Phylip file!"); + MFPhylipImporter *importer = [[MFPhylipImporter alloc]init]; + sequenceSet = [importer readSequencesFromString:content ]; + [importer release]; + [sequenceSet addAnnotation:[formats objectAtIndex:MFSequenceFormatPHYLIP ] forKey:MFSequenceFileFormat]; + + } + } + break; + } + [formats release]; + + return sequenceSet; +} + + ++ (MFSequenceSet *)readSequencesFromFile:(NSString *)path{ + + // For Phylip + NSString *pattern = @"^\\s*\\d+\\s+\\d+\\s*[iIsS]?"; + NSError *error = nil; + NSRegularExpression* regex = [NSRegularExpression regularExpressionWithPattern: pattern options:0 error:&error]; + + NSArray *formats = [[NSArray alloc] initWithObjects:MFSequenceFormatArray]; + + MFSequenceSet *sequenceSet = nil; + + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithFile:path]; + NSString *line = nil; + + while ( (line = [reader readLine]) ) { + // Allow the first line to be empty + if( [[line stringByTrimmingPaddingWhitespace] length] == 0 ){ + continue; + } + line = [line copy]; // seems bad + break; + } + [reader release]; + + // NBRF + if( [[line uppercaseString] hasPrefix:@">P1;"] ){ + NSLog(@"Reading NBRF Flat file!"); + MFNBRFImporter *importer = [[MFNBRFImporter alloc]init]; + sequenceSet = [importer readSequencesFromFile:path ]; + [importer release]; + [sequenceSet addAnnotation:[formats objectAtIndex:MFSequenceFormatNBRF ] forKey:MFSequenceFileFormat]; + } + // FASTA + else if( [line hasPrefix:@">"] ){ + NSLog(@"Reading FASTA file!"); + MFFASTAImporter *importer = [[MFFASTAImporter alloc]init]; + sequenceSet = [importer readSequencesFromFile:path ]; + [importer release]; + [sequenceSet addAnnotation:[formats objectAtIndex:MFSequenceFormatFASTA ] forKey:MFSequenceFileFormat]; + } + // Nexus + else if( [[line uppercaseString] hasPrefix:@"#NEXUS"]){ + NSLog(@"Reading NEXUS file!"); + MFNexusImporter *importer = [[MFNexusImporter alloc]init]; + sequenceSet = [importer readSequencesFromFile:path ]; + [importer release]; + [sequenceSet addAnnotation:[formats objectAtIndex:MFSequenceFormatNEXUS ] forKey:MFSequenceFileFormat]; + + } + // CLUSTAL + else if( [[line uppercaseString] hasPrefix:@"CLUSTAL"]){ + NSLog(@"Reading CLUSTAL file!"); + MFClustalImporter *importer = [[MFClustalImporter alloc]init]; + sequenceSet = [importer readSequencesFromFile:path ]; + [importer release]; + [sequenceSet addAnnotation:[formats objectAtIndex:MFSequenceFormatCLUSTAL ] forKey:MFSequenceFileFormat]; + + } + // MEGA + else if( [[line uppercaseString] hasPrefix:@"#MEGA"] ){ + NSLog(@"Reading MEGA file!"); + MFMEGAImporter *importer = [[MFMEGAImporter alloc]init]; + sequenceSet = [importer readSequencesFromFile:path ]; + [importer release]; + [sequenceSet addAnnotation:[formats objectAtIndex:MFSequenceFormatMEGA ] forKey:MFSequenceFileFormat]; + } + // Stockholm + else if( [[line uppercaseString] hasPrefix:@"# STOCKHOLM"] || [[line uppercaseString] hasPrefix:@"#STOCKHOLM"] ){ + NSLog(@"Reading Stockholm!"); + MFStockholmImporter *importer = [[MFStockholmImporter alloc]init]; + sequenceSet = [importer readSequencesFromFile:path ]; + [importer release]; + [sequenceSet addAnnotation:[formats objectAtIndex:MFSequenceFormatSTOCKHOLM ] forKey:MFSequenceFileFormat]; + } + // GDE + else if( [[line uppercaseString] hasPrefix:@"#"] ){ + NSLog(@"Reading GDE Flat file!"); + MFGDEImporter *importer = [[MFGDEImporter alloc]init]; + sequenceSet = [importer readSequencesFromFile:path ]; + [importer release]; + [sequenceSet addAnnotation:[formats objectAtIndex:MFSequenceFormatGDE ] forKey:MFSequenceFileFormat]; + } + else { + NSRange searchedRange = NSMakeRange(0, [line length]); + NSArray* matches = [regex matchesInString:line options:0 range: searchedRange]; + // Phylip + if( [matches count] == 1 ){ + NSLog(@"Reading Phylip file!"); + MFPhylipImporter *importer = [[MFPhylipImporter alloc]init]; + sequenceSet = [importer readSequencesFromFile:path ]; + [importer release]; + [sequenceSet addAnnotation:[formats objectAtIndex:MFSequenceFormatPHYLIP ] forKey:MFSequenceFileFormat]; + + } + } + + [line release]; + [formats release]; + + return sequenceSet; +} + +@end diff --git a/Seqotron/MFSequenceSet.h b/Seqotron/MFSequenceSet.h new file mode 100755 index 0000000..d5146ae --- /dev/null +++ b/Seqotron/MFSequenceSet.h @@ -0,0 +1,73 @@ +// +// MFSequences.h +// Seqotron +// +// Created by Mathieu Fourment on 25/01/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFDataType.h" + +#import "MFSequence.h" + +@interface MFSequenceSet : NSObject{ + NSMutableArray *_sequences; + NSMutableDictionary *_annotations; +} + +-(MFDataType*)guessDataType; + +- (id)initWithCapacity:(NSUInteger)capacity; + +- (id)initWithSequences:(NSArray*)sequence; + +-(NSUInteger)count; + +- (void) addAnnotation:(NSString *)annotation forKey: (NSString *) key; + +- (id) annotationForKey: (NSString *) key; + +-(NSMutableArray*)sequences; + +-(MFSequence*)sequenceAt:(NSUInteger)index; + +-(NSArray*)sequencesAtIndexes:(NSIndexSet*)indexes; + +-(void)addSequence:(MFSequence*)aSequence; + +-(void)insertSequence:(MFSequence*)aSequence atIndex:(NSUInteger)index; + +-(void)insertSequences:(NSArray*)someSequences atIndexes:(NSIndexSet*)indexes; + +-(void)removeSequence:(MFSequence*)aSequence; + +-(void)removeSequenceAtIndex:(NSUInteger)index; + +-(void)removeSequencesInRange:(NSRange)range; + +-(void)removeSequencesAtIndexes:(NSIndexSet*)indexes; + +- (NSUInteger)indexFirstNonGap; + +-(void)empty; + +-(NSUInteger)size; + +@end diff --git a/Seqotron/MFSequenceSet.m b/Seqotron/MFSequenceSet.m new file mode 100755 index 0000000..802aeb3 --- /dev/null +++ b/Seqotron/MFSequenceSet.m @@ -0,0 +1,239 @@ +// +// MFSequences.m +// Seqotron +// +// Created by Mathieu Fourment on 25/01/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFSequenceSet.h" + +#include "MFSequence.h" +#include "MFProtein.h" +#include "MFNucleotide.h" + +@implementation MFSequenceSet + + + +- (id)init{ + if ( (self = [super init]) ) { + _sequences = [[NSMutableArray alloc] init]; + _annotations = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (id)initWithCapacity:(NSUInteger)capacity{ + if ( (self = [super init]) ) { + _sequences = [[NSMutableArray alloc] initWithCapacity:capacity]; + _annotations = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (id)initWithSequences:(NSArray*)sequences{ + if ( (self = [super init]) ) { + _sequences = [[NSMutableArray alloc] initWithArray:sequences]; + _annotations = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (void)dealloc{ + [_sequences release]; + [_annotations release]; + [super dealloc]; +} + +-(MFDataType*)guessDataType{ + __block NSUInteger count = 0; + __block NSUInteger countAA = 0; + + NSCharacterSet *nucSet = [NSCharacterSet characterSetWithCharactersInString: @"ACTGU"]; + NSCharacterSet *aaSet = [NSCharacterSet characterSetWithCharactersInString: @"ACDEFGHIKLMNPQERSTVWY"]; + + for (int i = 0; i < [_sequences count]; i++ ) { + NSString *sequence = [[_sequences objectAtIndex:i] sequenceString]; + + [sequence enumerateSubstringsInRange:NSMakeRange(0,[sequence length]) + options:(NSStringEnumerationReverse | NSStringEnumerationByComposedCharacterSequences) + usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { + if ( [substring rangeOfCharacterFromSet: nucSet].location != NSNotFound ){ + count++; + } + if ( [substring rangeOfCharacterFromSet: aaSet].location != NSNotFound ){ + countAA++; + } + }]; + } + + MFDataType *dataType; + if( (CGFloat)count/countAA > 0.8 ){ + dataType = [[MFNucleotide alloc]init]; + } + else { + dataType = [[MFProtein alloc]init]; + } + for (MFSequence *sequence in _sequences) { + [sequence setDataType:dataType]; + } + return [dataType autorelease]; +} + +- (void) addAnnotation:(NSString*)annotation forKey: (NSString*) key{ + [_annotations setObject:annotation forKey:key]; +} + +- (NSString*) annotationForKey:(NSString*) key{ + return [_annotations objectForKey:key]; +} + +-(NSUInteger)count{ + return [_sequences count]; +} + +-(NSMutableArray*)sequences{ + return _sequences; +} + +-(NSArray*)sequencesAtIndexes:(NSIndexSet*)indexes{ + return [_sequences objectsAtIndexes:indexes]; +} + +-(MFSequence*)sequenceAt:(NSUInteger)index{ + return [_sequences objectAtIndex:index]; +} + +-(void)addSequence:(MFSequence*)aSequence{ + [_sequences addObject:aSequence]; +} + +-(void)insertSequence:(MFSequence*)aSequence atIndex:(NSUInteger)index{ + [_sequences insertObject:aSequence atIndex:index]; +} + +-(void)insertSequences:(NSArray*)someSequences atIndexes:(NSIndexSet*)indexes{ + [_sequences insertObjects:someSequences atIndexes:indexes]; +} + +-(void)removeSequence:(MFSequence*)aSequence{ + [_sequences removeObject:aSequence]; +} + +-(void)removeSequenceAtIndex:(NSUInteger)index{ + [_sequences removeObjectAtIndex:index]; +} + +-(void)removeSequencesInRange:(NSRange)range{ + [_sequences removeObjectsInRange:range]; +} + +-(void)removeSequencesAtIndexes:(NSIndexSet*)indexes{ + [_sequences removeObjectsAtIndexes:indexes]; +} + +- (void)concatenate:(MFSequenceSet*)someSequences{ + if( [self size] != [someSequences size] ){ + return; + } + for ( int i = 0; i < [_sequences count]; i++ ) { + MFSequence *seq = [_sequences objectAtIndex:i]; + [seq concatenate: [someSequences sequenceAt:i]]; + } +} + + +-(void)insertGaps:(NSUInteger)nGaps atSite:(NSUInteger)site inRange:(NSRange)aRange{ + NSMutableString *gaps = [NSMutableString stringWithCapacity:nGaps]; + for ( NSUInteger i = 0; i < nGaps; i++ ) { + [gaps appendString:@"-"]; + } + + for ( NSUInteger i = 0; i < aRange.length; i++ ) { + MFSequence *sequence = [self sequenceAt: aRange.location+i]; + [sequence insert:gaps atIndex:site]; + } + [self pad]; +} + + + +- (void)pad{ + if( [_sequences count] > 0 ){ + NSRange range; + NSUInteger maxLength = [self maxLength: _sequences]; + + + for (MFSequence *sequence in _sequences) { + if([sequence length] > maxLength ){ + range.location = maxLength; + range.length = [sequence length] - maxLength; + [sequence deleteResiduesInRange:range]; + } + else if( [sequence length] < maxLength ){ + NSUInteger nGaps = maxLength - [sequence length]; + [sequence appendGaps:nGaps]; + } + } + } +} + +// return the maximum length excuding the last gaps +-(NSUInteger)maxLength:(NSArray*)sequences{ + NSUInteger maxLength = 0; + if( [sequences count] != 0 ){ + // + for (MFSequence *sequence in sequences) { + for ( NSInteger i = [sequence length]-1; i >= 0; i--) { + if( [sequence residueAt:i] != '-' ){ + if( i > maxLength ){ + maxLength = i; + } + break; + } + } + } + } + return maxLength+1; +} + + +- (NSUInteger)indexFirstNonGap{ + MFDataType *datatype = [[_sequences objectAtIndex:0]dataType]; + for ( NSUInteger i = 0; i < [[_sequences objectAtIndex:0]length]; i++ ) { + for (MFSequence *sequence in _sequences) { + if( ![datatype isGap:[sequence residueStringAt:i]] ){ + return i; + } + } + } + return NSNotFound; +} + +-(void)empty{ + [_sequences removeAllObjects]; +} + +-(NSUInteger)size{ + return [_sequences count]; +} + + + +@end diff --git a/Seqotron/MFSequenceUtils.h b/Seqotron/MFSequenceUtils.h new file mode 100755 index 0000000..39d69d7 --- /dev/null +++ b/Seqotron/MFSequenceUtils.h @@ -0,0 +1,40 @@ +// +// MFSequenceUtils.h +// Seqotron +// +// Created by Mathieu Fourment on 29/10/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +@interface MFSequenceUtils : NSObject + ++ (NSUInteger)maxLength:(NSArray*)sequences; + ++ (BOOL)isStartingWithOneGap:(NSArray*)sequences; + ++ (NSUInteger)numberOfEmptyColumnsAtFront:(NSArray*)sequences; + ++ (BOOL)isEmptyBlockAtTheEnd:(NSArray*)sequences inRange:(NSRange)range; + ++ (void)pad:(NSArray*)sequences; + ++ (void)deleteFrontEmptyColumns:(NSArray*)sequences; + +@end diff --git a/Seqotron/MFSequenceUtils.m b/Seqotron/MFSequenceUtils.m new file mode 100755 index 0000000..f20649b --- /dev/null +++ b/Seqotron/MFSequenceUtils.m @@ -0,0 +1,179 @@ +// +// MFSequenceUtils.m +// Seqotron +// +// Created by Mathieu Fourment on 29/10/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFSequenceUtils.h" + +#import "MFSequence.h" + +@implementation MFSequenceUtils + ++ (NSUInteger)maxLength:(NSArray*)sequences{ + NSUInteger maxLength = 0; + if( [sequences count] != 0 ){ + // + for (MFSequence *sequence in sequences) { + for ( NSInteger i = [sequence length]-1; i >= 0; i--) { + if( [sequence residueAt:i] != '-' ){ + if( i > maxLength ){ + maxLength = i; + } + break; + } + } + } + } + return maxLength+1; +} + ++ (NSUInteger)numberOfEmptyColumnsAtFront:(NSArray*)sequences{ + MFDataType *datatype = [[sequences objectAtIndex:0]dataType]; + for ( NSUInteger i = 0; i < [[sequences objectAtIndex:0]length]; i++ ) { + for (MFSequence *sequence in sequences) { + if( ![datatype isGap:[sequence residueStringAt:i]] ){ + return i; + } + } + } + return NSNotFound; +} + + ++ (BOOL)isStartingWithOneGap:(NSArray*)sequences{ + for (MFSequence *sequence in sequences) { + if ( [sequence residueAt:0] != '-' ) { + return NO; + } + } + return YES; +} + +// test if the selection is made of gaps only and is located at the end of the aligment ++ (BOOL)isEmptyBlockAtTheEnd:(NSArray*)sequences inRange:(NSRange)range{ + if( [sequences count] > 0 ){ + + // test if there is residues behind the selection that are not gaps + if( range.location+range.length < [[sequences objectAtIndex:0] length]){ + for ( NSUInteger i = 0; i < [sequences count]; i++ ) { + MFSequence *sequence = [sequences objectAtIndex: i]; + + for ( NSUInteger j = range.location+range.length; j < [sequence length]; j++ ) { + if([sequence residueAt:j] != '-') return NO; + } + } + } + + // At this point selection can be gaps only or contain residues but not followed by gaps (it could be at the end) + for ( NSUInteger i = 0; i < [sequences count]; i++ ) { + MFSequence *sequence = [sequences objectAtIndex: i]; + NSUInteger j = range.location; + for ( ; j < range.location+range.length; j++ ) { + if([sequence residueAt:j] != '-') return NO; + } + } + return YES; + } + return NO; +} + ++ (void)pad:(NSArray*)sequences{ + if( [sequences count] != 0 ){ + NSUInteger maxLength = [MFSequenceUtils maxLength:sequences]; + + if( maxLength != 0 ){ + for (MFSequence *sequence in sequences) { + if( [sequence length] > maxLength ){ + NSRange range; + range.location = maxLength; + range.length = [sequence length] - maxLength; + [sequence deleteResiduesInRange:range]; + } + else if( [sequence length] < maxLength ){ + NSUInteger nGaps = maxLength - [sequence length]; + [sequence appendGaps:nGaps]; + } + } + } + } +} + ++ (void)deleteFrontEmptyColumns:(NSArray*)sequences{ + NSUInteger empties = [MFSequenceUtils numberOfEmptyColumnsAtFront:sequences]; + if( empties > 0 ){ + NSRange range = NSMakeRange(0, empties); + for (MFSequence *sequence in sequences) { + [sequence deleteResiduesInRange:range]; + } + } +} + ++ (MFSequence*)consensus:(NSArray*)sequences withDescription:(NSString*)desc{ + NSInteger len = [[sequences objectAtIndex:0]length]; + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + + NSMutableString *seqString = [[NSMutableString alloc]initWithCapacity:len]; + for ( NSInteger i = 0; i < len; i++ ) { + for (MFSequence *sequence in sequences) { + NSString *res = [sequence residueStringAt:i]; + if( [dict objectForKey:res]){ + [dict setObject:[NSNumber numberWithInt:[[dict objectForKey:res] intValue] + 1] forKey:res]; + } + else { + [dict setObject:[NSNumber numberWithInt:0] forKey:res]; + } + } + __block NSInteger max = -1; + __block NSMutableArray *residues; + [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){ + if( [obj intValue] >= max){ + max = [obj intValue]; + [residues addObject:key]; + } + }]; + + if( [residues count] == 1 ){ + [seqString appendString:[residues objectAtIndex:0]]; + } + else { + [residues removeObjectsInArray:[NSArray arrayWithObjects:@"-",@"?",nil]]; + // it was only -s and ?s + if ([residues count] == 0){ + [seqString appendString:@"?"]; + } + // it was a unique residue AND - and/or ? + else if ([residues count] == 1){ + [seqString appendString:[residues objectAtIndex:0]]; + } + else { + //TODO: should use ambiguity symbols + [seqString appendString:[residues objectAtIndex:0]]; + } + } + + } + + MFSequence *sequence = [[MFSequence alloc] initWithString:seqString name:desc]; + [seqString release]; + return [sequence autorelease]; +} + +@end diff --git a/Seqotron/MFSequenceWriter.h b/Seqotron/MFSequenceWriter.h new file mode 100755 index 0000000..cf5c15f --- /dev/null +++ b/Seqotron/MFSequenceWriter.h @@ -0,0 +1,83 @@ +// +// MFSequenceWriter.h +// Seqotron +// +// Created by Mathieu Fourment on 31/01/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFDefines.h" +#import "MFSequenceSet.h" + +extern NSString *MFSequenceWriterRange; +extern NSString *MFSequenceWriterIgnoreLeadingGaps; + +@interface MFSequenceWriter : NSObject + ++ (NSData *) data:(MFSequenceSet *) inSequences withFormat:(MFSequenceFormat)format attributes:(NSDictionary*)attrs; + ++ (NSString *) string:(MFSequenceSet *) inSequences withFormat:(MFSequenceFormat)format attributes:(NSDictionary*)attrs; + + +#pragma mark FASTA + ++ (void)writeFasta:(MFSequenceSet *) sequences toFile:(NSString *)file attributes:(NSDictionary*)attrs; + ++ (NSString*) stringFasta:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs; + ++ (NSData*) dataFasta:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs; + +#pragma mark Nexus + ++ (NSString*) stringNexus:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs; + ++ (NSData*) dataNexus:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs; + +#pragma mark Phylip + ++ (NSString*) stringPhylip:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs; + ++ (NSData*) dataPhylip:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs; + +#pragma mark MEGA + ++ (NSData *) dataMega:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs; + ++ (NSString *) stringMega:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs; + +#pragma mark GDE + ++ (NSData *) dataGDE:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs; + ++ (NSString *) stringGDE:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs; + +#pragma mark NBRF + ++ (NSData *) dataNBRF:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs; + ++ (NSString *) stringNBRF:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs; + +#pragma mark Stockholm + ++ (NSData *) dataStockholm:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs; + ++ (NSString *) stringStockholm:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs; + +@end diff --git a/Seqotron/MFSequenceWriter.m b/Seqotron/MFSequenceWriter.m new file mode 100755 index 0000000..c514931 --- /dev/null +++ b/Seqotron/MFSequenceWriter.m @@ -0,0 +1,477 @@ +// +// MFSequenceWriter.m +// Seqotron +// +// Created by Mathieu Fourment on 31/01/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFSequenceWriter.h" + +#include "MFSequenceSet.h" +#include "MFSequence.h" +#include "MFString.h" +#include "MFNucleotide.h" + + +NSString *MFSequenceWriterRange = @"MFSequenceRange"; +NSString *MFSequenceWriterIgnoreLeadingGaps = @"MFSequenceIgnoreLeadingGaps"; + +@implementation MFSequenceWriter + + + ++ (NSData *) data:(MFSequenceSet *) inSequences withFormat:(MFSequenceFormat)format attributes:(NSDictionary*)attrs{ + NSData *data = nil; + NSArray *formats = [[NSArray alloc] initWithObjects:MFSequenceFormatArray]; + switch (format) { + case MFSequenceFormatFASTA: + data = [MFSequenceWriter dataFasta:inSequences attributes:attrs]; + break; + case MFSequenceFormatNEXUS: + data = [MFSequenceWriter dataNexus:inSequences attributes:attrs]; + break; + case MFSequenceFormatPHYLIP: + data = [MFSequenceWriter dataPhylip:inSequences attributes:attrs]; + break; + case MFSequenceFormatMEGA: + data = [MFSequenceWriter dataMega:inSequences attributes:attrs]; + break; + case MFSequenceFormatCLUSTAL: + data = [MFSequenceWriter dataClustal:inSequences attributes:attrs]; + break; + case MFSequenceFormatGDE: + data = [MFSequenceWriter dataGDE:inSequences attributes:attrs]; + break; + case MFSequenceFormatNBRF: + data = [MFSequenceWriter dataNBRF:inSequences attributes:attrs]; + break; + case MFSequenceFormatSTOCKHOLM: + data = [MFSequenceWriter dataStockholm:inSequences attributes:attrs]; + break; + default: + NSLog(@"Format not recognized"); + break; + } + + [formats release]; + return data; +} + ++ (NSString *) string:(MFSequenceSet *) inSequences withFormat:(MFSequenceFormat)format attributes:(NSDictionary*)attrs{ + NSString *data = nil; + + NSArray *formats = [[NSArray alloc] initWithObjects:MFSequenceFormatArray]; + switch (format) { + case MFSequenceFormatFASTA: + data = [MFSequenceWriter stringFasta:inSequences attributes:attrs]; + break; + case MFSequenceFormatNEXUS: + data = [MFSequenceWriter stringNexus:inSequences attributes:attrs]; + break; + case MFSequenceFormatPHYLIP: + data = [MFSequenceWriter stringPhylip:inSequences attributes:attrs]; + break; + case MFSequenceFormatCLUSTAL: + data = [MFSequenceWriter stringClustal:inSequences attributes:attrs]; + break; + case MFSequenceFormatMEGA: + data = [MFSequenceWriter stringMega:inSequences attributes:attrs]; + break; + case MFSequenceFormatGDE: + data = [MFSequenceWriter stringGDE:inSequences attributes:attrs]; + break; + case MFSequenceFormatNBRF: + data = [MFSequenceWriter stringNBRF:inSequences attributes:attrs]; + break; + case MFSequenceFormatSTOCKHOLM: + data = [MFSequenceWriter stringStockholm:inSequences attributes:attrs]; + break; + default: + NSLog(@"Format not recognized"); + break; + } + + [formats release]; + return data; +} + +#pragma mark FASTA + ++ (void)writeFasta:(MFSequenceSet *) inSequences toFile:(NSString *)file attributes:(NSDictionary*)attrs{ + NSString *fastaString = [MFSequenceWriter stringFasta:inSequences attributes:attrs]; + [fastaString writeToFile:file atomically:NO encoding:NSUTF8StringEncoding error:nil]; +} + ++ (NSData *) dataFasta:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs{ + NSString *string = [MFSequenceWriter stringFasta:inSequences attributes:attrs]; + NSData *nsdata = [string dataUsingEncoding:NSUTF8StringEncoding]; + return nsdata; +} + ++ (NSString *) stringFasta:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs{ + NSMutableArray *sequences = [inSequences sequences]; + NSMutableString *fastaString = [NSMutableString string]; + + NSRange range = NSMakeRange(0, [[inSequences sequenceAt:0] length]); + if( [attrs objectForKey:MFSequenceWriterRange] ){ + range = [[attrs objectForKey:MFSequenceWriterRange]rangeValue]; + } + else if( [[attrs objectForKey:MFSequenceWriterIgnoreLeadingGaps]boolValue] ){ + NSUInteger index = [inSequences indexFirstNonGap]; + range.location = index; + range.length -= index; + } + + for (MFSequence *sequence in sequences) { + [fastaString appendFormat:@">%@\r",[sequence name]]; + [fastaString appendFormat:@"%@\r",[sequence subSequenceWithRange:range]]; + + } + return fastaString; +} + +#pragma mark NEXUS + ++ (NSData *) dataNexus:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs{ + NSString *string = [MFSequenceWriter stringNexus:inSequences attributes:attrs]; + NSData *nsdata = [string dataUsingEncoding:NSUTF8StringEncoding]; + return nsdata; +} + ++ (NSString *) stringNexus:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs{ + NSMutableArray *sequences = [inSequences sequences]; + NSMutableString *string = [NSMutableString string]; + + [string appendString:@"#NEXUS\r\r"]; + + + [string appendString:@"BEGIN DATA;\r"]; + + NSRange range = NSMakeRange(0, [[inSequences sequenceAt:0] length]); + if( [attrs objectForKey:MFSequenceWriterRange] ){ + range = [[attrs objectForKey:MFSequenceWriterRange]rangeValue]; + } + else if( [[attrs objectForKey:MFSequenceWriterIgnoreLeadingGaps]boolValue] ){ + NSUInteger index = [inSequences indexFirstNonGap]; + range.location = index; + range.length -= index; + } + + [string appendFormat:@"\tDimensions ntax=%ld nchar=%ld;\r", [inSequences size], range.length]; + if( [[inSequences sequenceAt:0] dataType] != nil ){ + if( [[inSequences sequenceAt:0] translated]){ + [string appendFormat:@"\tFormat datatype=protein gap=%@;\r", [[[inSequences sequenceAt:0] dataType]gap] ]; + } + else if( [[[inSequences sequenceAt:0] dataType] isKindOfClass:[MFNucleotide class]] ){ + [string appendFormat:@"\tFormat datatype=nucleotide gap=%@;\r", [[[inSequences sequenceAt:0] dataType]gap] ]; + } + else{ + [string appendFormat:@"\tFormat datatype=protein gap=%@;\r", [[[inSequences sequenceAt:0] dataType]gap] ]; + } + } + + [string appendString:@"\tMatrix\r"]; + + NSInteger maxlen = 0; + for (MFSequence *sequence in sequences) { + NSString *name = [sequence name]; + NSInteger len = [name length]; + NSRange whiteSpaceRange = [name rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]]; + if (whiteSpaceRange.location != NSNotFound) { + len+=2; + } + if(len > maxlen){ + maxlen = len; + } + } + + + for (MFSequence *sequence in sequences) { + NSString *name = [sequence name]; + NSInteger len = [name length]; + NSRange whiteSpaceRange = [name rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]]; + if (whiteSpaceRange.location != NSNotFound) { + [string appendFormat:@"\t\'%@\'",[sequence name]]; + len+=2; + } + else{ + [string appendFormat:@"\t%@",[sequence name]]; + } + for ( int i = 0; i < maxlen-len+2; i++) { + [string appendFormat:@" "]; + } + + [string appendFormat:@"%@\r",[sequence subSequenceWithRange:range]]; + + } + + [string appendString:@";\rEND;\r"]; + return string; +} + +#pragma mark Phylip + ++ (NSData *) dataPhylip:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs{ + NSString *string = [MFSequenceWriter stringPhylip:inSequences attributes:attrs]; + NSData *nsdata = [string dataUsingEncoding:NSUTF8StringEncoding]; + return nsdata; +} + +// sequential ++ (NSString *) stringPhylip:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs{ + NSMutableArray *sequences = [inSequences sequences]; + NSMutableString *string = [NSMutableString string]; + + NSRange range = NSMakeRange(0, [[inSequences sequenceAt:0] length]); + if( [attrs objectForKey:MFSequenceWriterRange] ){ + range = [[attrs objectForKey:MFSequenceWriterRange]rangeValue]; + } + else if( [[attrs objectForKey:MFSequenceWriterIgnoreLeadingGaps]boolValue] ){ + NSUInteger index = [inSequences indexFirstNonGap]; + range.location = index; + range.length -= index; + } + + [string appendFormat:@" %ld %ld\r", [inSequences size],range.length]; + + NSInteger maxlen = 0; + for (MFSequence *sequence in sequences) { + NSString *name = [sequence name]; + NSInteger len = [name length]; + NSRange whiteSpaceRange = [name rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]]; + if (whiteSpaceRange.location != NSNotFound) { + len+=2; + } + if(len > maxlen){ + maxlen = len; + } + } + + for (MFSequence *sequence in sequences) { + NSString *name = [sequence name]; + NSInteger len = [name length]; + NSRange whiteSpaceRange = [name rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]]; + if (whiteSpaceRange.location != NSNotFound) { + [string appendFormat:@"\"%@\"",[sequence name]]; + len+=2; + } + else{ + [string appendFormat:@"%@",[sequence name]]; + } + for ( int i = 0; i < maxlen-len+5; i++) { + [string appendFormat:@" "]; + } + + [string appendFormat:@"%@\r",[sequence subSequenceWithRange:range]]; + } + return string; +} + +#pragma mark MEGA + ++ (NSData *) dataMega:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs{ + NSString *string = [MFSequenceWriter stringMega:inSequences attributes:attrs]; + NSData *nsdata = [string dataUsingEncoding:NSUTF8StringEncoding]; + return nsdata; +} + ++ (NSString *) stringMega:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs{ + NSMutableArray *sequences = [inSequences sequences]; + NSMutableString *megaString = [NSMutableString string]; + + NSRange range = NSMakeRange(0, [[inSequences sequenceAt:0] length]); + if( [attrs objectForKey:MFSequenceWriterRange] ){ + range = [[attrs objectForKey:MFSequenceWriterRange]rangeValue]; + } + else if( [[attrs objectForKey:MFSequenceWriterIgnoreLeadingGaps]boolValue] ){ + NSUInteger index = [inSequences indexFirstNonGap]; + range.location = index; + range.length -= index; + } + + [megaString appendString:@"#mega\r"]; + + for (MFSequence *sequence in sequences) { + [megaString appendFormat:@"#%@\r",[sequence name]]; + [megaString appendFormat:@"%@\r",[sequence subSequenceWithRange:range]]; + } + return megaString; +} + +#pragma mark Clustal + ++ (NSData *) dataClustal:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs{ + NSString *string = [MFSequenceWriter stringClustal:inSequences attributes:attrs]; + NSData *nsdata = [string dataUsingEncoding:NSUTF8StringEncoding]; + return nsdata; +} + +// sequential ++ (NSString *) stringClustal:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs{ + NSMutableArray *sequences = [inSequences sequences]; + NSMutableString *string = [NSMutableString string]; + + NSRange alignmentRange = NSMakeRange(0, [[inSequences sequenceAt:0] length]); + if( [attrs objectForKey:MFSequenceWriterRange] ){ + alignmentRange = [[attrs objectForKey:MFSequenceWriterRange]rangeValue]; + } + else if( [[attrs objectForKey:MFSequenceWriterIgnoreLeadingGaps]boolValue] ){ + NSUInteger index = [inSequences indexFirstNonGap]; + alignmentRange.location = index; + alignmentRange.length -= index; + } + + NSUInteger len = alignmentRange.length; + + [string appendFormat:@"CLUSTAL W Generated by Seqotron ntax %tu nchar %tu\r\r", [inSequences size], len]; + + NSInteger maxlenName = 0; + for (MFSequence *sequence in sequences) { + NSString *name = [[sequence name]stringByReplacingOccurrencesOfString:@" " withString:@""]; + NSInteger len = [name length]; + if(len > maxlenName){ + maxlenName = len; + } + } + + + for ( NSUInteger i = 0; i < len; i+=60) { + for (MFSequence *sequence in sequences) { + NSString *name = [[sequence name]stringByReplacingOccurrencesOfString:@" " withString:@""]; + [string appendString:name]; + + for ( int i = 0; i < maxlenName-[name length]+5; i++) { + [string appendFormat:@" "]; + } + NSString *seq = [sequence subSequenceWithRange:alignmentRange]; + NSRange range = NSMakeRange(i, 60); + if( i + 60 > [seq length]){ + range.length = [seq length] - i; + } + + [string appendString:[seq substringWithRange:range ] ]; + + [string appendFormat:@" %tu\r", i+range.length]; + } + } + return string; +} + +#pragma mark GDE Flat + ++ (void)writeGDE:(MFSequenceSet *) inSequences toFile:(NSString *)file attributes:(NSDictionary*)attrs{ + NSString *str = [MFSequenceWriter stringGDE:inSequences attributes:attrs]; + [str writeToFile:file atomically:NO encoding:NSUTF8StringEncoding error:nil]; +} + ++ (NSData *) dataGDE:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs{ + NSString *string = [MFSequenceWriter stringGDE:inSequences attributes:attrs]; + NSData *nsdata = [string dataUsingEncoding:NSUTF8StringEncoding]; + return nsdata; +} + ++ (NSString *) stringGDE:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs{ + NSMutableArray *sequences = [inSequences sequences]; + NSMutableString *str = [NSMutableString string]; + + NSRange range = NSMakeRange(0, [[inSequences sequenceAt:0] length]); + if( [attrs objectForKey:MFSequenceWriterRange] ){ + range = [[attrs objectForKey:MFSequenceWriterRange]rangeValue]; + } + else if( [[attrs objectForKey:MFSequenceWriterIgnoreLeadingGaps]boolValue] ){ + NSUInteger index = [inSequences indexFirstNonGap]; + range.location = index; + range.length -= index; + } + + for (MFSequence *sequence in sequences) { + [str appendFormat:@"#%@\r",[sequence name]]; + [str appendFormat:@"%@\r",[sequence subSequenceWithRange:range]]; + } + return str; +} + +#pragma mark NBRF + ++ (NSData *) dataNBRF:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs{ + NSString *string = [MFSequenceWriter stringNBRF:inSequences attributes:attrs]; + NSData *nsdata = [string dataUsingEncoding:NSUTF8StringEncoding]; + return nsdata; +} + ++ (NSString *) stringNBRF:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs{ + NSMutableArray *sequences = [inSequences sequences]; + NSMutableString *nbrfString = [NSMutableString string]; + + NSRange range = NSMakeRange(0, [[inSequences sequenceAt:0] length]); + if( [attrs objectForKey:MFSequenceWriterRange] ){ + range = [[attrs objectForKey:MFSequenceWriterRange]rangeValue]; + } + else if( [[attrs objectForKey:MFSequenceWriterIgnoreLeadingGaps]boolValue] ){ + NSUInteger index = [inSequences indexFirstNonGap]; + range.location = index; + range.length -= index; + } + + NSString *type = @"P1"; + if( [[[sequences objectAtIndex:0] dataType] isKindOfClass:[MFNucleotide class]]){ + type = @"D1"; + } + + for (MFSequence *sequence in sequences) { + [nbrfString appendFormat:@">%@;%@\r",type, [sequence name]]; + [nbrfString appendFormat:@"%@\r",[sequence name]]; + [nbrfString appendFormat:@"%@*\r",[sequence subSequenceWithRange:range]]; + } + return nbrfString; +} + + +#pragma mark Stockholm + ++ (NSData *) dataStockholm:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs{ + NSString *string = [MFSequenceWriter stringStockholm:inSequences attributes:attrs]; + NSData *nsdata = [string dataUsingEncoding:NSUTF8StringEncoding]; + return nsdata; +} + ++ (NSString *) stringStockholm:(MFSequenceSet *) inSequences attributes:(NSDictionary*)attrs{ + NSMutableArray *sequences = [inSequences sequences]; + NSMutableString *stockholmString = [NSMutableString string]; + + NSUInteger spacing = 5; + NSUInteger maxLength = 0; + for (MFSequence *sequence in sequences) { + if( [[sequence name]length] > maxLength ){ + maxLength = [[sequence name] length]; + } + } + [stockholmString appendString:@"# STOCKHOLM 1.0"]; + for (MFSequence *sequence in sequences) { + NSString *name = [[[sequence name]componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]componentsJoinedByString:@"_"]; + [stockholmString appendFormat:@"%@", name]; + for ( NSUInteger i = 0; i < maxLength - [name length] + spacing ; i++ ) { + [stockholmString appendString:@" "]; + } + [stockholmString appendFormat:@"%@\r", [sequence sequenceString]]; + } + [stockholmString appendString:@"//"]; + return stockholmString; +} +@end diff --git a/Seqotron/MFSequencesView.h b/Seqotron/MFSequencesView.h new file mode 100755 index 0000000..4b9cb14 --- /dev/null +++ b/Seqotron/MFSequencesView.h @@ -0,0 +1,118 @@ +// +// MFSequencesView.h +// Seqotron +// +// Created by Mathieu Fourment on 25/01/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#include "MFDefines.h" + +#import "MFAbstractSequencesView.h" + +extern NSString *MFSequenceViewForegroundColorBindingName; +extern NSString *MFSequenceViewBackgroundColorBindingName; + +extern NSString *MFTranslationDidChangeNotification; + +@interface MFSequencesView : MFAbstractSequencesView{ + @public + IBOutlet id delegate; + + + @private + NSPoint dragStartPoint; + NSPoint dragPoint; + NSPoint dragEndPoint; + + BOOL firstDrag; + BOOL secondaryDrag; + BOOL draggedRight; + BOOL dragging; + BOOL _pingpong; + + NSMutableDictionary *_attsDict; + NSDictionary *_foregroundColor; + NSDictionary *_backgroundColor; + + BOOL _drawBackground; + NSColor *_canvasColor; + + + NSMutableIndexSet *_siteSelectionIndexes; + MF2DRange _rangeSelection; + NSColor *_marqueeColor; + CGFloat _marqueeAlpha; + + BOOL _isTranslated; + NSUInteger _maximumNumberOfSites; + + NSColor *_unknownBackgroundColor; + NSMutableAttributedString *_mutableAttributedString; + + NSRange _fakeInsertionRange; + NSRange _fakeDeletionRange; + + NSTrackingArea * trackingArea; + NSString *_mouseOverSequence; + NSInteger _mouseOverSequenceIndex; + NSInteger _mouseOverSiteIndex; + + NSRect _caretRect; + BOOL _caretBlinkActive; +} + +@property (readwrite) MF2DRange rangeSelection; +@property (readwrite, copy)NSString *mouseOverSequence; + +@property (readwrite, assign) id delegate; // Don't retain or copy + +-(void)updateFrameSize; + +-(void)setForegroundColor:(NSDictionary*)colors; + +-(NSDictionary*)foregroundColor; + +-(void)setBackgroundColor:(NSDictionary*)colors; + +-(NSDictionary*)backgroundColor; + +@end + +@interface NSObject (MFSequencesViewDelegate) + +- (void)sequencesView:(MFSequencesView *)inSequenceView insertGaps:(NSUInteger)nGaps inSequenceRange:(NSRange)sequenceRange atIndex:(NSUInteger)index; + +- (void)sequencesView:(MFSequencesView *)inSequenceView deleteGaps:(NSRange)gapRange inSequenceRange:(NSRange)sequenceRange; + +- (void)sequencesView:(MFSequencesView *)inSequenceView insertSites:(NSArray*)sites atIndex:(NSUInteger)index inSequenceRange:(NSRange)sequenceRange; + +- (void)sequencesView:(MFSequencesView *)inSequenceView removeSitesInRange:(NSRange)siteRange inSequenceRange:(NSRange)sequenceRange; + +- (void)sequencesView:(MFSequencesView *)inSequenceView insertSites:(NSArray*)sites atIndexes:(NSIndexSet*)indexes inSequenceRange:(NSRange)sequenceRange; + +- (void)sequencesView:(MFSequencesView *)inSequenceView removeSitesAtIndexes:(NSIndexSet*)siteIndexes inSequenceRange:(NSRange)sequenceRange; + +- (void)sequencesView:(MFSequencesView *)inSequenceView slideSitesLeftInRange:(NSRange)siteRange inSequenceRange:(NSRange)sequenceRange by:(NSUInteger)amount; + +- (void)sequencesView:(MFSequencesView *)inSequenceView slideSitesRightInRange:(NSRange)siteRange inSequenceRange:(NSRange)sequenceRange by:(NSUInteger)amount; + +- (void)sequencesView:(MFSequencesView *)inSequenceView replaceResiduesInRange:(NSRange)range atSequenceIndex:(NSUInteger)sequenceIndex withString:(NSString*)string; +@end diff --git a/Seqotron/MFSequencesView.m b/Seqotron/MFSequencesView.m new file mode 100755 index 0000000..f56581a --- /dev/null +++ b/Seqotron/MFSequencesView.m @@ -0,0 +1,1212 @@ +// +// MFSequencesView.m +// Seqotron +// +// Created by Mathieu Fourment on 25/01/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFSequencesView.h" + +#import "MFDefines.h" + +#import "MFSequence.h" +#import "MFSequence+MFSequenceDrawing.h" +#import "MFSequenceUtils.h" +#import "MFNucleotide.h" + + +#import "MFSequenceSet.h" +#import "MFSequenceReader.h" +#import "MFSequenceWriter.h" + +NSString *MFSequenceViewForegroundColorBindingName = @"foregroundColor"; +NSString *MFSequenceViewBackgroundColorBindingName = @"backgroundColor"; + +NSString *MFTranslationDidChangeNotification = @"MFTranslationDidChange"; + +@implementation MFSequencesView + +@synthesize delegate; +@synthesize rangeSelection = _rangeSelection; +@synthesize mouseOverSequence = _mouseOverSequence; + +- (id)initWithFrame:(NSRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + } + return self; +} + +-(void)awakeFromNib { + [super awakeFromNib]; + NSLog(@"awakeFromNib SequenceView"); + + _attsDict = [[NSMutableDictionary alloc] init]; + [_attsDict setObject:[NSFont fontWithName:_fontName size:_fontSize] forKey:NSFontAttributeName]; + + _foregroundColor = nil; + _backgroundColor = nil; + + _drawBackground = NO; + _canvasColor = [NSColor whiteColor]; + + _unknownBackgroundColor = [NSColor grayColor]; + _marqueeAlpha = 0.3; + _marqueeColor = [NSColor redColor];//[[NSColor redColor]colorWithAlphaComponent:0.3]; + + _siteSelectionIndexes = [[NSMutableIndexSet alloc] init]; + _rangeSelection = MFMakeEmpty2DRange(); + + _isTranslated = NO; + + _maximumNumberOfSites = 0; + + _mutableAttributedString = [[NSMutableAttributedString alloc]init]; + _fakeInsertionRange = NSMakeRange(0, 0); + _fakeDeletionRange = NSMakeRange(0, 0); + + [self createTrackingArea]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(translationDidChange:) name:MFTranslationDidChangeNotification object:nil]; + +} + +-(void)dealloc{ + NSLog(@"MFSequenceView dealloc"); + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [_mutableAttributedString release]; + [_attsDict release]; + [_foregroundColor release]; + [_backgroundColor release]; + [_siteSelectionIndexes release]; + [trackingArea release]; + [_mouseOverSequence release]; + [super dealloc]; +} + +#pragma mark *** Drawing Methods *** + +- (void)drawRect:(NSRect)dirtyRect { + + // Draw background + [[NSColor whiteColor] set]; + NSRectFill(dirtyRect); + + NSArray *sequences = [self sequences]; + NSUInteger numberOfSequences = [sequences count]; + + if(numberOfSequences == 0 ){ + return; + } + + [_attsDict setObject:[NSFont fontWithName:_fontName size:_fontSize] forKey:NSFontAttributeName]; + + _maximumNumberOfSites = [self numberOfSites]; + + [self updateFrameSize]; + + [self drawSequences: dirtyRect]; + + if( [_backgroundColor count] == 0 ){ + [self drawSelection: dirtyRect]; + } + else { + [self drawSelection2:dirtyRect]; + } + +} + +-(NSRect)selectionRect{ + NSRect rectSelection; + + rectSelection.origin.x = _rangeSelection.x.location * _residueWidth; + rectSelection.size.width = _rangeSelection.x.length * _residueWidth; + + rectSelection.origin.y = _rangeSelection.y.location * (_residueHeight+_rowSpacing)+_rowSpacing/2; + rectSelection.size.height = _rangeSelection.y.length * (_residueHeight+_rowSpacing); + return rectSelection; +} + +-(void)drawSelection:(NSRect)dirtyRect{ + + if ( [[self selectedSequences] count] != 0 ) { + + } + else if( [_siteSelectionIndexes count] != 0 ){ + + } + else { + + if( _rangeSelection.x.length != 0 || _rangeSelection.y.length != 0 ){ + [[_marqueeColor colorWithAlphaComponent:_marqueeAlpha] set]; + NSRect rectSelection = [self selectionRect]; + + NSRectFillUsingOperation(rectSelection, NSCompositeSourceAtop); + } + + } +} + +// This method draws the selection on top of the regular alignment. +// It would be better to draw selection first and then draw the rest of the alignment around it. +-(void)drawSelection2:(NSRect)dirtyRect{ + + if ( [[self selectedSequences] count] != 0 ) { + + } + else if( [_siteSelectionIndexes count] != 0 ){ + + } + else { + + if( _rangeSelection.x.length != 0 || _rangeSelection.y.length != 0 ){ + + + NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc]init]; + + NSRect rectSelection = [self selectionRect]; + [[NSColor redColor] set]; + CGRect intersectRect = CGRectIntersection(rectSelection, dirtyRect); + NSRectFill(intersectRect); + + NSRange siteRange; + NSRange seqRange; + + [self getSelectionSequenceRange: &seqRange siteRange:&siteRange inRect:dirtyRect]; + + if( _fakeDeletionRange.length != 0 ){ + siteRange.location += _fakeDeletionRange.length; + } + else if( _fakeInsertionRange.length != 0 ){ + + siteRange.location -= _fakeInsertionRange.length; + } + + + for ( NSUInteger i = seqRange.location; i < seqRange.location+seqRange.length; i++ ) { + NSPoint point = NSMakePoint(siteRange.location*_residueWidth, (i * (_residueHeight + _rowSpacing)) - _lineGap + _rowSpacing); + + if( _fakeDeletionRange.length != 0 ){ + point.x -= _fakeDeletionRange.length *_residueWidth; + } + else if( _fakeInsertionRange.length != 0 ){ + point.x += _fakeInsertionRange.length *_residueWidth; + } + + MFSequence *sequence = [[self sequences] objectAtIndex:i]; + + + NSString *seqString = [sequence subSequenceWithRange:siteRange]; + [[mutableAttributedString mutableString] setString:seqString]; + [mutableAttributedString addAttribute:NSFontAttributeName value:[NSFont fontWithName:_fontName size:_fontSize] range:NSMakeRange(0, [seqString length])]; + [mutableAttributedString addAttribute:NSForegroundColorAttributeName value:[NSColor whiteColor] range:NSMakeRange(0, [seqString length])]; + + [mutableAttributedString drawAtPoint:point]; + } + [mutableAttributedString release]; + } + + } +} + +-(void)getSelectionSequenceRange:(NSRange*)seqRange siteRange:(NSRange*)siteRange inRect:(NSRect)dirtyRect{ + + NSUInteger numberOfSequences = [[self sequences] count]; + + NSRect rectSelection = [self selectionRect]; + CGRect intersectRect = CGRectIntersection(rectSelection, dirtyRect); + + + siteRange->length = MIN(ceil(intersectRect.size.width/_residueWidth), _maximumNumberOfSites); + seqRange->length = MIN(ceil(intersectRect.size.height/(_residueHeight + _rowSpacing)), numberOfSequences); + + siteRange->location = intersectRect.origin.x/_residueWidth; + seqRange->location = intersectRect.origin.y/(_residueHeight + _rowSpacing); + + if( siteRange->location + siteRange->length > _maximumNumberOfSites ){ + //siteRange.length -= siteRange.location + siteRange.length - _maximumNumberOfSites; + } + if( seqRange->location + seqRange->length > numberOfSequences ){ + seqRange->length -= seqRange->location + seqRange->length - numberOfSequences; + } + +} + +-(void)drawSequences:(NSRect)dirtyRect{ + + NSUInteger numberOfSequences = [[self sequences] count]; + + // These NSRanges represent the number of rows (sequences) and (columns) sites that can be displayed in the NSScrollView (without copying) + // Not including the insertion + NSRange siteRange; + NSRange seqRange; + + siteRange.length = ceil(dirtyRect.size.width/_residueWidth); + seqRange.length = ceil(dirtyRect.size.height/(_residueHeight + _rowSpacing)); + + siteRange.location = dirtyRect.origin.x/_residueWidth; + seqRange.location = dirtyRect.origin.y/(_residueHeight + _rowSpacing); + + // Outside of bounds. When we drag a large region + if( siteRange.location >= _maximumNumberOfSites ){ + siteRange.length = 0; + } + else if( siteRange.location + siteRange.length > _maximumNumberOfSites ){ + siteRange.length = _maximumNumberOfSites - siteRange.location; + } + if( seqRange.location + seqRange.length > numberOfSequences ){ + seqRange.length -= seqRange.location + seqRange.length - numberOfSequences; + } + + if( _drawBackground ){ + [self drawBackgroundForSequencesinRange:seqRange inSiteRange:siteRange inRect:dirtyRect]; + } + + [self drawForegroudForSequencesinRange:seqRange inSiteRange:siteRange inRect:dirtyRect]; +} + + +-(void)drawForegroudForSequencesinRange:(NSRange)seqRange inSiteRange:(NSRange)siteRange inRect:(NSRect)dirty{ + + // These NSRanges represent the width and height of the view in terms of number of sites and sequences, respectively + NSRange siteViewRange; + NSRange seqViewRange; + + siteViewRange.length = ceil(dirty.size.width/_residueWidth); + seqViewRange.length = ceil(dirty.size.height/(_residueHeight + _rowSpacing)); + + siteViewRange.location = dirty.origin.x/_residueWidth; + seqViewRange.location = dirty.origin.y/(_residueHeight + _rowSpacing); + + NSArray *sequences = [self sequences]; + NSUInteger alignmentLength = [[sequences objectAtIndex: 0] length]; + + NSUInteger sitePos = siteRange.location; + NSUInteger nCol = siteRange.length; + + NSUInteger nSiteLeftVisible = 0; + NSUInteger nGapVisible = 0; + NSUInteger nSiteRightVisible = 0; + + // number of residues visible after the insertion + NSInteger nResidues = 0; + // We have an insertion from dragging right or left + if(_fakeInsertionRange.length > 0 ){ + + // there are some sites + if( _fakeInsertionRange.location > sitePos ){ + nSiteLeftVisible = _fakeInsertionRange.location-sitePos; + nGapVisible = _fakeInsertionRange.length; + } + // it starts with fake gaps + else { + // when we drag left and it starts with gaps there can be no gaps visible + if( _fakeInsertionRange.location + _fakeInsertionRange.length > sitePos ){ + nGapVisible = _fakeInsertionRange.location + _fakeInsertionRange.length - sitePos; + } + } + + nSiteRightVisible = MIN(alignmentLength - _fakeInsertionRange.location, siteViewRange.length - nGapVisible - nSiteLeftVisible); + + + + NSUInteger len = _fakeInsertionRange.length; + // calculate the number of visible residues after the insertion + if( nCol+sitePos > _fakeInsertionRange.location + _fakeInsertionRange.length){ + nResidues = nCol+sitePos - _fakeInsertionRange.location - _fakeInsertionRange.length; + // substract the number of visible residues (nResidues) from the number of visible sites (nCol) + // since the first visible site is a gap + if( _fakeInsertionRange.location < sitePos ){ + len = nCol - nResidues; + } + } + // When we drag left and we hit the scrollview the number of visible gaps can be 0 + if( nResidues < nCol){ + [[_mutableAttributedString mutableString] setString:@"-"]; + for ( int i = 1; i < len; i++ ) { + [[_mutableAttributedString mutableString]appendString:@"-" ]; + } + [_mutableAttributedString addAttribute:NSFontAttributeName value:[_attsDict objectForKey: NSFontAttributeName] range:NSMakeRange(0, len)]; + [_mutableAttributedString addAttribute:NSForegroundColorAttributeName value:[NSColor grayColor] range:NSMakeRange(0, len)]; + } + + + } + + NSPoint point; + + for ( NSUInteger i = seqRange.location; i < seqRange.location+seqRange.length; i++ ) { + + MFSequence *sequence = [sequences objectAtIndex: i]; + + point.x = sitePos * _residueWidth; + point.y = (i * (_residueHeight + _rowSpacing)) - _lineGap + _rowSpacing; + + if( _fakeInsertionRange.length != 0 && _fakeDeletionRange.length != 0 && NSLocationInRange(i, _rangeSelection.y) ){ + // Draw sequence up to deletion + if( _fakeDeletionRange.location > sitePos){ + [sequence drawAtPoint:point withRange:NSMakeRange(sitePos, _fakeDeletionRange.location-sitePos) withAttributes:_attsDict]; + } + + // Draw the selection that we used to drag + point.x += (_fakeDeletionRange.location-sitePos)*_residueWidth; + [sequence drawAtPoint:point withRange:NSMakeRange(_fakeDeletionRange.location+_fakeDeletionRange.length, _rangeSelection.x.length) withAttributes:_attsDict]; + + // Draw the insertion + point.x += _rangeSelection.x.length*_residueWidth; + [_mutableAttributedString drawAtPoint:point]; + + // Draw the rest + if( _fakeDeletionRange.location+_fakeDeletionRange.length+_rangeSelection.x.length < [sequence length]){ + point.x += _fakeInsertionRange.length*_residueWidth; + [sequence drawAtPoint:point withRange:NSMakeRange(_fakeDeletionRange.location+_fakeDeletionRange.length+_rangeSelection.x.length, nResidues) withAttributes:_attsDict]; + } + + } + else if( _fakeInsertionRange.length != 0 && NSLocationInRange(i, _rangeSelection.y) ){ + + if( nSiteLeftVisible > 0 ){ + [sequence drawAtPoint:point withRange:NSMakeRange(sitePos, nSiteLeftVisible) withAttributes:_attsDict]; + point.x += nSiteLeftVisible * _residueWidth; + } + + if( nGapVisible > 0 ){ + [_mutableAttributedString drawAtPoint:point]; + } + + if( nSiteRightVisible > 0 ){ + point.x += nGapVisible * _residueWidth; + [sequence drawAtPoint:point withRange:NSMakeRange(_fakeInsertionRange.location, nSiteRightVisible) withAttributes:_attsDict]; + } + + } + else if( _fakeDeletionRange.length != 0 && NSLocationInRange(i, _rangeSelection.y) ){ + NSUInteger n = 0; // number of sites before the deletion + if( _fakeDeletionRange.location > sitePos ){ + [sequence drawAtPoint:point withRange:NSMakeRange(sitePos, _fakeDeletionRange.location-sitePos) withAttributes:_attsDict]; + n = _fakeDeletionRange.location-sitePos; + } + point.x += (_fakeDeletionRange.location-sitePos)*_residueWidth; + [sequence drawAtPoint:point withRange:NSMakeRange(_fakeDeletionRange.location+_fakeDeletionRange.length, nCol-(n+_fakeDeletionRange.length)) withAttributes:_attsDict]; + } + else { + if( nCol > 0){ + NSUInteger nRes = nCol; + if( nCol < siteViewRange.length && _fakeDeletionRange.length != 0 && _fakeInsertionRange.length == 0 ){ + nRes -= _fakeDeletionRange.length; + } + [sequence drawAtPoint:point withRange:NSMakeRange(sitePos, nRes) withAttributes:_attsDict]; + } + // The sequence is too short and there is an insertion + if( nCol < siteViewRange.length && _fakeInsertionRange.length != 0 ){ + NSUInteger nGaps = MIN(_fakeInsertionRange.length, siteViewRange.length-nCol); + point.x += nCol*_residueWidth; + [self drawForegroundGaps:nGaps atPoint:point]; + } + } + } +} + +-(void)drawBackgroundForSequencesinRange:(NSRange)seqRange inSiteRange:(NSRange)siteRange inRect:(NSRect)dirty{ + + // These NSRanges represent the width and height of the view in terms of number of sites and sequences, respectively + NSRange siteViewRange; + NSRange seqViewRange; + + siteViewRange.length = ceil(dirty.size.width/_residueWidth); + seqViewRange.length = ceil(dirty.size.height/(_residueHeight + _rowSpacing)); + + siteViewRange.location = dirty.origin.x/_residueWidth; + seqViewRange.location = dirty.origin.y/(_residueHeight + _rowSpacing); + + NSArray *sequences = [self sequences]; + NSUInteger alignmentLength = [[sequences objectAtIndex: 0] length]; + + NSUInteger sitePos = siteRange.location; + NSUInteger nCol = siteRange.length; + + NSUInteger nSiteLeftVisible = 0; + NSUInteger nGapVisible = 0; + NSUInteger nSiteRightVisible = 0; + + // number of residues visible after the insertion + NSInteger nResidues = 0; + // We have an insertion from dragging right or left + if(_fakeInsertionRange.length > 0 ){ + + // there are some sites + if( _fakeInsertionRange.location > sitePos ){ + nSiteLeftVisible = _fakeInsertionRange.location-sitePos; + nGapVisible = _fakeInsertionRange.length; + } + // it starts with fake gaps + else { + // when we drag left and it starts with gaps there can be no gaps visible + if( _fakeInsertionRange.location + _fakeInsertionRange.length > sitePos ){ + nGapVisible = _fakeInsertionRange.location + _fakeInsertionRange.length - sitePos; + } + } + + nSiteRightVisible = MIN(alignmentLength - _fakeInsertionRange.location, siteViewRange.length - nGapVisible - nSiteLeftVisible); + + + + NSUInteger len = _fakeInsertionRange.length; + // calculate the number of visible residues after the insertion + if( nCol+sitePos > _fakeInsertionRange.location + _fakeInsertionRange.length){ + nResidues = nCol+sitePos - _fakeInsertionRange.location - _fakeInsertionRange.length; + // substract the number of visible residues (nResidues) from the number of visible sites (nCol) + // since the first visible site is a gap + if( _fakeInsertionRange.location < sitePos ){ + len = nCol - nResidues; + } + } + // When we drag left and we hit the scrollview the number of visible gaps can be 0 + if( nResidues < nCol){ + [[_mutableAttributedString mutableString] setString:@"-"]; + for ( int i = 1; i < len; i++ ) { + [[_mutableAttributedString mutableString]appendString:@"-" ]; + } + [_mutableAttributedString addAttribute:NSFontAttributeName value:[_attsDict objectForKey: NSFontAttributeName] range:NSMakeRange(0, len)]; + [_mutableAttributedString addAttribute:NSForegroundColorAttributeName value:[NSColor grayColor] range:NSMakeRange(0, len)]; + } + + + } + + NSPoint point; + + for ( NSUInteger i = seqRange.location; i < seqRange.location+seqRange.length; i++ ) { + + MFSequence *sequence = [sequences objectAtIndex: i]; + + point.x = sitePos * _residueWidth; + point.y = (i+1)*_rowSpacing+ i*_residueHeight - _rowSpacing/2; + + if( _fakeInsertionRange.length != 0 && _fakeDeletionRange.length != 0 && NSLocationInRange(i, _rangeSelection.y) ){ + + // Draw sequence up to deletion + if( _fakeDeletionRange.location > sitePos){ + [self drawBackgroundForSequence:sequence InRange:NSMakeRange(sitePos, _fakeDeletionRange.location-sitePos) atPoint:point]; + } + + // Draw the selection that we used to drag + point.x += (_fakeDeletionRange.location-sitePos)*_residueWidth; + [self drawBackgroundForSequence:sequence InRange:NSMakeRange(_fakeDeletionRange.location+_fakeDeletionRange.length, _rangeSelection.x.length) atPoint:point]; + + // Draw the insertion + point.x += _rangeSelection.x.length*_residueWidth; + [self drawBackgroundGaps:_fakeInsertionRange.length atPoint:point]; + + // Draw the rest + if( _fakeDeletionRange.location+_fakeDeletionRange.length+_rangeSelection.x.length < [sequence length]){ + point.x += _fakeInsertionRange.length*_residueWidth; + [self drawBackgroundForSequence:sequence InRange:NSMakeRange(_fakeDeletionRange.location+_fakeDeletionRange.length+_rangeSelection.x.length, nResidues) atPoint:point]; + } + + } + else if( _fakeInsertionRange.length != 0 && NSLocationInRange(i, _rangeSelection.y) ){ + + if( nSiteLeftVisible > 0 ){ + [self drawBackgroundForSequence:sequence InRange:NSMakeRange(sitePos, nSiteLeftVisible) atPoint:point]; + point.x += nSiteLeftVisible * _residueWidth; + } + + if( nGapVisible > 0 ){ + [self drawBackgroundGaps:_fakeInsertionRange.length atPoint:point]; + } + + if( nSiteRightVisible > 0 ){ + point.x += nGapVisible * _residueWidth; + [self drawBackgroundForSequence:sequence InRange:NSMakeRange(_fakeInsertionRange.location, nSiteRightVisible) atPoint:point]; + } + + } + else if( _fakeDeletionRange.length != 0 && NSLocationInRange(i, _rangeSelection.y) ){ + NSUInteger n = 0; // number of sites before the deletion + if( _fakeDeletionRange.location > sitePos ){ + [self drawBackgroundForSequence:sequence InRange:NSMakeRange(sitePos, _fakeDeletionRange.location-sitePos) atPoint:point]; + n = _fakeDeletionRange.location-sitePos; + } + point.x += (_fakeDeletionRange.location-sitePos)*_residueWidth; + [self drawBackgroundForSequence:sequence InRange:NSMakeRange(_fakeInsertionRange.location, nResidues) atPoint:point]; + } + else { + if( nCol > 0){ + NSUInteger nRes = nCol; + if( nCol < siteViewRange.length && _fakeDeletionRange.length != 0 && _fakeInsertionRange.length == 0 ){ + nRes -= _fakeDeletionRange.length; + } + [self drawBackgroundForSequence:sequence InRange:NSMakeRange(sitePos, nRes) atPoint:point]; + } + // The sequence is too short and there is an insertion + if( nCol < siteViewRange.length && _fakeInsertionRange.length != 0 ){ + NSUInteger nGaps = MIN(_fakeInsertionRange.length, siteViewRange.length-nCol); + point.x += nCol*_residueWidth; + [self drawBackgroundGaps:nGaps atPoint:point]; + } + } + } +} + + +-(void)drawBackgroundForSequence:(MFSequence*)sequence InRange:(NSRange)siteRange atPoint:(NSPoint)point{ + + NSRect rect = NSMakeRect(point.x, point.y, _residueWidth, _residueHeight+_rowSpacing); + + for ( NSUInteger j = siteRange.location; j < siteRange.location+siteRange.length; j++ ) { + NSString *residue = [sequence subSequenceWithRange:NSMakeRange(j, 1)]; + NSColor *background; + + if( [_backgroundColor objectForKey:[residue uppercaseString]] != nil ){ + background = [_backgroundColor objectForKey: [residue uppercaseString] ]; + } + else if( [_backgroundColor objectForKey:@"?"] != nil ){ + background = [_backgroundColor objectForKey: @"?" ]; + } + else{ + background = [NSColor grayColor]; + } + [background set]; + NSRectFill(rect); + rect.origin.x += _residueWidth; + } +} + +-(void)drawBackgroundGaps:(NSUInteger)nGaps atPoint:(NSPoint)point{ + + NSRect rect = NSMakeRect(point.x, point.y, _residueWidth*nGaps, _residueHeight+_rowSpacing); + NSColor *background; + + if( [_backgroundColor objectForKey:@"-"] != nil ){ + background = [_backgroundColor objectForKey: @"-" ]; + } + else{ + background = [NSColor grayColor]; + } + [background set]; + NSRectFill(rect); +} + +-(void)drawForegroundGaps:(NSUInteger)nGaps atPoint:(NSPoint)point{ + NSColor *foreground = [NSColor grayColor]; + + + NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc]init]; + [[mutableAttributedString mutableString] setString:@"-"]; + for ( int i = 1; i < nGaps; i++ ) { + [[mutableAttributedString mutableString]appendString:@"-" ]; + } + [mutableAttributedString addAttribute:NSFontAttributeName value:[_attsDict objectForKey: NSFontAttributeName] range:NSMakeRange(0, nGaps)]; + [mutableAttributedString addAttribute:NSForegroundColorAttributeName value:foreground range:NSMakeRange(0, nGaps)]; + + [mutableAttributedString drawAtPoint:point]; + [mutableAttributedString release]; +} + +#pragma mark *** Mouse Event Handling *** + +- (void)mouseDown:(NSEvent *)anEvent { + + _fakeInsertionRange = NSMakeRange(0, 0); + _fakeDeletionRange = NSMakeRange(0, 0); + + if( [[self sequences] count] == 0 ) return; + + NSPoint location = [self convertPoint:[anEvent locationInWindow] fromView:nil]; + + dragging = NO; + + // There is something already selected + if ( !MFIsEmpty2DRange(_rangeSelection) ) { + NSUInteger seq = location.y/(_residueHeight+_rowSpacing); + NSUInteger site = location.x/_residueWidth; + + // We clicked inside the selection + if( MFLocationsin2DRange(site, seq, _rangeSelection) ){ + + // Moving blocks of gaps is not allowed + MF2DRange range = _rangeSelection; + NSUInteger i = range.y.location; + for ( ; i < range.y.location+range.y.length; i++ ) { + MFSequence *sequence = [[self sequences ]objectAtIndex: i]; + NSUInteger j = range.x.location; + for ( ; j < range.x.location+range.x.length; j++ ) { + if([sequence residueAt:j] != '-') break; + } + if(j != range.x.location+range.x.length)break; + } + + // The block is not just gaps + if(i != range.y.location+range.y.length){ + if(firstDrag) secondaryDrag = YES; + firstDrag = YES; + dragging = YES; + _fakeInsertionRange.location = _rangeSelection.x.location; + _fakeDeletionRange.location = _rangeSelection.x.location; + } + } + else{ + // Expand selection when shift is pressed + if( [anEvent modifierFlags] & NSShiftKeyMask ){ + if( site >= _rangeSelection.x.location + _rangeSelection.x.length){ + _rangeSelection.x.length = site - _rangeSelection.x.location + 1; + } + else { + _rangeSelection.x.length += _rangeSelection.x.location - site; + _rangeSelection.x.location = site; + } + + if( seq >= _rangeSelection.y.location + _rangeSelection.y.length){ + _rangeSelection.y.length = seq - _rangeSelection.y.location + 1; + } + else { + _rangeSelection.y.length += _rangeSelection.y.location - seq; + _rangeSelection.y.location = seq; + } + } + firstDrag = secondaryDrag = NO; + } + } + + if( !dragging ){ + if( !([anEvent modifierFlags] & NSShiftKeyMask) ){ + _rangeSelection = MFMakeEmpty2DRange(); + if( [[self selectionIndexes] count] > 0 ){ + [self changeSelectionIndexes:[NSIndexSet indexSet] ]; + } + [_siteSelectionIndexes removeAllIndexes]; + firstDrag = secondaryDrag = NO; + } + } + + _pingpong = NO; + + // should empty site and sequence selection sets + + dragStartPoint = location; + [self setNeedsDisplay:YES]; +} + + +// Drag repositions any selected shapes +- (void)mouseDragged:(NSEvent *)anEvent { + + if( [[self sequences] count] == 0 ) return; + + dragPoint = [self convertPoint: [anEvent locationInWindow] fromView:nil]; + + // Not dragging and did not move enough + if ( !dragging && !secondaryDrag && fabs(dragPoint.x-dragStartPoint.x) < _residueWidth/4 && fabs(dragPoint.y-dragStartPoint.y) < _rowHeight/4 ) { + return; + } + + MF2DRange range = [self convertSelectionPoints]; + + if( dragging ){ + NSUInteger startResidue = dragStartPoint.x/_residueWidth; + NSUInteger endResidue = dragPoint.x/_residueWidth; + + // Moved right: insert gaps if needed + if( endResidue > startResidue){ + NSUInteger nGaps = endResidue - startResidue; + // change of direction without mouse up: shift right + if( _fakeDeletionRange.length != 0 ){ + NSUInteger allowedGaps = MIN(_fakeInsertionRange.length, nGaps); + _fakeInsertionRange.location += allowedGaps; + _fakeInsertionRange.length -= allowedGaps; + + _fakeDeletionRange.location += allowedGaps; + _fakeDeletionRange.length -= allowedGaps; + + _rangeSelection.x.location += allowedGaps; + } + // Avoid pushing the sequences + else if( _pingpong ){ + + } + else { + draggedRight = YES; + + NSArray *unselectedSequences = [self unselectedSequencesInRangeSelection]; + NSUInteger maxLengthNotSelected = [MFSequenceUtils maxLength:unselectedSequences]; + + // move the rangeSelection to its new location but we don't want create empty columns + //if( _rangeSelection.x.location < maxLengthNotSelected){ + nGaps = MIN(nGaps, maxLengthNotSelected-_rangeSelection.x.location); + _rangeSelection.x.location += nGaps; + _fakeInsertionRange.length += nGaps; + //} + + } + } + // Moved left: remove gaps if needed + else if( endResidue < startResidue){ + // Proposed number of gaps to be removed + NSUInteger nGaps = startResidue - endResidue; + + // Actual number of gaps that we can remove + NSUInteger actualNGaps = 0; + + // If it was previously dragged right without releasing the button: shift the whole sequences + if( draggedRight ){ + if( nGaps <= _fakeInsertionRange.length ){ + actualNGaps = nGaps; + _fakeInsertionRange.length -= actualNGaps; + } + else { + actualNGaps = [self test:nGaps beforeSequenceIndex:_rangeSelection.x.location inSiteRange:_rangeSelection.y]; + _fakeDeletionRange.length += actualNGaps; + _fakeDeletionRange.location -= actualNGaps; + } + } + // We only move the block represented by rangeSelection + // no change of length unless we move residues in the last columns and the other residues (in the same columns) are gaps! + else { + actualNGaps = [self test:nGaps beforeSequenceIndex:_rangeSelection.x.location inSiteRange:_rangeSelection.y]; + if( actualNGaps > 0 ){ + _fakeDeletionRange.length += actualNGaps; + _fakeDeletionRange.location -= actualNGaps; + + if ( _fakeDeletionRange.length + _rangeSelection.x.location + _rangeSelection.x.length < [self numberOfSites]) { + _fakeInsertionRange.length += actualNGaps; + _fakeInsertionRange.location = _fakeDeletionRange.location+_rangeSelection.x.length; + } + } + _pingpong = YES; + } + + _rangeSelection.x.location -= actualNGaps; + } + dragStartPoint.x = dragPoint.x; + [self updateFrameSize]; + } + else{ + _rangeSelection = range; + } + + [self autoscroll:anEvent]; + [self setNeedsDisplay:YES]; + +} + +- (void)mouseUp:(NSEvent *)anEvent{ + + if( [[self sequences] count] == 0 ) return; + + dragEndPoint = [self convertPoint: [anEvent locationInWindow] fromView:nil]; + + + // Shifting (Insertion+deletion) + if( _fakeInsertionRange.length != 0 && _fakeDeletionRange.length != 0 ){ + NSRange temp = _fakeDeletionRange; + _fakeInsertionRange = NSMakeRange(0, 0); + _fakeDeletionRange = NSMakeRange(0, 0); + [delegate sequencesView:self slideSitesLeftInRange:temp inSequenceRange:_rangeSelection.y by:_rangeSelection.x.length]; + } + // Inserting + else if( _fakeInsertionRange.length != 0 ){ + NSUInteger location = _fakeInsertionRange.location; + NSUInteger length = _fakeInsertionRange.length; + _fakeInsertionRange = NSMakeRange(0, 0); + [delegate sequencesView:self insertGaps:length inSequenceRange:_rangeSelection.y atIndex:location]; + } + // Deletion + else if( _fakeDeletionRange.length != 0 ){ + NSRange temp = _fakeDeletionRange; + _fakeDeletionRange = NSMakeRange(0, 0); + [delegate sequencesView:self removeSitesInRange:temp inSequenceRange:_rangeSelection.y]; + } + else { + NSArray *unselectedSequences = [self unselectedSequencesInRangeSelection]; + if( [MFSequenceUtils isEmptyBlockAtTheEnd:unselectedSequences inRange:_rangeSelection.x ] ) _rangeSelection = MFMakeEmpty2DRange(); + } + + draggedRight = NO; + [self setNeedsDisplay:YES]; +} + +- (void)mouseExited:(NSEvent *)theEvent { + self.mouseOverSequence = @""; +} + +- (void)mouseMoved:(NSEvent *)theEvent { + NSPoint point = [self convertPoint:[theEvent locationInWindow] fromView:nil]; + + if( point.x < [[self superview]bounds].origin.x || point.y < [[self superview]bounds].origin.y ) [self mouseExited:nil]; + + NSInteger indexSequence = (NSInteger)(point.y/(_residueHeight+_rowSpacing)); + NSInteger indexSite = (NSInteger)(point.x/_residueWidth); + NSInteger length = [self numberOfSites]; + + + // filter points outside the alignment. Also filter out empty alignments + if(indexSequence >= 0 && indexSequence < [[self sequences]count] && indexSite >= 0 && indexSite < length ){ + + if( _mouseOverSequenceIndex != indexSequence || _mouseOverSiteIndex != indexSite ){ + MFSequence *seq = [[self sequences] objectAtIndex:indexSequence]; + NSMutableString *str = [[NSMutableString alloc]init]; + if( [seq.dataType isKindOfClass: [MFNucleotide class]] && seq.translated ){ + [str appendString:[NSString stringWithFormat:@"Amino acid %lu (%c -> %@)",indexSite+1,[seq residueAt:indexSite], [seq subCodonSequenceWithRange:NSMakeRange(indexSite, 1)]]]; + } + else { + if( [seq.dataType isKindOfClass: [MFNucleotide class]] ){ + [str appendString:[NSString stringWithFormat:@"Base"]]; + } + else { + [str appendString:[NSString stringWithFormat:@"Amino acid"]]; + } + [str appendString:[NSString stringWithFormat:@" %lu (%c)", indexSite+1, [seq residueAt:indexSite]]]; + } + [str appendString:[NSString stringWithFormat:@" in sequence %ld: %@",indexSequence+1, [seq name]]]; + + self.mouseOverSequence = str; + [str release]; + _mouseOverSequenceIndex = indexSequence; + _mouseOverSiteIndex = indexSite; + } + } + else { + self.mouseOverSequence = @""; + } +} + + +// http://stackoverflow.com/questions/8979639/mouseexited-isnt-called-when-mouse-leaves-trackingarea-while-scrolling#comment34491426_9107224 +- (void) createTrackingArea{ + int opts = (NSTrackingMouseEnteredAndExited|NSTrackingMouseMoved | NSTrackingActiveAlways); + trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds] + options:opts + owner:self + userInfo:nil]; + [self addTrackingArea:trackingArea]; + + NSPoint mouseLocation = [[self window] mouseLocationOutsideOfEventStream]; + mouseLocation = [self convertPoint: mouseLocation + fromView: nil]; + + if (NSPointInRect(mouseLocation, [self bounds]) ){ + [self mouseEntered: nil]; + } + else{ + [self mouseExited: nil]; + } +} + +- (void)updateTrackingAreas { + [self removeTrackingArea:trackingArea]; + [trackingArea release]; + [self createTrackingArea]; + [super updateTrackingAreas]; +} + +//You should also remove the tracking rectangle when your view is removed from its window, which can happen either because the view is moved to a different window, or because the view is removed as part of deallocation. One place to do this is the viewWillMoveToWindow: method, as shown in Compatibility Issues. + + + +#pragma mark *** Overrides Methods *** + +- (BOOL)acceptsFirstResponder { + return YES; +} + +- (BOOL)isFlipped{ + return YES; +} + +-(void)resetCursorRects{ + [super resetCursorRects]; + [self addCursorRect:[self bounds] cursor:[NSCursor IBeamCursor] ]; +} + +#pragma mark *** Keyboard Event Handling *** + + +// An override of the NSResponder method. NSResponder's implementation would just forward the message to the next responder (an NSClipView, in our case) and our overrides like -delete: would never be invoked. +- (void)keyDown:(NSEvent *)event { + BOOL ok = NO; + + if ( !( [event modifierFlags] & NSCommandKeyMask ) && !( [event modifierFlags] & NSAlternateKeyMask ) && _rangeSelection.y.length == 1 && _rangeSelection.x.length > 0 && !_isTranslated) { + NSString *str = [[event charactersIgnoringModifiers] uppercaseString]; + ok = [[[[self sequences] objectAtIndex:0]dataType] isValid:str]; + } + if( ok ){ + NSString *str = [[event charactersIgnoringModifiers] uppercaseString]; + [delegate sequencesView:self replaceResiduesInRange:NSMakeRange(_rangeSelection.x.location, 1) atSequenceIndex:_rangeSelection.y.location withString:str]; + + _rangeSelection.x.length--; + _rangeSelection.x.location++; + if(_rangeSelection.x.length == 0 ){ + _rangeSelection = MFMakeEmpty2DRange(); + } + } + else { + // Ask the key binding manager to interpret the event for us. + [self interpretKeyEvents:[NSArray arrayWithObject:event]]; + } + + +} + + +#pragma mark *** Bindings *** + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(NSObject *)observedObject change:(NSDictionary *)change context:(void *)context { + + // Use the observation context value to distinguish between them. We can do a simple pointer comparison because KVO doesn't do anything at all with the context value, not even retain or copy it. + if (context == MFSequenceViewSequencesObservationContext) { + [self updateFrameSize]; + [self setNeedsDisplay:YES]; + } + else if (context== MFSequenceViewSelectionIndexesObservationContext) { + if([self selectedSequences] > 0){ + _rangeSelection = MFMakeEmpty2DRange(); + } + [self setNeedsDisplay:YES]; + } + else { + [super observeValueForKeyPath:keyPath ofObject:observedObject change:change context:context]; + + } + +} + +- (void)unbind:(NSString *)bindingName { + if( [bindingName isEqualToString:MFSequenceViewForegroundColorBindingName] ){ + + } + [super unbind:bindingName]; +} + +-(NSDictionary*)foregroundColor{ + return _foregroundColor; +} + +-(void)setForegroundColor:(NSDictionary*)colors{ + if ( _foregroundColor != colors ) { + [_foregroundColor release]; + _foregroundColor = [colors retain]; + [_attsDict setObject:_foregroundColor forKey:NSForegroundColorAttributeName]; + } + [self setNeedsDisplay:YES]; +} + +-(NSDictionary*)backgroundColor{ + return _backgroundColor; +} + +-(void)setBackgroundColor:(NSDictionary*)colors{ + if ( _backgroundColor != nil ) { + [_backgroundColor release]; + } + _backgroundColor = [colors retain]; + [_attsDict setObject:_backgroundColor forKey:NSBackgroundColorAttributeName]; + + + + if( _backgroundColor == nil || [_backgroundColor count] == 0 ){ + _drawBackground = NO; + _canvasColor = [NSColor whiteColor]; + } + else { + NSColor *backgroundCol = [self commonColorBackground]; + if( !backgroundCol ){ + _drawBackground = YES; + _canvasColor = [NSColor whiteColor]; + } + else { + _drawBackground = NO; + _canvasColor = backgroundCol; + } + } + + + [self setNeedsDisplay:YES]; +} + +-(void)translationDidChange:(NSNotification *)notification{ + BOOL isTranslated = [[[self sequences]objectAtIndex:0]translated]; + + // it was amino acid before this change + if(_isTranslated && !isTranslated ){ + if(_rangeSelection.x.length != 0 ){ + _rangeSelection.x.location *= 3; + _rangeSelection.x.length *= 3; + + // if the sequence length is not a multiple of 3 + if ( _rangeSelection.x.location + _rangeSelection.x.length > [[[self sequences]objectAtIndex:0]length] ) { + _rangeSelection.x.length = [[[self sequences]objectAtIndex:0]length] - _rangeSelection.x.location; + } + } + } + else if(!_isTranslated && isTranslated ){ + if(_rangeSelection.x.length != 0 ){ + // ** + //ACTGTG + //M M + + // A bit ugly: round up the number of amino acid, then multiply by 3 to get the number of nuceotides and + // finaly subtsract the original selection to get what's missing on the right side of the selection + // The right part is the number of missing nuceotides to get an amino acid at the beginning (left side of selection). + // If the selection is TG then we also need to select 2 nucleotide at the beginning and at the end + NSUInteger temp = ((((_rangeSelection.x.location+_rangeSelection.x.length+2)/3)*3) -(_rangeSelection.x.location+_rangeSelection.x.length) ) + (_rangeSelection.x.location %3); + _rangeSelection.x.location /= 3; + _rangeSelection.x.length = (_rangeSelection.x.length+temp) / 3; + } + } + _isTranslated = isTranslated; + [self setNeedsDisplay:YES]; +} + +#pragma mark *** Private functions *** + +-(void)updateFrameSize{ + NSSize oldSize = [self frame ].size; + NSSize size; + size.width = _residueWidth * [self numberOfSites]; + size.height = (_residueHeight+_rowSpacing) * [[self sequences] count] + _rowSpacing; + + if( _fakeInsertionRange.length != 0 && _fakeDeletionRange.length == 0 ){ + size.width += _residueWidth * _fakeInsertionRange.length; + [self setFrameSize:size]; + [self viewDidMoveToWindow]; + } + if( size.width != oldSize.width || size.height != oldSize.height){ + [self setFrameSize:size]; + [self viewDidMoveToWindow]; + } +} + +-(NSUInteger)numberOfSites{ + NSUInteger maxLength = 0; + for (MFSequence *sequence in [self sequences] ) { + if( [sequence length] > maxLength ){ + maxLength = [sequence length]; + } + } + return maxLength; +} + +-(NSArray*)unselectedSequencesInRangeSelection{ + NSMutableArray *unselectedSequences = [[NSMutableArray alloc]init]; + + for ( NSUInteger i = 0; i < [[self sequences] count]; i++ ) { + if( i >= _rangeSelection.y.location && i < _rangeSelection.y.location+_rangeSelection.y.length) continue; + [unselectedSequences addObject:[[self sequences] objectAtIndex:i]]; + } + return [unselectedSequences autorelease]; +} + + +-(NSUInteger)test:(NSUInteger)nGaps beforeSequenceIndex:(NSUInteger)index inSiteRange:(NSRange)aRange{ + NSRange range = NSMakeRange(index, 0); + + for ( NSUInteger i = 0; i < nGaps; i++ ) { + NSUInteger j = 0; + for ( ; j < aRange.length; j++ ) { + MFSequence *sequence = [[self sequences]objectAtIndex: aRange.location+j]; + if( [sequence residueAt:range.location-1] != '-' ){ + break; + + } + } + if( j == aRange.length ){ + range.location--; + range.length++; + } + else{ + break; + } + } + return range.length; +} + +// Convert dragStartSelection and dragPoint to a MF2DRange +-(MF2DRange)convertSelectionPoints{ + MF2DRange range; + + NSUInteger numberOfSites = [[[self sequences] objectAtIndex:0]length]; + NSUInteger numberOfSequences = [[self sequences] count]; + + // We are dragging to the right + if(dragStartPoint.x < dragPoint.x ){ + range.x.location = dragStartPoint.x/_residueWidth; + range.x.length = ceil(dragPoint.x/_residueWidth) - range.x.location; + + if ( numberOfSites < range.x.location+range.x.length ) { + range.x.length = numberOfSites - range.x.location; + } + } + else { + if(dragPoint.x < 0 ){ + dragPoint.x = 0; + } + range.x.location = dragPoint.x/_residueWidth; + range.x.length = ceil(dragStartPoint.x/_residueWidth) - range.x.location; + } + + // We are dragging down + if(dragStartPoint.y < dragPoint.y ){ + range.y.location = dragStartPoint.y/(_residueHeight + _rowSpacing); + range.y.length = ceil(dragPoint.y/(_residueHeight + _rowSpacing)) - range.y.location; + + if( numberOfSequences < range.y.location+range.y.length ){ + range.y.length = numberOfSequences - range.y.location; + } + } + else { + if(dragPoint.y < 0 ){ + dragPoint.y = 0; + } + NSInteger temp = MIN(numberOfSequences, ceil(dragStartPoint.y/(_residueHeight + _rowSpacing))); + range.y.location = dragPoint.y/(_residueHeight + _rowSpacing); + range.y.length = temp - range.y.location; + } + return range; +} + + +-(NSColor *)commonColorBackground{ + + NSArray *values = [_backgroundColor allValues]; + NSColor *color = [values objectAtIndex:0]; + for ( NSUInteger i = 1; i < [values count]; i++ ) { + if( ![color isEqualTo:[values objectAtIndex:i]] ){ + color = nil; + break; + } + } + + return color; +} + + +/*[NSTimer scheduledTimerWithTimeInterval:caretBlinkRate + target:self + selector:@selector(updateCaret:) + userInfo:nil + repeats:YES];*/ +- (void)updateCaret:(NSTimer*)timer { + _caretBlinkActive = !_caretBlinkActive; //this sets the blink state + [self setNeedsDisplayInRect:_caretRect]; +} + + +@end diff --git a/Seqotron/MFSimpleAlignmentView.h b/Seqotron/MFSimpleAlignmentView.h new file mode 100644 index 0000000..1a3b644 --- /dev/null +++ b/Seqotron/MFSimpleAlignmentView.h @@ -0,0 +1,47 @@ +// +// MFSimpleAlignmentView.h +// Seqotron +// +// Created by Mathieu Fourment on 3/03/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +@interface MFSimpleAlignmentView : NSView{ + NSArray *_sequences; + CGFloat _fontSize; + NSString *_fontName; + CGFloat _rowSpacing; + CGFloat _residueWidth; + CGFloat _residueHeight; + CGFloat _rowHeight; + CGFloat _lineGap; + NSMutableDictionary *_attsDict; + NSMutableDictionary *_foregroundColor; + NSMutableDictionary *_backgroundColor; + BOOL _drawBackground; + NSColor *_canvasColor; +} + +@property (retain, readwrite) NSArray *sequences; +@property (nonatomic,retain, readwrite) NSMutableDictionary *foregroundColor; +@property (nonatomic,retain, readwrite) NSMutableDictionary *backgroundColor; +@property CGFloat residueHeight; + +@end diff --git a/Seqotron/MFSimpleAlignmentView.m b/Seqotron/MFSimpleAlignmentView.m new file mode 100644 index 0000000..fb0ed20 --- /dev/null +++ b/Seqotron/MFSimpleAlignmentView.m @@ -0,0 +1,198 @@ +// +// MFSimpleAlignmentView.m +// Seqotron +// +// Created by Mathieu Fourment on 3/03/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFSimpleAlignmentView.h" + +#import "MFSequence.h" +#import "MFSequence+MFSequenceDrawing.h" + +@implementation MFSimpleAlignmentView + +@synthesize sequences = _sequences; +@synthesize residueHeight = _residueHeight; +@synthesize foregroundColor = _foregroundColor; +@synthesize backgroundColor = _backgroundColor; + +-(void)awakeFromNib { + [super awakeFromNib]; + NSLog(@"MFSimpleAlignmentView view awakeFromNib"); + + _foregroundColor = nil; + _backgroundColor = nil; + + _drawBackground = NO; + _canvasColor = [NSColor whiteColor]; + + _sequences = nil; + _fontSize = 15; + _fontName = [[NSString alloc] initWithString: @"Courier"]; + _rowSpacing = 4; + _attsDict = [[NSMutableDictionary alloc] init]; + [_attsDict setObject:[NSFont fontWithName:_fontName size:_fontSize] forKey:NSFontAttributeName]; + [self initSize]; +} + +-(void)dealloc{ + [_sequences release]; + [_fontName release]; + [_attsDict release]; + [super dealloc]; +} + +- (BOOL)isFlipped{ + return YES; +} + +- (void)drawRect:(NSRect)dirtyRect { + [super drawRect:dirtyRect]; + + [[NSColor whiteColor] set]; + NSRectFill(dirtyRect); + + NSUInteger numberOfSequences = [[self sequences] count]; + if ( numberOfSequences == 0 ) { + return; + } + + // These NSRanges represent the number of rows (sequences) and (columns) sites that can be displayed in the NSScrollView (without copying) + NSRange siteRange; + NSRange seqRange; + NSUInteger numberOfSites = [[[self sequences]objectAtIndex:0]length]; + + siteRange.length = MIN(numberOfSites, ceil(dirtyRect.size.width/_residueWidth)); + seqRange.length = MIN(numberOfSequences, ceil(dirtyRect.size.height/(_residueHeight + _rowSpacing))); + + siteRange.location = dirtyRect.origin.x/_residueWidth; + seqRange.location = dirtyRect.origin.y/(_residueHeight + _rowSpacing); + + NSPoint point; + + for ( NSUInteger i = seqRange.location; i < seqRange.location+seqRange.length; i++ ) { + + MFSequence *sequence = [_sequences objectAtIndex: i]; + + point.x = siteRange.location * _residueWidth; + point.y = (i+1)*_rowSpacing+ i*_residueHeight - _rowSpacing/2; + + if( _drawBackground ){ + [self drawBackgroundForSequence:sequence InRange:NSMakeRange(siteRange.location, siteRange.length) atPoint:point]; + } + + point.y = (i * (_residueHeight + _rowSpacing)) - _lineGap + _rowSpacing; + [sequence drawAtPoint:point withRange:NSMakeRange(siteRange.location, siteRange.length) withAttributes:_attsDict]; + } +} + +-(void)drawBackgroundForSequence:(MFSequence*)sequence InRange:(NSRange)siteRange atPoint:(NSPoint)point{ + + NSRect rect = NSMakeRect(point.x, point.y, _residueWidth, _residueHeight+_rowSpacing); + + for ( NSUInteger j = siteRange.location; j < siteRange.location+siteRange.length; j++ ) { + NSString *residue = [sequence subSequenceWithRange:NSMakeRange(j, 1)]; + NSColor *background; + + if( [_backgroundColor objectForKey:[residue uppercaseString]] != nil ){ + background = [_backgroundColor objectForKey: [residue uppercaseString] ]; + } + else if( [_backgroundColor objectForKey:@"?"] != nil ){ + background = [_backgroundColor objectForKey: @"?" ]; + } + else{ + background = [NSColor grayColor]; + } + [background set]; + NSRectFill(rect); + rect.origin.x += _residueWidth; + } +} + +-(void)setForegroundColor:(NSMutableDictionary*)colors{ + if ( _foregroundColor != colors ) { + [_foregroundColor release]; + _foregroundColor = [colors retain]; + [_attsDict setObject:_foregroundColor forKey:NSForegroundColorAttributeName]; + } + [self setNeedsDisplay:YES]; +} + +-(void)setBackgroundColor:(NSMutableDictionary*)colors{ + if ( _backgroundColor != nil ) { + [_backgroundColor release]; + } + _backgroundColor = [colors retain]; + [_attsDict setObject:_backgroundColor forKey:NSBackgroundColorAttributeName]; + + + + if( _backgroundColor == nil || [_backgroundColor count] == 0 ){ + _drawBackground = NO; + _canvasColor = [NSColor whiteColor]; + } + else { + NSColor *backgroundCol = [self commonColorBackground]; + if( !backgroundCol ){ + _drawBackground = YES; + _canvasColor = [NSColor whiteColor]; + } + else { + _drawBackground = NO; + _canvasColor = backgroundCol; + } + } + + + [self setNeedsDisplay:YES]; +} + +-(NSColor *)commonColorBackground{ + + NSArray *values = [_backgroundColor allValues]; + NSColor *color = [values objectAtIndex:0]; + for ( NSUInteger i = 1; i < [values count]; i++ ) { + if( ![color isEqualTo:[values objectAtIndex:i]] ){ + color = nil; + break; + } + } + + return color; +} + +-(void)initSize{ + NSMutableDictionary *attsDict = [[NSMutableDictionary alloc] init]; + NSFont *font = [NSFont fontWithName:_fontName size:_fontSize]; + [attsDict setObject:font forKey:NSFontAttributeName]; + + NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:@"A" attributes:attsDict]; + //_rowHeight = [font capHeight]+_rowSpacing;//[string size].height - 4; + + _lineGap = [string size].height+[font descender] - [font capHeight]; + _rowHeight = [string size].height - _lineGap; + _residueHeight = [font capHeight]; + _residueWidth = [string size].width; + [string release]; + [attsDict release]; + +} + +@end diff --git a/Seqotron/MFStockholmImporter.h b/Seqotron/MFStockholmImporter.h new file mode 100755 index 0000000..2dec05f --- /dev/null +++ b/Seqotron/MFStockholmImporter.h @@ -0,0 +1,32 @@ +// +// MFStockholmImporter.h +// Seqotron +// +// Created by Mathieu on 3/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFSequenceImporter.h" + +@interface MFStockholmImporter : NSObject { + +} + +@end \ No newline at end of file diff --git a/Seqotron/MFStockholmImporter.m b/Seqotron/MFStockholmImporter.m new file mode 100755 index 0000000..6576bca --- /dev/null +++ b/Seqotron/MFStockholmImporter.m @@ -0,0 +1,106 @@ +// +// MFStockholmImporter.m +// Seqotron +// +// Created by Mathieu on 3/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFStockholmImporter.h" + +#import "MFSequence.h" +#import "MFReaderCluster.h" +#import "MFString.h" + +// http://sonnhammer.sbc.su.se/Stockholm.html +// Gaps are not allowed in sequence names + +@implementation MFStockholmImporter + +- (MFSequenceSet *)readSequencesFromFile:(NSString *)path{ + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithFile:path]; + + MFSequenceSet *sequences = [self readSequences:reader]; + + [reader release]; + + return sequences; +} + +-(MFSequenceSet*)readSequencesFromURL:(NSURL*)url{ + NSString *content = [ NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil]; + MFSequenceSet *sequences = [self readSequencesFromString:content]; + + return sequences; +} + +-(MFSequenceSet*)readSequencesFromData:(NSData*)data{ + NSString *content = [[NSString alloc] initWithData: data encoding: NSASCIIStringEncoding]; + + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithString:content]; + + MFSequenceSet *sequences = [self readSequences:reader]; + + [reader release]; + [content release]; + + return sequences; +} + +-(MFSequenceSet*)readSequencesFromString:(NSString*)content{ + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithString:content]; + + MFSequenceSet *sequences = [self readSequences:reader]; + + [reader release]; + + return sequences; +} + +- (MFSequenceSet *)readSequences:(MFReaderCluster *)reader{ + + MFSequenceSet *sequences = [[MFSequenceSet alloc] init]; + + NSString *line; + + while ( (line = [reader readLine]) ) { + line = [[line componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]componentsJoinedByString:@""]; + if( [[line lowercaseString] hasPrefix:@"#stockholm"]) break; + } + + while ( (line = [reader readLine]) ) { + if( [line isEmpty]) continue; + + line = [line stringByTrimmingLeadingWhitespace]; + + if( ![line hasPrefix:@"#"] && ![line hasPrefix:@"//"] ){ + NSRange range = [line rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]]; + NSString *name = [line substringToIndex:range.location]; + NSString *seq = [[line substringFromIndex:range.location+1] uppercaseString]; + seq = [seq stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + seq = [seq stringByReplacingOccurrencesOfString:@"." withString:@"-"]; + MFSequence *sequence = [[ MFSequence alloc] initWithString:seq name:name]; + [sequences addSequence:sequence]; + [sequence release]; + } + } + + return [sequences autorelease]; +} + +@end \ No newline at end of file diff --git a/Seqotron/MFString.h b/Seqotron/MFString.h new file mode 100755 index 0000000..3abba8b --- /dev/null +++ b/Seqotron/MFString.h @@ -0,0 +1,46 @@ +// +// MFString.h +// Seqotron +// +// Created by Mathieu Fourment on 5/02/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +@interface NSString (MoreString) + +- (NSArray*)splitLines; + +- (NSString*)stringByTrimmingLeadingWhitespace; + +- (NSString*)stringByTrimmingTrailingWhitespace; + +- (NSString*)stringByTrimmingPaddingWhitespace; + +- (NSString*)reverse:(NSString*)aString; + +- (BOOL) containCharacter:(char)character; + +- (BOOL) isEmpty; + +- (NSString*)stringByAddingQuotesIfSpaces; + ++ (NSString*)stringRandomWithLength:(NSUInteger)length; + +@end diff --git a/Seqotron/MFString.m b/Seqotron/MFString.m new file mode 100755 index 0000000..42a7dfe --- /dev/null +++ b/Seqotron/MFString.m @@ -0,0 +1,144 @@ +// +// MFString.m +// Seqotron +// +// Created by Mathieu Fourment on 5/02/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFString.h" + +@implementation NSString (MoreString) + + +-(NSArray*)splitLines{ + NSUInteger length = [self length]; + NSUInteger paraStart = 0, paraEnd = 0, contentsEnd = 0; + NSMutableArray *array = [NSMutableArray array]; + NSRange currentRange; + while (paraEnd < length) { + [self getParagraphStart:¶Start end:¶End contentsEnd:&contentsEnd forRange:NSMakeRange(paraEnd, 0)]; + currentRange = NSMakeRange(paraStart, contentsEnd - paraStart); + [array addObject:[self substringWithRange:currentRange]]; + } + return array; +} + +-(NSString*)stringByTrimmingLeadingWhitespace { + NSInteger i = 0; + + while ((i < [self length]) + && [[NSCharacterSet whitespaceCharacterSet] characterIsMember:[self characterAtIndex:i]]) { + i++; + } + return [self substringFromIndex:i]; +} + +-(NSString*)stringByTrimmingTrailingWhitespace { + NSInteger i = [self length]-1; + + while ( i >= 0 && [[NSCharacterSet whitespaceCharacterSet] characterIsMember:[self characterAtIndex:i]] ) { + i--; + } + return [self substringToIndex:i]; +} + +// NSPanel seems to implement stringByTrimmingWhitespace too +-(NSString*)stringByTrimmingPaddingWhitespace { + NSInteger i = [self length]-1; + NSRange range = NSMakeRange(0, [self length]); + + while ( i >= 0 && [[NSCharacterSet whitespaceCharacterSet] characterIsMember:[self characterAtIndex:i]] ) { + range.length--; + i--; + } + if( i == -1 ) return @""; + + i = 0; + + while ( i < [self length] && [[NSCharacterSet whitespaceCharacterSet] characterIsMember:[self characterAtIndex:i]]) { + range.location++; + range.length--; + i++; + } + return [self substringWithRange:range]; +} + +-(NSString*)reverse:(NSString*)aString{ + NSMutableString *reversedString = [NSMutableString stringWithCapacity:[aString length]]; + + [aString enumerateSubstringsInRange:NSMakeRange(0,[aString length]) + options:(NSStringEnumerationReverse | NSStringEnumerationByComposedCharacterSequences) + usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { + [reversedString appendString:substring]; + }]; + return [reversedString autorelease]; +} + +- (BOOL) containCharacter:(char)character { + if ([self rangeOfString:[NSString stringWithFormat:@"%c",character]].location != NSNotFound){ + return YES; + } + return NO; +} + +-(BOOL) isEmpty{ + NSUInteger i = 0; + while ( i < [self length]){ + if( ![[NSCharacterSet whitespaceCharacterSet] characterIsMember:[self characterAtIndex:i]] ){ + break; + } + i++; + } + return i == [self length]; +} + ++ (NSString*)stringRandomWithLength:(NSUInteger)length { + NSString *alphabet = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXZY0123456789"; + NSMutableString* string = [NSMutableString stringWithCapacity:length]; + for (NSUInteger i = 0; i < length; i++) { + u_int32_t r = arc4random() % [alphabet length]; + unichar c = [alphabet characterAtIndex:r]; + [string appendFormat:@"%C", c]; + } + return string; +} + +-(NSString*)stringByAddingQuotesIfSpaces{ + NSRange range = [self rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]]; + if (range.location != NSNotFound) { + return [NSString stringWithFormat:@"""%@""", self]; + } + return self; +} + +-(NSString*)stringByExcapingSpaces{ + NSRange range = [self rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]]; + if (range.location != NSNotFound) { + return [NSString stringWithFormat:@"\"%@\"", self]; + } + return self; +} + +-(NSString*)stringByChopping{ + if( [self length] == 0 ){ + return self; + } + return [self substringToIndex:[self length]-1]; +} +@end diff --git a/Seqotron/MFStringReader.h b/Seqotron/MFStringReader.h new file mode 100755 index 0000000..eb2c346 --- /dev/null +++ b/Seqotron/MFStringReader.h @@ -0,0 +1,34 @@ +// +// MFStringReader.h +// Seqotron +// +// Created by Mathieu Fourment on 7/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFReaderCluster.h" + +@interface MFStringReader : MFReaderCluster{ + NSString *_content; + NSRange _range; + NSUInteger _start; + NSUInteger _end; + NSUInteger _contentsEnd; +} + +@end diff --git a/Seqotron/MFStringReader.m b/Seqotron/MFStringReader.m new file mode 100755 index 0000000..bd4c61a --- /dev/null +++ b/Seqotron/MFStringReader.m @@ -0,0 +1,58 @@ +// +// MFStringReader.m +// Seqotron +// +// Created by Mathieu Fourment on 7/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFStringReader.h" + +@implementation MFStringReader + + +-(id)initWithString:(NSString *)content{ + self = [super init]; + if (self) { + _content = [content retain]; + _contentsEnd = 0; + _range = NSMakeRange(0, 0); + } + return self; +} + +-(void)dealloc{ + [_content release]; + [super dealloc]; +} + +-(NSString*)readLine{ + NSString *line = nil; + if ( _contentsEnd < [_content length] ){ + [_content getLineStart:&_start end:&_end contentsEnd:&_contentsEnd forRange:_range]; + line = [_content substringWithRange:NSMakeRange(_start,_contentsEnd-_start)]; + _range = NSMakeRange(_end,0); + } + return line; +} + +-(void)rewind{ + _contentsEnd = 0; + _range = NSMakeRange(0, 0); +} +@end diff --git a/Seqotron/MFSyncronizedScrollView.h b/Seqotron/MFSyncronizedScrollView.h new file mode 100755 index 0000000..30b832e --- /dev/null +++ b/Seqotron/MFSyncronizedScrollView.h @@ -0,0 +1,35 @@ +// +// MFSyncronizedScrollView.h +// Seqotron +// +// Created by Mathieu Fourment on 25/01/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +@interface MFSyncronizedScrollView : NSScrollView{ + NSScrollView* synchronizedScrollView; + BOOL _synchronizeVertical; +} + +- (void)setSynchronizedScrollView:(NSScrollView*)scrollview onVertical:(BOOL)vertical; +- (void)stopSynchronizing; +- (void)synchronizedViewContentBoundsDidChange:(NSNotification *)notification; + +@end diff --git a/Seqotron/MFSyncronizedScrollView.m b/Seqotron/MFSyncronizedScrollView.m new file mode 100755 index 0000000..aaf637a --- /dev/null +++ b/Seqotron/MFSyncronizedScrollView.m @@ -0,0 +1,118 @@ +// +// MFSyncronizedScrollView.m +// Seqotron +// +// Created by Mathieu Fourment on 25/01/14. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFSyncronizedScrollView.h" + +// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/NSScrollViewGuide/Articles/SynchroScroll.html#//apple_ref/doc/uid/TP40003537-SW5 + +@implementation MFSyncronizedScrollView + +- (id)initWithFrame:(NSRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + _synchronizeVertical = YES; + [self setVerticalScrollElasticity:NSScrollElasticityNone]; + [self setHorizontalScrollElasticity:NSScrollElasticityNone]; + } + return self; +} + +- (void)drawRect:(NSRect)dirtyRect +{ + [super drawRect:dirtyRect]; + + // Drawing code here. +} + +- (void)setSynchronizedScrollView:(NSScrollView*)scrollview onVertical:(BOOL)vertical{ + NSView *synchronizedContentView; + _synchronizeVertical = vertical; + + // stop an existing scroll view synchronizing + [self stopSynchronizing]; + + // don't retain the watched view, because we assume that it will + // be retained by the view hierarchy for as long as we're around. + synchronizedScrollView = scrollview; + + // get the content view of the + synchronizedContentView = [synchronizedScrollView contentView]; + + // Make sure the watched view is sending bounds changed + // notifications (which is probably does anyway, but calling + // this again won't hurt). + [synchronizedContentView setPostsBoundsChangedNotifications:YES]; + + // a register for those notifications on the synchronized content view. + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(synchronizedViewContentBoundsDidChange:) + name:NSViewBoundsDidChangeNotification + object:synchronizedContentView]; +} + + +- (void)synchronizedViewContentBoundsDidChange:(NSNotification *)notification{ + // get the changed content view from the notification + NSClipView *changedContentView = [notification object]; + + // get the origin of the NSClipView of the scroll view that + // we're watching + NSPoint changedBoundsOrigin = [changedContentView documentVisibleRect].origin;; + + // get our current origin + NSPoint curOffset = [[self contentView] bounds].origin; + NSPoint newOffset = curOffset; + + // scrolling is synchronized in the vertical plane + // so only modify the y component of the offset + if(_synchronizeVertical) newOffset.y = changedBoundsOrigin.y; + else newOffset.x = changedBoundsOrigin.x; + + // if our synced position is different from our current + // position, reposition our content view + if (!NSEqualPoints(curOffset, changedBoundsOrigin)){ + // note that a scroll view watching this one will + // get notified here + [[self contentView] scrollToPoint:newOffset]; + // we have to tell the NSScrollView to update its + // scrollers + [self reflectScrolledClipView:[self contentView]]; + } +} + +- (void)stopSynchronizing{ + if (synchronizedScrollView != nil) { + NSView* synchronizedContentView = [synchronizedScrollView contentView]; + + // remove any existing notification registration + [[NSNotificationCenter defaultCenter] removeObserver:self + name:NSViewBoundsDidChangeNotification + object:synchronizedContentView]; + + // set synchronizedScrollView to nil + synchronizedScrollView=nil; + } +} + +@end diff --git a/Seqotron/MFTextGraphic.h b/Seqotron/MFTextGraphic.h new file mode 100755 index 0000000..c9d7133 --- /dev/null +++ b/Seqotron/MFTextGraphic.h @@ -0,0 +1,39 @@ +// +// MFTextGraphic.h +// Seqotron +// +// Created by Mathieu on 5/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFGraphic.h" + +@interface MFTextGraphic : MFGraphic { + + @private + NSTextStorage *_contents; + BOOL _updateBounds; +} + +-(id)initWithString:(NSString*)string attributes:(NSDictionary*)attrs; + +-(id)initWithString:(NSString*)string; + +- (NSTextStorage *)contents; + +@end diff --git a/Seqotron/MFTextGraphic.m b/Seqotron/MFTextGraphic.m new file mode 100755 index 0000000..4421e9a --- /dev/null +++ b/Seqotron/MFTextGraphic.m @@ -0,0 +1,233 @@ +// +// MFTextGraphic.m +// Seqotron +// +// Created by Mathieu on 5/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFTextGraphic.h" + +@implementation MFTextGraphic + + +-(id)initWithString:(NSString*)string attributes:(NSDictionary*)attrs{ + if( self = [super init]){ + _contents = [[NSTextStorage alloc]initWithString:string attributes:attrs]; + + NSRect bounds = [self bounds]; + NSSize naturalSize = [self naturalSize]; + [self setBounds:NSMakeRect(bounds.origin.x, bounds.origin.y, naturalSize.width, naturalSize.height)]; + _contents.delegate = self; + } + return self; +} + +-(id)initWithString:(NSString*)string{ + if( self = [super init]){ + _contents = [[NSTextStorage alloc]initWithString:string]; + [_contents addAttribute:NSFontAttributeName + value:[NSFont fontWithName:@"Courier" size:12] + range:NSMakeRange(0, [_contents length])]; +// [_contents addAttribute:NSBackgroundColorAttributeName +// value:[NSColor redColor] +// range:NSMakeRange(0, [_contents length])]; + + NSRect bounds = [self bounds]; + NSSize naturalSize = [self naturalSize]; + [self setBounds:NSMakeRect(bounds.origin.x, bounds.origin.y, naturalSize.width, naturalSize.height)]; + _contents.delegate = self; + _updateBounds = NO; + } + return self; +} + +- (void)dealloc { + + // Do the regular Cocoa thing. + [_contents setDelegate:nil]; + [_contents release]; + [super dealloc]; + +} + +- (NSString*)description{ + return [NSString stringWithFormat: @"MFTextGraphic %@ x %f y %f w %f h %f", [_contents string], _bounds.origin.x,_bounds.origin.y, _bounds.size.width, _bounds.size.height ]; +} + +- (NSTextStorage *)contents { + return _contents; + +} + +- (BOOL)isDrawingStroke { + + // We never draw a stroke on this kind of graphic. + return NO; + +} + +- (NSRect)drawingBounds { + if (_updateBounds ) { + NSRect bounds = [self bounds]; + NSSize naturalSize = [self naturalSize]; + [self setBounds:NSMakeRect(bounds.origin.x, bounds.origin.y, naturalSize.width, naturalSize.height)]; + + _updateBounds = NO; + } + // The drawing bounds must take into account the focus ring that might be drawn by this class' override of -drawContentsInView:isBeingCreatedOrEdited:. It can't forget to take into account drawing done by -drawHandleInView:atPoint: though. Because this class doesn't override -drawHandleInView:atPoint:, it should invoke super to let SKTGraphic take care of that, and then alter the results. + return NSUnionRect([super drawingBounds], NSInsetRect([self bounds], -1.0f, -1.0f)); +} + + +- (void)drawContentsInView2:(NSView *)view isBeingCreateOrEdited:(BOOL)isBeingCreatedOrEditing { + + // Draw the fill color if appropriate. + NSRect bounds = [self bounds]; +// if ([self isDrawingFill]) { +// [[self fillColor] set]; +// NSRectFill(bounds); +// } + + // If this graphic is being created it has no text. If it is being edited then the editor returned by -newEditingViewWithSuperviewBounds: will draw the text. + if (isBeingCreatedOrEditing) { + + // Just draw a focus ring. + [[NSColor knobColor] set]; + NSFrameRect(NSInsetRect(bounds, -1.0, -1.0)); + + } + else { + + // Don't bother doing anything if there isn't actually any text. + NSTextStorage *contents = [self contents]; + if ( [contents length] > 0 ) { + + // Get a layout manager, size its text container, and use it to draw text. -glyphRangeForTextContainer: forces layout and tells us how much of text fits in the container. + NSLayoutManager *layoutManager = [[self class] sharedLayoutManager]; + NSTextContainer *textContainer = [[layoutManager textContainers] objectAtIndex:0]; + [textContainer setContainerSize:bounds.size]; + [contents addLayoutManager:layoutManager]; + NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; + if (glyphRange.length > 0 ) { + [layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:bounds.origin]; + [layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:bounds.origin]; + } + [contents removeLayoutManager:layoutManager]; + + } + + } + +} + +- (void)drawContentsInView:(NSView *)view isSelected:(BOOL)isSelected { + + if (_updateBounds ) { + NSRect bounds = [self bounds]; + NSSize naturalSize = [self naturalSize]; + [self setBounds:NSMakeRect(bounds.origin.x, bounds.origin.y, naturalSize.width, naturalSize.height)]; + + _updateBounds = NO; + } + + NSRect bounds = [self bounds]; + if ([self isDrawingFill]) { + [[self fillColor] set]; + NSRectFill(bounds); + } + +// NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init]; +// NSTextContainer *textContainer = [[NSTextContainer alloc] init]; +// +// [layoutManager addTextContainer:textContainer]; +// [textContainer release]; +// [_contents addLayoutManager:layoutManager]; +// +// [layoutManager release]; + // Get a layout manager, size its text container, and use it to draw text. -glyphRangeForTextContainer: forces layout and tells us how much of text fits in the container. + + NSLayoutManager *layoutManager = [[self class] sharedLayoutManager]; + NSTextContainer *textContainer = [[layoutManager textContainers] objectAtIndex:0]; + [textContainer setContainerSize:bounds.size]; + [_contents addLayoutManager:layoutManager]; + + NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; + + [view lockFocus]; + if ( isSelected) { + [[NSColor knobColor] set]; + NSRectFill(bounds); + //NSFrameRect(NSInsetRect(bounds, -1.0, -1.0)); + } + else { + [layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:bounds.origin]; + } + [layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:bounds.origin]; + [view unlockFocus]; + + [_contents removeLayoutManager:layoutManager]; + +} + + ++ (NSLayoutManager *)sharedLayoutManager { + + // Return a layout manager that can be used for any drawing. + static NSLayoutManager *layoutManager = nil; + if (!layoutManager) { + NSTextContainer *textContainer = [[NSTextContainer alloc] initWithContainerSize:NSMakeSize(1.0e7f, 1.0e7f)]; + layoutManager = [[NSLayoutManager alloc] init]; + [textContainer setWidthTracksTextView:NO]; + [textContainer setHeightTracksTextView:NO]; + [layoutManager addTextContainer:textContainer]; + [textContainer release]; + } + return layoutManager; +} + +- (NSSize)naturalSize { + + // Figure out how big this graphic would have to be to show all of its contents. -glyphRangeForTextContainer: forces layout. + NSLayoutManager *layoutManager = [[self class] sharedLayoutManager]; + NSTextContainer *textContainer = [[layoutManager textContainers] objectAtIndex:0]; + [textContainer setContainerSize:NSMakeSize(1.0e7f, 1.0e7f)]; + NSTextStorage *contents = [self contents]; + [contents addLayoutManager:layoutManager]; + [layoutManager glyphRangeForTextContainer:textContainer]; + NSSize naturalSize = [layoutManager usedRectForTextContainer:textContainer].size; + [contents removeLayoutManager:layoutManager]; + return naturalSize; + +} + +- (void)setHeightToMatchContents { + NSRect bounds = [self bounds]; + NSSize naturalSize = [self naturalSize]; + [self setBounds:NSMakeRect(bounds.origin.x, bounds.origin.y, naturalSize.width, naturalSize.height)]; + +} + +// Conformance to the NSTextStorageDelegate protocol. +// In my case setHeightToMatchContents is called after drawRect so it does not have the right bounds +- (void)textStorageDidProcessEditing:(NSNotification *)notification { + // The work we're going to do here involves sending -glyphRangeForTextContainer: to a layout manager, but you can't send that message to a layout manager attached to a text storage that's still responding to -endEditing, so defer the work to a point where -endEditing has returned. + //[self performSelector:@selector(setHeightToMatchContents) withObject:nil afterDelay:0.0]; + _updateBounds = YES; +} +@end diff --git a/Seqotron/MFTree.h b/Seqotron/MFTree.h new file mode 100755 index 0000000..6b37ed6 --- /dev/null +++ b/Seqotron/MFTree.h @@ -0,0 +1,73 @@ +// +// MFTree.h +// Seqotron +// +// Created by Mathieu Fourment on 7/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFNode.h" + +typedef enum { + MFTreeTraverseAlgorithmInorder, + MFTreeTraverseAlgorithmPreorder, + MFTreeTraverseAlgorithmPostorder, + MFTreeTraverseAlgorithmBreadthFirst, + +} MFTreeTraverseAlgorithm; + +//typedef bool (^NSTreeTraverseBlock)(NSTreeNode *node, id data, id extra); + +@interface MFTree : NSObject{ + MFNode *_root; + NSUInteger _nodeCount; + NSUInteger _taxonCount; + NSMutableDictionary *_attributes; +} + +-(id)initWithRoot:(MFNode*)root; + +-(id)initWithNewick:(NSString*)newick; + +- (NSUInteger)nodeCount; + +- (NSUInteger)taxonCount; + +-(MFNode*)root; + +- (void)setRootAtNode:(MFNode*)theNode; + +- (void)ladderize; + +-(NSString*)newick; + +- (void)enumerateNodesWithAlgorithm:(MFTreeTraverseAlgorithm)algorithm usingBlock:(void (^)(MFNode *node))block; + +- (void)enumeratePostorderNode:(MFNode*)node usingBlock:(void (^)(MFNode *node))block; + +- (void)enumeratePreorderNode:(MFNode*)node usingBlock:(void (^)(MFNode *node))block; + +- (void)enumerateInorderNode:(MFNode*)node usingBlock:(void (^)(MFNode *node))block; + +- (void)setAttribute:(id)object forKey:(NSString*)key; + +- (id)attributeForKey:(NSString*)key; + +@end diff --git a/Seqotron/MFTree.m b/Seqotron/MFTree.m new file mode 100755 index 0000000..2fb4769 --- /dev/null +++ b/Seqotron/MFTree.m @@ -0,0 +1,416 @@ +// +// MFTree.m +// Seqotron +// +// Created by Mathieu Fourment on 7/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFTree.h" + +@implementation MFTree + +-(id)initWithRoot:(MFNode*)root{ + if( self = [super init] ){ + _attributes = nil; + _root = [root retain]; + [self updateNodeCounts]; + } + return self; +} + +-(id)initWithNewick:(NSString*)newick{ + if( self = [super init] ){ + _attributes = nil; + [self buildTree:newick]; + [self updateNodeCounts]; + } + return self; +} + +-(void)dealloc{ + NSLog(@"MFTree dealloc"); + [_root release]; + [_attributes release]; + [super dealloc]; +} + +-(MFNode*)root{ + return _root; +} + +- (void)updateNodeCounts{ + _nodeCount = 0; + _taxonCount = 0; + [self enumeratePostorderNode:_root usingBlock:^(MFNode* node){ + if( [node isLeaf] ){ + _taxonCount++; + } + _nodeCount++; + }]; +} + +- (NSUInteger)nodeCount{ + return _nodeCount; +} + +- (NSUInteger)taxonCount{ + return _taxonCount; +} + +-(NSString*)newick{ + NSMutableString *newick = [[NSMutableString alloc]init]; + [self newickFromNode:_root inString:newick]; + [newick appendString:@";"]; + return [newick autorelease]; +} + +-(void)newickFromNode:(MFNode*)node inString:(NSMutableString*)newick { + if( [node childCount] > 0 ){ + [newick appendString:@"("]; + for ( NSUInteger i = 0; i < [node childCount]; i++ ) { + [self newickFromNode:[node childAtIndex:i] inString:newick]; + if(i < [node childCount]-1) [newick appendString:@","]; + } + [newick appendString:@")"]; + if( node.parent != nil) [newick appendFormat:@"%@:%f", [node name], [node branchLength] ]; + } + else { + [newick appendFormat:@"%@:%f", [node name], [node branchLength] ]; + } +} + +- (void)enumerateNodesWithAlgorithm:(MFTreeTraverseAlgorithm)algorithm usingBlock:(void (^)(MFNode *node))block{ + switch ( algorithm) { + case MFTreeTraverseAlgorithmPostorder: + [self enumeratePostorderNode:_root usingBlock:block]; + break; + case MFTreeTraverseAlgorithmPreorder: + [self enumeratePreorderNode:_root usingBlock:block]; + break; + case MFTreeTraverseAlgorithmInorder: + [self enumerateInorderNode:_root usingBlock:block]; + break; + default: + break; + } +} + +- (void)enumeratePostorderNode:(MFNode*)node usingBlock:(void (^)(MFNode *node))block{ + for (int i = 0; i < [node childCount]; i++) { + [self enumeratePostorderNode:[node childAtIndex:i] usingBlock:block]; + } + block(node); +} + +- (void)enumeratePreorderNode:(MFNode*)node usingBlock:(void (^)(MFNode *node))block{ + block(node); + for (int i = 0; i < [node childCount]; i++) { + [self enumeratePreorderNode:[node childAtIndex:i] usingBlock:block]; + } +} + +- (void)enumerateInorderNode:(MFNode*)node usingBlock:(void (^)(MFNode *node))block{ + for (int i = 0; i < [node childCount]; i++) { + [self enumerateInorderNode:[node childAtIndex:i] usingBlock:block]; + block(node); + } +} + +// return desc without the comment +-(NSString*)extractAttributes:(NSString*)desc node:(MFNode*)node{ + NSMutableString *mutString = [[NSMutableString alloc]init]; + NSUInteger start = [desc rangeOfString:@"["].location; + if( start != 0 ){ + [mutString appendString:[desc substringToIndex:start]]; + } + NSUInteger end = [desc rangeOfString:@"]"].location; + if(end < [desc length]){ + [mutString appendString:[desc substringFromIndex:end+1]]; + } + + NSString *comment = [desc substringWithRange:NSMakeRange(start+1, end-start-1)]; + NSMutableString *attr = [[NSMutableString alloc]init]; + + NSUInteger j = 0; + while ( j < [comment length]) { + if( [comment characterAtIndex:j] == ',' || j == [comment length]-1 ){ + if (j == [comment length]-1) { + [attr appendFormat:@"%c",[comment characterAtIndex:j]]; + } + + NSUInteger loc = [attr rangeOfString:@"="].location; + NSString *key = [attr substringToIndex:loc]; + if ([key hasPrefix:@"&"]) { + key = [key substringFromIndex:1]; + } + NSString *value = [attr substringFromIndex:loc+1]; + + [node setAttribute:value forKey:key]; + [attr setString:@""]; + } + else if( [comment characterAtIndex:j] == '{' ){ + while ( [comment characterAtIndex:j] == '}' ) { + [attr appendFormat:@"%c",[comment characterAtIndex:j]]; + j++; + } + [attr appendFormat:@"%c",[comment characterAtIndex:j]]; + + } + else { + [attr appendFormat:@"%c",[comment characterAtIndex:j]]; + } + j++; + } + [attr release]; + return [mutString autorelease]; +} + +-(void)buildTree:(NSString*)newick{ + + MFNode *current = [[MFNode alloc]initWithName:@"node0"]; // the root + _root = current; + NSUInteger count = 1; + + NSCharacterSet *aSet = [NSCharacterSet characterSetWithCharactersInString:@":,)["]; + + for ( NSUInteger i = 1; i < [newick length]; i++ ) { + unichar c = [newick characterAtIndex:i]; + + if( c == ';' ) break; + + if( c == '(' ){ + MFNode *node = [[MFNode alloc]initWithName:[NSString stringWithFormat:@"node%lu",count]]; + [current addChild:node]; + + current = node; + [node release]; + + count++; + + } + else if( c == ')' ){ + + NSUInteger p = ++i; + while ( [newick characterAtIndex:p] != ',' && [newick characterAtIndex:p] != ')' && [newick characterAtIndex:p] != ';' ) { + if( [newick characterAtIndex:p] == '['){ + while ([newick characterAtIndex:p] != ']') p++; + } + p++; + } + + + if ( p != i ) { + NSRange range = NSMakeRange(i, p-i); + NSString *desc = [newick substringWithRange:range]; + + // extract comment + if( [desc rangeOfString:@"["].location != NSNotFound ){ + desc = [self extractAttributes:desc node:current]; + } + + if( [desc rangeOfString:@":"].location != NSNotFound ){ + NSUInteger idx = [desc rangeOfString:@":"].location; + if( idx == 0 ){ + CGFloat branchLength = [[desc substringFromIndex:1] floatValue]; + [current setBranchLength:branchLength]; + } + else { + // could be a name or a bootstrap + NSString *temp = [desc substringToIndex:idx]; + NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc]init]; + if( [numberFormatter numberFromString:temp] ){ + [current setAttribute:temp forKey:MFNodeDefaultInternalKey]; + } + else { + [current setName:temp]; + } + [numberFormatter release]; + + CGFloat branchLength = [[desc substringFromIndex:idx+1] floatValue]; + [current setBranchLength:branchLength]; + } + + } + i = p-1; + } + current = current.parent; + + } + // leaf node + else if( c != ',' ){ + NSUInteger p = [newick rangeOfCharacterFromSet:aSet options:0 range:NSMakeRange(i, [newick length]-i)].location; + NSRange range = NSMakeRange(i, p-i); + NSString *taxon = [newick substringWithRange:range]; + taxon = [taxon stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"'\""]]; + + MFNode *n = [[MFNode alloc]initWithName:taxon]; + [current addChild:n]; + + i = p; + + while ( [newick characterAtIndex:p] != ',' && [newick characterAtIndex:p] != ')' ) { + if( [newick characterAtIndex:p] == '['){ + while ([newick characterAtIndex:p] != ']') p++; + } + p++; + } + + if( p != i ){ + range = NSMakeRange(i, p-i); + NSString *desc = [newick substringWithRange:range]; + + // extract comment + if( [desc rangeOfString:@"["].location != NSNotFound ){ + desc = [self extractAttributes:desc node:n]; + } + + if( [desc hasPrefix:@":"] ){ + CGFloat branchLength = [[desc substringFromIndex:1] floatValue]; + [n setBranchLength:branchLength]; + } + + i = p; + } + + [n release]; + i--; + } + } +} + +- (void)setAttribute:(id)object forKey:(NSString*)key{ + if( _attributes == nil ){ + _attributes = [[NSMutableDictionary alloc]init]; + } + [_attributes setObject:object forKey:key]; +} + +- (id)attributeForKey:(NSString*)key{ + if( _attributes == nil ) return nil; + return [_attributes objectForKey:key]; +} + +- (void)setRootAtNode:(MFNode*)theNode{ + if( [theNode isRoot] || [[theNode parent]isRoot] ) return; + + MFNode *newRoot = [[MFNode alloc]initWithName:@"root"]; + MFNode *node = [theNode parent]; + + CGFloat branchLength = [node branchLength]; + NSMutableDictionary *attributes = [node attributes]; + CGFloat midPoint = [theNode branchLength]*0.5; + + [theNode setBranchLength:midPoint]; + [node setBranchLength:midPoint]; + + if( ![theNode isLeaf] ){ + node.attributes = theNode.attributes; + } + else { + theNode.attributes = [NSMutableDictionary dictionary]; + node.attributes = [NSMutableDictionary dictionary]; + } + + MFNode *parentNode = [node parent]; + [newRoot addChild:node ]; + [newRoot addChild:theNode]; + + [parentNode removeChild:node]; + [node removeChild:theNode]; + + while ( ![parentNode isRoot] ) { + CGFloat tempBranchLength = [parentNode branchLength]; + [parentNode setBranchLength:branchLength]; + branchLength = tempBranchLength; + + NSMutableDictionary *tempAttributes = [parentNode attributes]; + parentNode.attributes = attributes; + attributes = tempAttributes; + + MFNode *tempNode = [parentNode parent]; + [node addChild:parentNode]; + + node = parentNode; + parentNode = tempNode; + [parentNode removeChild:node]; + } + + + + parentNode = [parentNode childAtIndex:0]; + [parentNode setBranchLength:[parentNode branchLength]+branchLength]; + [node addChild:parentNode]; + + + MFNode *root = [self root]; + for ( NSUInteger i = 0; i < [root childCount]; i++ ) { + [root removeChildAtIndex:i]; + } + [root addChild:[newRoot childAtIndex:0]]; + [root addChild:[newRoot childAtIndex:1]]; + [root setBranchLength:0]; + + if( [theNode isLeaf] ){ + for ( NSUInteger i = 0; i < [root childCount]; i++) { + [root childAtIndex:i].attributes = [NSMutableDictionary dictionary]; + } + } + + [newRoot release]; +} + + +NSComparisonResult customCompareFunction(NSArray* first, NSArray* second, void* context) +{ + id firstValue = [first objectAtIndex:0]; + id secondValue = [second objectAtIndex:0]; + return [firstValue compare:secondValue]; +} + +- (void)ladderize{ + + [self enumerateNodesWithAlgorithm:MFTreeTraverseAlgorithmPostorder usingBlock:^(MFNode *node){ + if ( [node isLeaf] ) { + + [node setAttribute:[NSNumber numberWithFloat:1] forKey:@"?mlaf.ladderize"]; + } + else { + NSUInteger count = 0; + NSMutableArray *array = [NSMutableArray array]; + for ( NSUInteger i = 0; i < [node childCount]; i++ ) { + [array addObject:[NSArray arrayWithObjects:[[node childAtIndex:i] attributeForKey:@"?mlaf.ladderize"], [node childAtIndex:i], nil]]; + count += [[[node childAtIndex:i] attributeForKey:@"?mlaf.ladderize"]unsignedIntegerValue]; + [node removeChildAtIndex:i]; + } + [node setAttribute:[NSNumber numberWithFloat:count] forKey:@"?mlaf.ladderize"]; + + NSArray* sortedArray = [array sortedArrayUsingFunction:customCompareFunction context:NULL]; + + for (NSArray *pair in sortedArray) { + [node addChild:[pair objectAtIndex:1]]; + } + } + }]; + + [self enumerateNodesWithAlgorithm:MFTreeTraverseAlgorithmPostorder usingBlock:^(MFNode *node){ + [node.attributes removeObjectForKey:@"?mlaf.ladderize"]; + }]; +} + +@end diff --git a/Seqotron/MFTreeBuilderController.h b/Seqotron/MFTreeBuilderController.h new file mode 100644 index 0000000..b2290a4 --- /dev/null +++ b/Seqotron/MFTreeBuilderController.h @@ -0,0 +1,77 @@ +// +// MFTreeBuilder.h +// Seqotron +// +// Created by Mathieu Fourment on 18/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFDataType.h" +#import "MFExternalOperation.h" +#import "MFSequenceSet.h" +#import "MFOperationBuilder.h" + +@interface MFTreeBuilderController : NSWindowController { + + NSString *_alignmentName; + NSArray *_sequences; + + NSString *_physherPath; + NSString *_tempPath; + + // Resampling + IBOutlet NSTextField *_bootstrapTextField; + IBOutlet NSTextField *_seedTextField; + + // Distance + NSMutableArray *_distanceMatrices; + NSArray *_distanceTreeMethods; + + // Maximum likelihood + IBOutlet NSTextField *_categoriesTextField; +} + +@property NSUInteger indexTabView; + +@property (retain) NSArray *resampling; +@property NSUInteger resamplingSelection; +@property NSUInteger bootstrap; +@property NSUInteger resamplingThreads; + +// Distance +@property (retain) NSMutableArray *distanceMatrices; +@property (retain) NSArray *distanceTreeMethods; +@property NSUInteger distanceMatricesSelection; +@property NSUInteger distanceTreeMethodsSelection; + +// Maximum likelihood +@property (retain) NSMutableArray *mlModels; +@property NSUInteger mlModelsSelection; +@property (retain,readonly) NSArray *topologySearches; +@property NSUInteger topologySearchesSelection; +@property BOOL gamma; +@property NSUInteger categories; +@property BOOL pInvariant; + +- (id)initWithSequences:(NSArray *)sequences withName:(NSString*)name; + +-(void)initType; + +@end diff --git a/Seqotron/MFTreeBuilderController.m b/Seqotron/MFTreeBuilderController.m new file mode 100644 index 0000000..00e1b86 --- /dev/null +++ b/Seqotron/MFTreeBuilderController.m @@ -0,0 +1,321 @@ +// +// MFTreeBuilder.m +// Seqotron +// +// Created by Mathieu Fourment on 18/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFTreeBuilderController.h" + +#import "MFExternalOperation.h" +#import "MFNucleotide.h" +#import "MFSequence.h" +#import "MFSequenceSet.h" +#import "MFSequenceWriter.h" +#import "MFString.h" + +#define MFNUCLEOTIDEMODELS @"GTR", @"HKY", @"JC69", @"K80",@"GY94", nil +#define MFAMINOACIDMODELS @"LG",@"WAG", @"Dayhoff", nil +#define MFCODONMODELS @"GY94", nil + +#define MFNUCLEOTIDEDISTANCE @"Uncorrected",@"JC69",@"K2P", nil +#define MFAMINOACIDDISTANCE @"K83", nil + + +@implementation MFTreeBuilderController + +@synthesize resampling, bootstrap, indexTabView, resamplingSelection, resamplingThreads; + +@synthesize distanceTreeMethods = _distanceTreeMethods; +@synthesize distanceMatrices = _distanceMatrices; +@synthesize distanceTreeMethodsSelection,distanceMatricesSelection; + +@synthesize topologySearches, topologySearchesSelection; +@synthesize mlModels; +@synthesize gamma,categories,pInvariant; + +- (id)initWithSequences:(NSArray *)sequences withName:(NSString*)name{ + if(self = [super initWithWindowNibName:@"MFTreeBuilderController"]){ + + _sequences = [sequences retain]; + _alignmentName = [name copy]; + + _distanceTreeMethods = [[NSArray alloc]initWithObjects:@"Neighbor joining", @"UPGMA", nil]; + distanceMatricesSelection = 0; + distanceTreeMethodsSelection = 0; + + mlModels = [[NSMutableArray alloc]init]; + _distanceMatrices = [[NSMutableArray alloc]init]; + [self initType]; + + // Maximum Likelihood + topologySearches = [[NSArray alloc]initWithObjects:@"Fixed", @"NNI", nil]; + topologySearchesSelection = 0; + _mlModelsSelection = 0; + gamma = NO; + categories = 1; + pInvariant = NO; + + resampling = [[NSArray alloc]initWithObjects:@"None", @"Bootstrap", @"Jackknife", nil]; + bootstrap = 100; + resamplingThreads = 1; + + indexTabView = 0; + + _physherPath = [[[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"bin/physher"]stringByAddingQuotesIfSpaces] copy]; + + NSError *error = nil; + NSURL *cacheDir = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&error]; + + NSString *executableName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleExecutable"]; + _tempPath = [[NSString alloc]initWithString:[[[cacheDir path] stringByAppendingPathComponent:executableName]stringByAppendingPathComponent:@"temp"]]; + } + return self; +} + +-(void)initType{ + [mlModels removeAllObjects]; + [_distanceMatrices removeAllObjects]; + + MFSequence *sequence = [_sequences objectAtIndex:0]; + + [self willChangeValueForKey:@"mlModels"]; + [self willChangeValueForKey:@"distanceMatrices"]; + if( [[sequence dataType] isKindOfClass:[MFNucleotide class]] && ![sequence translated] ){ + [mlModels addObjectsFromArray:[NSArray arrayWithObjects:MFNUCLEOTIDEMODELS]]; + [_distanceMatrices addObjectsFromArray:[NSArray arrayWithObjects:MFNUCLEOTIDEDISTANCE]]; + } + else { + [mlModels addObjectsFromArray:[NSArray arrayWithObjects:MFAMINOACIDMODELS]]; + [_distanceMatrices addObjectsFromArray:[NSArray arrayWithObjects:MFAMINOACIDDISTANCE]]; + } + [self didChangeValueForKey:@"mlModels"]; + [self didChangeValueForKey:@"distanceMatrices"]; +} + +-(void)dealloc{ + NSLog(@"MFTreeBuilderController delloc"); + [_alignmentName release]; + [_sequences release]; + + [_distanceTreeMethods release]; + [_distanceMatrices release]; + [topologySearches release]; + + + [mlModels release]; + [resampling release]; + + [_physherPath release]; + [_tempPath release]; + + [super dealloc]; +} + +- (void)windowDidLoad { + [super windowDidLoad]; + NSInteger t = time(NULL); + [_seedTextField setStringValue:[@(t) stringValue]]; +} + +-(NSArray*)operations{ + + NSMutableDictionary *options = [[NSMutableDictionary alloc]init]; + NSMutableArray *arguments = [[NSMutableArray alloc]init]; + + [options setObject:arguments forKey:MFExternalOperationArgumentsKey]; + + [options setObject:@"MFTreeDocument" forKey:MFOperationDocumentClassKey]; + + NSString *tempDirPath = [_tempPath stringByAppendingPathComponent:[NSString stringWithFormat: @"%.0f", [NSDate timeIntervalSinceReferenceDate] * 1000.0]]; + BOOL isDir = YES; + while ( [[NSFileManager defaultManager]fileExistsAtPath:tempDirPath isDirectory:&isDir]) { + tempDirPath = [_tempPath stringByAppendingPathComponent:[NSString stringWithFormat: @"%.0f", [NSDate timeIntervalSinceReferenceDate] * 1000.0]]; + } + + NSError *error = nil; + [[NSFileManager defaultManager] createDirectoryAtPath:tempDirPath withIntermediateDirectories:YES attributes:nil error:&error]; + + if( !error ){ + + // Input + [arguments addObject:@"-i"]; + + NSString *inputFile = [tempDirPath stringByAppendingPathComponent:@"input.fa"]; + NSURL *inputURL = [NSURL URLWithString:[inputFile stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; + + NSCharacterSet *forbiddenCharacters = [NSCharacterSet characterSetWithCharactersInString:@",[]():; "]; + + MFSequenceSet *set = [[MFSequenceSet alloc] initWithSequences:_sequences]; + for (MFSequence *sequence in [set sequences]) { + sequence.name = [[sequence.name componentsSeparatedByCharactersInSet:forbiddenCharacters]componentsJoinedByString:@"_"]; + } + [MFSequenceWriter writeFasta:set toFile:inputFile attributes:nil]; + [set release]; + + [arguments addObject:[[inputURL path]stringByAddingQuotesIfSpaces] ]; + + // Output + NSString *prefix = @"output"; + NSString *stemPath = [tempDirPath stringByAppendingPathComponent:prefix]; + NSMutableString *outputFile = [NSMutableString stringWithString:stemPath]; + + [arguments addObject:@"-o"]; + [arguments addObject:[stemPath stringByAddingQuotesIfSpaces]]; + + if(self.indexTabView == 0 ){ + [self setUpDistance: options withOutput:outputFile]; + } + else if( self.indexTabView == 1 ){ + [self setUpML:options withOutput:outputFile]; + } + + + //NSURL *outputURL = [NSURL URLWithString:[outputFile stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; + NSURL *outputURL = [NSURL fileURLWithPath:outputFile isDirectory:NO]; + [options setObject:outputURL forKey:MFOperationOutputKey]; + } + + if(error){ + [options release]; + [arguments release]; + return nil; + } + + if( self.resamplingSelection == 1 && self.bootstrap > 0 ){ + [arguments addObject:@"-b"]; + [arguments addObject:[@(self.bootstrap) stringValue]]; + + } + else if( self.resamplingSelection == 2 ){ + [arguments addObject:@"-j"]; + } + + if( resamplingThreads > 0 ){ + [arguments addObject:@"-T"]; + [arguments addObject:[@(resamplingThreads) stringValue]]; + } + + [arguments addObject:@"-R"]; + [arguments addObject:[_seedTextField stringValue]]; + + [options setObject:_physherPath forKey:MFExternalOperationLaunchPathKey]; + + [arguments release]; + + NSLog(@"%@", options); + + MFExternalOperation *op = [[MFExternalOperation alloc]initWithOptions:options]; + op.description = [options objectForKey:MFOperationDescriptionKey]; + [options release]; + + NSArray *ops = [NSArray arrayWithObject:op]; + [op release]; + + return ops; +} + +-(void)setUpDistance:(NSMutableDictionary*)options withOutput:(NSMutableString*)output{ + NSMutableArray *arguments = [options objectForKey:MFExternalOperationArgumentsKey]; + + if( self.distanceTreeMethodsSelection == 0 ){ + [arguments addObject:@"-D"]; + [arguments addObject:@"nj"]; + [output appendString:@".nj.tree"]; + [options setObject:[@"Neighbor joining: " stringByAppendingString:_alignmentName] forKey:MFOperationDescriptionKey]; + } + else if( self.distanceTreeMethodsSelection == 1 ){ + [arguments addObject:@"-D"]; + [arguments addObject:@"upgma"]; + [output appendString:@".upgma.tree"]; + [options setObject:[@"UPGMA: " stringByAppendingString:_alignmentName] forKey:MFOperationDescriptionKey]; + } + + if( ![[_distanceMatrices objectAtIndex:self.distanceMatricesSelection]isEqualToString:@"Uncorrected"]){ + [arguments addObject:@"-m"]; + [arguments addObject:[_distanceMatrices objectAtIndex:self.distanceMatricesSelection] ]; + } +} + +-(void)setUpML:(NSMutableDictionary*)options withOutput:(NSMutableString*)output{ + NSMutableArray *arguments = [options objectForKey:MFExternalOperationArgumentsKey]; + + [options setObject:[@"Maximum likelihood: " stringByAppendingString: _alignmentName] forKey:MFOperationDescriptionKey]; + [output appendString:@".freerate.tree"]; + + [arguments addObject:@"-m"]; + [arguments addObject:[mlModels objectAtIndex:self.mlModelsSelection]]; + + if( self.pInvariant ){ + [arguments addObject:@"-I"]; + } + [arguments addObject:@"-c"]; + if(self.gamma && self.categories > 0 ){ + [arguments addObject:[@(categories) stringValue]]; + } + else { + [arguments addObject:@"1"]; + } + + if ( self.topologySearchesSelection > 0 ) { + [arguments addObject:@"-O nni"]; + } +} + +- (void)controlTextDidChange:(NSNotification *)notification { + + NSCharacterSet *cs = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789"] invertedSet]; + + NSTextField *obj = [notification object]; + if( [obj tag] == 1){ + NSString *filtered = [[[_bootstrapTextField stringValue] componentsSeparatedByCharactersInSet:cs] componentsJoinedByString:@""]; + if( [filtered isEqualToString:@""] ){ + self.bootstrap = 0; + [_bootstrapTextField setStringValue:@"0"]; + } + else { + self.bootstrap = [filtered intValue]; + [_bootstrapTextField setStringValue:filtered]; + } + } + else if( [obj tag] == 2){ + NSString *filtered = [[[_categoriesTextField stringValue] componentsSeparatedByCharactersInSet:cs] componentsJoinedByString:@""]; + if( [filtered isEqualToString:@""] ){ + self.categories = 0; + [_categoriesTextField setStringValue:@"0"]; + } + else { + self.categories = [filtered intValue]; + [_categoriesTextField setStringValue:filtered]; + } + } +} + +-(IBAction)close:(id)sender{ + [NSApp endSheet:[self window] returnCode:NSCancelButton]; + [[self window] orderOut:nil]; +} + +-(IBAction)build:(id)sender{ + [NSApp endSheet:[self window] returnCode:NSOKButton]; + [[self window] orderOut:nil]; +} + + +@end diff --git a/Seqotron/MFTreeBuilderController.xib b/Seqotron/MFTreeBuilderController.xib new file mode 100644 index 0000000..9d86ac2 --- /dev/null +++ b/Seqotron/MFTreeBuilderController.xib @@ -0,0 +1,352 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Seqotron/MFTreeDocument.h b/Seqotron/MFTreeDocument.h new file mode 100644 index 0000000..7e54296 --- /dev/null +++ b/Seqotron/MFTreeDocument.h @@ -0,0 +1,36 @@ +// +// MFTreeDocument.h +// Seqotron +// +// Created by Mathieu Fourment on 14/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFDefines.h" + +@interface MFTreeDocument : NSDocument{ + NSMutableArray *_trees; + MFTreeFormat _indexFormat; + MFTreeFormat _currentFileFormat; +} + +- (id)initWithTrees:(NSArray*)trees; + +@end diff --git a/Seqotron/MFTreeDocument.m b/Seqotron/MFTreeDocument.m new file mode 100644 index 0000000..daf94ed --- /dev/null +++ b/Seqotron/MFTreeDocument.m @@ -0,0 +1,177 @@ +// +// MFTreeDocument.m +// Seqotron +// +// Created by Mathieu Fourment on 14/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFTreeDocument.h" + +#import "MFTreeWindowController.h" +#import "MFTreeReader.h" +#import "MFTreeWriter.h" + +@implementation MFTreeDocument + +- (id)init { + self = [super init]; + if (self) { + _trees = [[NSMutableArray alloc]init]; + _currentFileFormat = MFTreeFormatNEWICK; + _indexFormat = MFTreeFormatNEWICK; + } + return self; +} + +- (id)initWithTrees:(NSArray*)trees{ + + self = [super init]; + if (self) { + _trees = [trees mutableCopy]; + _currentFileFormat = MFTreeFormatNEWICK; + _indexFormat = MFTreeFormatNEWICK; + } + return self; +} + +-(void)dealloc{ + NSLog(@"MFTreeDocument dealloc"); + [_trees release]; + [super dealloc]; +} + +- (void)makeWindowControllers { + // Start off with one document window. + MFTreeWindowController *windowController = [[MFTreeWindowController alloc] init]; + [self addWindowController:windowController]; + [windowController release]; +} + +- (void)windowControllerDidLoadNib:(NSWindowController *)aController { + [super windowControllerDidLoadNib:aController]; + // Add any code here that needs to be executed once the windowController has loaded the document's window. + +} + +- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError { + // Insert code here to write your document to data of the specified type. If outError != NULL, ensure that you create and set an appropriate error when returning nil. + // You can also choose to override -fileWrapperOfType:error:, -writeToURL:ofType:error:, or -writeToURL:ofType:forSaveOperation:originalContentsURL:error: instead. + if (outError) { + *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:nil]; + } + NSData *data = [MFTreeWriter data:_trees withFormat:_indexFormat attributes:nil]; + _currentFileFormat = _indexFormat; + NSLog(@"Writing to file %lu", _indexFormat); + return data; +} + + +- (BOOL)readFromURL:(NSURL*) url ofType:(NSString*)type error:(NSError**) outError { + BOOL readSuccessfully = NO; + + NSArray *trees = [MFTreeReader readTreesFromFile:url.path]; + NSLog(@"succesful %@", trees); + if( trees != nil ){ + MFTree *tree = [trees objectAtIndex:0]; + _currentFileFormat = [self formatTypeStringToEnum: [tree attributeForKey:MFTreeFileFormat]]; + NSLog(@"File Format %@", [tree attributeForKey:MFTreeFileFormat]); + // without it when we close the alignment without editing it, undos will be generated + [[self undoManager] disableUndoRegistration]; + + [self removeTreesAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [[self trees] count])]]; + [self insertTrees:trees atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [trees count])]]; + + [[self undoManager] enableUndoRegistration]; + + readSuccessfully = YES; + + } + return readSuccessfully; +} + ++ (BOOL)autosavesInPlace { + return NO; +} + + +- (NSArray *)trees { + return _trees; +} + +- (void)insertTrees:(NSArray *)trees atIndexes:(NSIndexSet *)indexes{ + [_trees insertObjects:trees atIndexes:indexes]; + NSLog(@"insertTrees %lu",[trees count]); + NSUndoManager *undoManager = [self undoManager]; + [undoManager registerUndoWithTarget:self selector:@selector(removeTreesAtIndexes:) object:indexes]; +} + +- (void)removeTreesAtIndexes:(NSIndexSet *)indexes { + NSArray *trees = [_trees objectsAtIndexes:indexes]; + [[[self undoManager] prepareWithInvocationTarget:self] insertTrees:trees atIndexes:indexes]; + [_trees removeObjectsAtIndexes:indexes]; +} + +// A method to retrieve the int value from the NSArray of NSStrings +-(MFTreeFormat) formatTypeStringToEnum:(NSString*)strVal{ + NSArray *typeArray = [[NSArray alloc] initWithObjects:MFTreeFormatArray]; + NSUInteger n = [typeArray indexOfObject:strVal]; + [typeArray release]; + return (MFTreeFormat) n; +} + +#pragma mark - Save panel + +// https://developer.apple.com/library/mac/samplecode/CustomSave/Introduction/Intro.html#//apple_ref/doc/uid/DTS10004201 + +// ------------------------------------------------------------------------------- +// prepareSavePanel:inSavePanel: +// ------------------------------------------------------------------------------- +// Invoked by runModalSavePanel to do any customization of the Save panel savePanel. +// +- (BOOL)prepareSavePanel:(NSSavePanel *)inSavePanel { + [inSavePanel setDelegate:self]; // allows us to be notified of save panel events + + MFTreeWindowController *windowController = [[self windowControllers] objectAtIndex:0]; + [inSavePanel setMessage:@"Save Tree as:"]; + [inSavePanel setAccessoryView: windowController.saveDialogCustomView]; // add our custom view + //[inSavePanel setAllowedFileTypes:@[@"txt"]]; // save files with 'txt' extension only + //[inSavePanel setNameFieldLabel:@"FILE NAME:"]; // override the file name label + + //_savePanel = inSavePanel; // keep track of the save panel for later + [windowController.saveFileFormat removeAllItems]; + NSArray *formats = [[NSArray alloc] initWithObjects:MFTreeFormatArray]; + [windowController.saveFileFormat addItemsWithObjectValues:formats]; + [formats release]; + + [windowController.saveFileFormat setAction:@selector(formatComboBoxChanged:)]; + [windowController.saveFileFormat setTarget:self]; + [windowController.saveFileFormat setEditable:NO]; + [windowController.saveFileFormat selectItemAtIndex: _currentFileFormat]; + + _indexFormat = _currentFileFormat; + + return YES; +} + +- (void)formatComboBoxChanged:(NSComboBox *)comboBox { + _indexFormat = [comboBox indexOfSelectedItem]; +} + + +@end diff --git a/Seqotron/MFTreeExporter.h b/Seqotron/MFTreeExporter.h new file mode 100644 index 0000000..250aa40 --- /dev/null +++ b/Seqotron/MFTreeExporter.h @@ -0,0 +1,65 @@ +// +// MFTreeExporter.h +// Seqotron +// +// Created by Mathieu Fourment on 21/01/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFTree.h" + +extern NSString *MFTreeExporterShowInternalNodeNameKey; // default: NO +extern NSString *MFTreeExporterShowBootstrapNodeNameKey; // default: NO + +@interface MFTreeExporter : NSObject{ + NSArray *_trees; + NSDictionary *_options; + NSMutableDictionary *_map; +} + + +-(id)initWithTrees:(NSArray*)trees options:(NSDictionary*)options; + +-(id)initWithTrees:(NSArray*)trees; + +-(id)initWithTree:(MFTree*)tree; + +-(void)writeToFile:(NSString*)path error:(NSError**)error; + +-(void)writeToURL:(NSURL*)url error:(NSError**)error; + +-(NSString*)string; + +-(NSData*)data; + +@end + + +//@protocol MFTreeExporter +// +//-(void)writeToFile:(NSString*)path error:(NSError**)error; +// +//-(void)writeToURL:(NSURL*)url error:(NSError**)error; +// +//-(NSData*)data; +// +//-(NSString*)string; +// +//@end \ No newline at end of file diff --git a/Seqotron/MFTreeExporter.m b/Seqotron/MFTreeExporter.m new file mode 100644 index 0000000..3b792b2 --- /dev/null +++ b/Seqotron/MFTreeExporter.m @@ -0,0 +1,92 @@ +// +// MFTreeExporter.m +// Seqotron +// +// Created by Mathieu Fourment on 21/01/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFTreeExporter.h" + +#import "MFTree.h" + +NSString *MFTreeExporterShowInternalNodeNameKey = @"tk.phylogenetics.seqotron.exporter.tree.show.internal.node"; +NSString *MFTreeExporterShowBootstrapNodeNameKey = @"tk.phylogenetics.seqotron.exporter.tree.show.bootstrap"; + +@implementation MFTreeExporter + + +-(id)initWithTrees:(NSArray*)trees{ + if( self = [super init]){ + _trees = [trees retain]; + _options = nil; + _map = nil; + } + return self; +} + + +-(id)initWithTrees:(NSArray*)trees options:(NSDictionary*)options{ + if( self = [super init]){ + _trees = [trees retain]; + _options = [options retain]; + _map = nil; + } + return self; +} + +-(id)initWithTree:(MFTree*)tree{ + if( self = [super init]){ + _trees = [[NSArray alloc]initWithObjects:tree, nil]; + _options = nil; + _map = nil; + } + return self; +} + +-(void)dealloc{ + [_options release]; + [_map release]; + [_trees release]; + [super dealloc]; +} + +-(void)writeToFile:(NSString*)path error:(NSError**)error{ + if (error) { + *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:nil]; + } +} + +-(void)writeToURL:(NSURL*)url error:(NSError**)error{ + if (error) { + *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:nil]; + } +} + +-(NSString*)string{ + NSLog( @"NSOperation is an abstract class, implement -[%@ %@]", [self class], NSStringFromSelector( _cmd ) ); + return nil; +} + +-(NSData*)data{ + NSString *string = [self string]; + NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; + return data; +} + +@end diff --git a/Seqotron/MFTreeImporter.h b/Seqotron/MFTreeImporter.h new file mode 100644 index 0000000..350f28a --- /dev/null +++ b/Seqotron/MFTreeImporter.h @@ -0,0 +1,37 @@ +// +// MFTreeImporter.h +// Seqotron +// +// Created by Mathieu Fourment on 14/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + + +@protocol MFTreeImporter + +-(NSArray*)readTreesFromFile:(NSString*)path; + +-(NSArray*)readTreesFromURL:(NSURL*)url; + +-(NSArray*)readTreesFromData:(NSData*)data; + +-(NSArray*)readTreesFromString:(NSString*)content; + +@end diff --git a/Seqotron/MFTreeReader.h b/Seqotron/MFTreeReader.h new file mode 100644 index 0000000..732ea17 --- /dev/null +++ b/Seqotron/MFTreeReader.h @@ -0,0 +1,36 @@ +// +// MFTreeReader.h +// Seqotron +// +// Created by Mathieu Fourment on 15/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +@interface MFTreeReader : NSObject + ++ (NSArray *)readTreesFromData:(NSData *)data; + ++ (NSArray *)readTreesFromFile:(NSString *)filePath; + ++ (NSArray *)readTreesFromURL:(NSURL*)url; + ++ (NSArray *)readTreesFromString:(NSString *)content; + +@end diff --git a/Seqotron/MFTreeReader.m b/Seqotron/MFTreeReader.m new file mode 100644 index 0000000..c6f35bd --- /dev/null +++ b/Seqotron/MFTreeReader.m @@ -0,0 +1,119 @@ +// +// MFTreeReader.m +// Seqotron +// +// Created by Mathieu Fourment on 15/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFTreeReader.h" + +#import "MFReaderCluster.h" +#import "MFString.h" +#import "MFNexusImporter.h" +#import "MFNewickImporter.h" +#import "MFTree.h" +#import "MFDefines.h" + +NSString * const MFTreeFileFormat = @"tk.phylogenetics.tree.file.format"; + +@implementation MFTreeReader + ++ (NSArray *)readTreesFromData:(NSData *)data{ + + NSString *content = [[NSString alloc] initWithData: data encoding: NSASCIIStringEncoding]; + NSArray *trees = [MFTreeReader readTreesFromString:content]; + [content release]; + return trees; +} + ++ (NSArray *)readTreesFromFile:(NSString *)filePath{ + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithFile:filePath]; + NSString *line = nil; + NSArray *trees = nil; + id importer = nil; + NSArray *formats = [[NSArray alloc] initWithObjects:MFTreeFormatArray]; + + while ( (line = [reader readLine]) ) { + if( [line isEmpty] ) continue; + if( [[line uppercaseString] hasPrefix:@"#NEXUS"] ){ + importer = [[MFNexusImporter alloc]init]; + + trees = [importer readTreesFromFile:filePath]; + for (MFTree *tree in trees) { + [tree setAttribute:[formats objectAtIndex:MFTreeFormatNEXUS] forKey:MFTreeFileFormat]; + } + + } + else if( [line hasPrefix:@"("] ){ + importer = [[MFNewickImporter alloc]init]; + + trees = [importer readTreesFromFile:filePath]; + for (MFTree *tree in trees) { + [tree setAttribute:[formats objectAtIndex:MFTreeFormatNEWICK] forKey:MFTreeFileFormat]; + } + } + break; + } + [importer release]; + [reader release]; + [formats release]; + return trees; +} + ++ (NSArray *)readTreesFromURL:(NSURL*)url{ + NSString *content = [ NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil]; + NSArray *trees = [MFTreeReader readTreesFromString:content]; + return trees; +} + ++ (NSArray *)readTreesFromString:(NSString *)content{ + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithString:content]; + NSString *line = nil; + NSArray *trees = nil; + id importer = nil; + NSArray *formats = [[NSArray alloc] initWithObjects:MFTreeFormatArray]; + + while ( (line = [reader readLine]) ) { + if( [line isEmpty] ) continue; + if( [[line uppercaseString] hasPrefix:@"#NEXUS"] ){ + importer = [[MFNexusImporter alloc]init]; + + trees = [importer readTreesFromString:content]; + for (MFTree *tree in trees) { + [tree setAttribute:[formats objectAtIndex:MFTreeFormatNEXUS] forKey:MFTreeFileFormat]; + } + + } + else if( [line hasPrefix:@"("] ){ + importer = [[MFNewickImporter alloc]init]; + + trees = [importer readTreesFromString:content]; + for (MFTree *tree in trees) { + [tree setAttribute:[formats objectAtIndex:MFTreeFormatNEWICK] forKey:MFTreeFileFormat]; + } + } + break; + } + [importer release]; + [reader release]; + [formats release]; + return trees; +} + +@end diff --git a/Seqotron/MFTreeView.h b/Seqotron/MFTreeView.h new file mode 100755 index 0000000..ae104f7 --- /dev/null +++ b/Seqotron/MFTreeView.h @@ -0,0 +1,129 @@ +// +// MFTreeView.h +// Seqotron +// +// Created by Mathieu Fourment on 11/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFTree.h" + +@interface MFTreeView : NSView{ + NSMutableArray *_trees; + + NSIndexSet *_selectionIndexes; + + NSColor *_backgroundColor; + + CGFloat _rootOffset;// offset on the left (i.e. where the root starts) + CGFloat _rightMargin; + CGFloat _branchTipSpace; // space between a taxon and its branch + CGFloat _rootLength; + + // scale bar + CGFloat _scaleBarWidth; // width of the bar (line) in pixels + CGFloat _scaleBarSpace; // space between text and bar in pixels + CGFloat _scaleBarFontSize; // font size + CGFloat _scaleBarFontHeight; // height of the text alone + NSMutableAttributedString *_scaleBarValue; // text of the scale bar (e.g 0.01) + + NSPoint _dragStartPoint; + NSPoint _dragPoint; + + CGFloat _treeScaler; + CGFloat _maximumDepth; + BOOL _taxaShown; + + NSFont *_font; + NSString *_fontName; + CGFloat _fontSize; + CGFloat _lineGap; + CGFloat _taxonHeight; + CGFloat _taxonSpacing; // spacing between taxa + CGFloat _taxonSpacingDefault; + + NSMutableDictionary *_attsDict; + NSMutableAttributedString *_attributedString; + + BOOL _showXAxis; + BOOL _scaleBarShown; + + NSMutableDictionary *_graphicTaxa; + NSMutableDictionary *_graphicBranch; + NSMutableDictionary *_graphicBranchLabel; + NSMutableDictionary *_graphicVerticalBranch; + + CGFloat _maximumNameLength; + + CGFloat _bottomSpace; + + BOOL _observingSuperview; + + NSInteger _selectionMode; + NSArray *_selectedNodes; + NSRect _marqueeSelectionBounds; + + NSString *_branchAttribute; + NSTrackingArea *_trackingArea; +} + +@property (readwrite)BOOL taxaShown; + +//@property (readwrite, retain)NSMutableArray *trees; + +- (void)setTrees:(NSMutableArray *)trees; +- (NSArray*)trees; + +- (void)setSelectionIndexes:(NSIndexSet*)indexes; +- (NSIndexSet*)selectionIndexes; + +- (void)setSelectedNodes:(NSArray*)nodes; +- (NSArray*)selectedNodes; + +- (void)incrementTaxonSpacing:(CGFloat)inc; + +- (void)incrementWidth:(CGFloat)inc; + +- (void)setDefaultSize; + +- (void)showBranchAttribute:(NSString*)attribute; + +- (BOOL)setColorForSelected:(NSColor*)color; + +- (void)rotateSelectedBranch; + +- (void)rootAtSelectedBranch; + +- (void)setFontSize:(CGFloat)fontSize; + +- (void)reloadData; + +/*- (MFTree*)objectInTreesAtIndex:(NSUInteger)index; +-(NSArray*)treesAtIndexes:(NSIndexSet*)indexes; +- (NSUInteger)countOfTrees; +- (void)insertObject:(MFTree *)tree inTreesAtIndex:(NSUInteger)index; +- (void)insertTrees:(NSArray*)trees atIndexes:(NSIndexSet*)indexes; +- (void)removeObjectFromTreesAtIndex:(NSUInteger)index; +- (void)removeTreesAtIndexes:(NSIndexSet*)indexes; +- (NSIndexSet*)selectionIndexes; +- (void)setSelectionIndexes:(NSIndexSet*)indexes;*/ + + +@end diff --git a/Seqotron/MFTreeView.m b/Seqotron/MFTreeView.m new file mode 100755 index 0000000..9685933 --- /dev/null +++ b/Seqotron/MFTreeView.m @@ -0,0 +1,1020 @@ +// +// MFTreeView.m +// Seqotron +// +// Created by Mathieu Fourment on 11/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFTreeView.h" + +#import "MFGraphic.h" +#import "MFTextGraphic.h" +#import "MFLineGraphic.h" +#import "MFString.h" +#import "NSObject+TDBindings.h" + +NSString *MFDepthKey = @"?MFTreeView.depth"; + +@implementation MFTreeView + +@synthesize taxaShown = _taxaShown; + + +-(id)initWithFrame:(NSRect)frameRect{ + + if( self = [super initWithFrame:frameRect] ){ + _trees = [[NSMutableArray alloc]init]; + _selectionIndexes = [[NSIndexSet alloc]init]; + _fontName = [[[NSUserDefaults standardUserDefaults] objectForKey:@"MFDefaultTreeFontName"]copy]; + _fontSize = [[[NSUserDefaults standardUserDefaults] objectForKey:@"MFDefaultTreeFontSize"]floatValue]; + _font = [NSFont fontWithName:_fontName size:_fontSize]; + _taxonSpacing = _taxonSpacingDefault = 10; + _attsDict = [[NSMutableDictionary alloc] init]; + [_attsDict setObject:_font forKey:NSFontAttributeName]; + _attributedString = [[NSMutableAttributedString alloc] initWithString:@"A" attributes:_attsDict]; + _taxonHeight = [_attributedString size].height;// [_font capHeight]; + _backgroundColor = [NSColor whiteColor]; + _taxaShown = YES; + _scaleBarShown = YES; + _showXAxis = NO; + + _graphicTaxa = [[NSMutableDictionary alloc]init]; + _graphicBranch = [[NSMutableDictionary alloc]init]; + _graphicBranchLabel = [[NSMutableDictionary alloc]init]; + _graphicVerticalBranch = [[NSMutableDictionary alloc]init]; + + // Horizontal + _rootOffset = 10.0f; + _rootLength = _rootOffset/2.0; + _rightMargin = 2.0f; + _branchTipSpace = 2.0f; + _maximumNameLength = 0; + + // Vertical + _bottomSpace = 10; //space between the last taxon and the edge of the view + + // Scale bar + _scaleBarWidth = 0; + _scaleBarFontSize = _fontSize; + NSFont *font = [NSFont fontWithName:_fontName size:_scaleBarFontSize]; + NSMutableDictionary *attsDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, nil]; + _scaleBarValue = [[NSMutableAttributedString alloc] initWithString:@"A" attributes:attsDict]; + _scaleBarFontHeight = [font capHeight]; + _scaleBarSpace = 10; + + _observingSuperview = NO; + + _selectedNodes = [[NSArray array]retain]; + _branchAttribute = [[NSString alloc]init]; + + [self createTrackingArea]; + } + return self; +} + + +- (void)dealloc{ + NSLog(@"MFTreeView dealloc"); + if (_observingSuperview) { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + } + [_trees release]; + [_attsDict release]; + [_graphicTaxa release]; + [_trackingArea release]; + [_scaleBarValue release]; + [_graphicBranch release]; + [_selectedNodes release]; + [_branchAttribute release]; + [_selectionIndexes release]; + [_attributedString release]; + [_graphicBranchLabel release]; + [_graphicVerticalBranch release]; + [super dealloc]; +} + ++ (void)initialize{ + [self exposeBinding:@"trees"]; + [self exposeBinding:@"selectedNodes"]; +} + +- (BOOL)isFlipped{ + return YES; +} + +- (BOOL)acceptsFirstResponder { + return YES; +} + + +-(void)setTrees:(NSMutableArray *)trees{ + [_trees release]; + _trees = [trees retain]; +} + +-(NSArray*)trees{ + return _trees; +} + +-(void)setSelectionIndexes:(NSIndexSet*)indexes{ + [_selectionIndexes release]; + _selectionIndexes = [indexes copy]; + + if( !_observingSuperview ){ + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(superviewResized:) + name:NSViewFrameDidChangeNotification object:[self superview]]; + _observingSuperview = YES; + } + + // first time + if( [_graphicBranch count] == 0 ){ + [self setUpSelectedTree]; + } +} + +-(NSIndexSet*)selectionIndexes{ + return _selectionIndexes; +} + +- (void)setUpSelectedTree{ + MFTree *theTree = [_trees objectAtIndex:[_selectionIndexes firstIndex]]; + + [self updateFrameSize:theTree]; + [self calculateBranchScaler:theTree]; + + if( _scaleBarShown ){ + [self updateScaleBar]; + } + [self createGraphicsForTree:theTree]; + [self updateGraphicsForTree:theTree]; + + [self setNeedsDisplay:YES]; +} + +-(void)updateScaleBar{ + _scaleBarWidth = self.frame.size.width/10.0; + CGFloat w = _scaleBarWidth/_treeScaler; + NSString *value; + if ( w < 1e-3) { + value = [NSString stringWithFormat:@"%.2e",w]; + } + else { + value = [NSString stringWithFormat:@"%.4f",w]; + } + [[_scaleBarValue mutableString] setString:value]; +} + + +-(void)calculateBranchScaler:(MFTree*)tree{ + _treeScaler = CGFLOAT_MAX; + _maximumDepth = 0; + _maximumNameLength = 0; + + NSRect bounds = [self frame]; + CGFloat width = bounds.size.width - _rightMargin - _rootOffset; + + + if( _taxaShown ){ + width -= _branchTipSpace; + + [tree enumerateNodesWithAlgorithm:MFTreeTraverseAlgorithmPreorder usingBlock:^(MFNode *node){ + if( [node isRoot] ){ + [node setAttribute:[NSNumber numberWithFloat:0] forKey:MFDepthKey]; + } + else { + CGFloat depth = [[[node parent] attributeForKey:MFDepthKey]floatValue] + [node branchLength]; + [node setAttribute:[NSNumber numberWithFloat:depth] forKey:MFDepthKey]; + + if( [node isLeaf] ){ + NSMutableString *mutString = [_attributedString mutableString]; + [mutString setString:node.name]; + + CGFloat s = (width - [_attributedString size].width)/depth; + if(s < _treeScaler){ + _treeScaler = s; + } + + if( [_attributedString size].width > _maximumNameLength ){ + _maximumNameLength = [_attributedString size].width; + } + } + } + }]; + } + else { + [tree enumerateNodesWithAlgorithm:MFTreeTraverseAlgorithmPreorder usingBlock:^(MFNode *node){ + if( [node isRoot] ){ + [node setAttribute:[NSNumber numberWithFloat:0] forKey:MFDepthKey]; + } + else { + CGFloat depth = [[[node parent] attributeForKey:MFDepthKey]floatValue] + [node branchLength]; + [node setAttribute:[NSNumber numberWithFloat:depth] forKey:MFDepthKey]; + + if( [node isLeaf] ){ + if( depth > _maximumDepth ){ + _maximumDepth = depth; + } + } + + } + }]; + _treeScaler = width/_maximumDepth; + } +} + +-(void)createGraphicsForTree:(MFTree*)tree{ + + NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:_font,NSFontAttributeName, nil]; + + [tree enumerateNodesWithAlgorithm:MFTreeTraverseAlgorithmPostorder usingBlock:^(MFNode *node){ + + + if( [node isLeaf] ){ + // Taxon text + MFTextGraphic *textGraphic = [[MFTextGraphic alloc]initWithString:[node name] attributes:attrs]; + [_graphicTaxa setObject:textGraphic forKey:[NSValue valueWithNonretainedObject:node]]; + [textGraphic release]; + + // Taxon branch + MFLineGraphic *horizontal = [[MFLineGraphic alloc]init]; + [_graphicBranch setObject:horizontal forKey:[NSValue valueWithNonretainedObject:node]]; + [horizontal release]; + } + else { + // Node branch + MFLineGraphic *horizontal = [[MFLineGraphic alloc]init]; + [_graphicBranch setObject:horizontal forKey:[NSValue valueWithNonretainedObject:node]]; + [horizontal release]; + + // Vertical branch + MFLineGraphic *vertical = [[MFLineGraphic alloc]init]; + [_graphicVerticalBranch setObject:vertical forKey:[NSValue valueWithNonretainedObject:node]]; + [vertical release]; + } + + // Always init with a non-empty string otherwise the initial font is ignored when we + // set another string in nstextstorage + MFTextGraphic *horizontalLabel = [[MFTextGraphic alloc]initWithString:@" " attributes:attrs]; + [_graphicBranchLabel setObject:horizontalLabel forKey:[NSValue valueWithNonretainedObject:node]]; + [horizontalLabel release]; + }]; +} + + +- (void)updateBranchAttributes{ + if ( ![_branchAttribute isEqualToString:@""] ) { + MFTree *tree = [_trees objectAtIndex:[_selectionIndexes firstIndex]]; + [tree enumerateNodesWithAlgorithm:MFTreeTraverseAlgorithmPostorder usingBlock:^(MFNode *node){ + + + if( [_branchAttribute isEqualToString:@"Branch Length"] ){ + // Node branch + MFLineGraphic *horizontal = [_graphicBranch objectForKey:[NSValue valueWithNonretainedObject:node]]; + + MFTextGraphic *textGraphic = [_graphicBranchLabel objectForKey:[NSValue valueWithNonretainedObject:node]]; + + NSString *value = [NSString stringWithFormat:@"%f", [node branchLength] ]; + [textGraphic setXPosition:horizontal.xPosition]; + [textGraphic setYPosition:horizontal.yPosition]; + [[textGraphic contents]beginEditing]; + [[textGraphic contents]replaceCharactersInRange:NSMakeRange(0, [[textGraphic contents ]length]) withString:value]; + [[textGraphic contents]endEditing]; + + } + else if( [_branchAttribute isEqualToString:@"Name"] ){ + // Node branch + MFLineGraphic *horizontal = [_graphicBranch objectForKey:[NSValue valueWithNonretainedObject:node]]; + + MFTextGraphic *textGraphic = [_graphicBranchLabel objectForKey:[NSValue valueWithNonretainedObject:node]]; + + NSString *value = node.name; + [textGraphic setXPosition:horizontal.xPosition]; + [textGraphic setYPosition:horizontal.yPosition]; + [[textGraphic contents]beginEditing]; + [[textGraphic contents]replaceCharactersInRange:NSMakeRange(0, [[textGraphic contents ]length]) withString:value]; + [[textGraphic contents]endEditing]; + + } + else if ( [[node attributes]objectForKey:_branchAttribute]) { + // Node branch + MFLineGraphic *horizontal = [_graphicBranch objectForKey:[NSValue valueWithNonretainedObject:node]]; + + MFTextGraphic *textGraphic = [_graphicBranchLabel objectForKey:[NSValue valueWithNonretainedObject:node]]; + + NSString *value = [[node attributes]objectForKey:_branchAttribute]; + [textGraphic setXPosition:horizontal.xPosition]; + [textGraphic setYPosition:horizontal.yPosition]; + [[textGraphic contents]beginEditing]; + [[textGraphic contents]replaceCharactersInRange:NSMakeRange(0, [[textGraphic contents ]length]) withString:value]; + [[textGraphic contents]endEditing]; + } + else { + MFTextGraphic *textGraphic = [_graphicBranchLabel objectForKey:[NSValue valueWithNonretainedObject:node]]; + [[textGraphic contents]beginEditing]; + [[textGraphic contents]replaceCharactersInRange:NSMakeRange(0, [[textGraphic contents ]length]) withString:@" "]; + [[textGraphic contents]endEditing]; + } + }]; + } +} +// We don't need to update the other graphic objects when we use this method +- (void)showBranchAttribute:(NSString*)attribute{ + if ( ![attribute isEqualToString:_branchAttribute] ) { + [_branchAttribute release]; + _branchAttribute = [attribute retain]; + + [self updateBranchAttributes]; + + [self setNeedsDisplay:YES]; + } +} + +- (void)updateGraphicsForTree:(MFTree*)tree{ + + CGFloat yspace = _taxonSpacing; + + __block CGFloat y = _taxonHeight; + [tree enumerateNodesWithAlgorithm:MFTreeTraverseAlgorithmPostorder usingBlock:^(MFNode *node){ + + CGFloat distanceFromRoot = [[node attributeForKey:MFDepthKey] floatValue]; + + if( [node isLeaf] ){ + CGFloat x1 = distanceFromRoot * _treeScaler + _rootOffset; + CGFloat x2 = x1-[node branchLength]*_treeScaler; + + if( _taxaShown ){ + // Taxon text + MFTextGraphic *textGraphic = [_graphicTaxa objectForKey:[NSValue valueWithNonretainedObject:node]]; + + [textGraphic setXPosition:x1]; + [textGraphic setYPosition:y-_taxonHeight]; + } + + // Taxon branch + MFLineGraphic *horizontal = [_graphicBranch objectForKey:[NSValue valueWithNonretainedObject:node]]; + CGFloat y2 = y-_taxonHeight/2; + [horizontal setBeginPoint:NSMakePoint(x1, y2)]; + [horizontal setEndPoint:NSMakePoint(x2, y2)]; + + // Branch attribute + if( ![_branchAttribute isEqualToString:@""] ){ + MFTextGraphic *textGraphic = [_graphicBranchLabel objectForKey:[NSValue valueWithNonretainedObject:node]]; + + [textGraphic setXPosition:x2]; + [textGraphic setYPosition:y2]; + } + + y += yspace + _taxonHeight; + + } + else { + // Node branch + MFLineGraphic *horizontal = [_graphicBranch objectForKey:[NSValue valueWithNonretainedObject:node]]; + MFLineGraphic *son1 = [_graphicBranch objectForKey:[NSValue valueWithNonretainedObject:[node childAtIndex:0]]]; + MFLineGraphic *son2 = [_graphicBranch objectForKey:[NSValue valueWithNonretainedObject:[node childAtIndex:1]]]; + CGFloat yson1 = [son1 endPoint].y; + CGFloat yson2 = [son2 endPoint].y; + CGFloat y = yson1+(yson2-yson1)/2.0; + + CGFloat x1 = distanceFromRoot *_treeScaler + _rootOffset; + CGFloat x2 = x1-[node branchLength]*_treeScaler; + + + if ( [node isRoot] ) { + x2 = x1-_rootLength; + } + + [horizontal setBeginPoint:NSMakePoint(x1, y)]; + [horizontal setEndPoint:NSMakePoint(x2, y)]; + + // Branch attribute + if( ![_branchAttribute isEqualToString:@""] ){ + MFTextGraphic *textGraphic = [_graphicBranchLabel objectForKey:[NSValue valueWithNonretainedObject:node]]; + + [textGraphic setXPosition:x2]; + [textGraphic setYPosition:y]; + } + + // Vertical branch + MFLineGraphic *vertical = [_graphicVerticalBranch objectForKey:[NSValue valueWithNonretainedObject:node]];//[[MFBranchGraphic alloc]init]; + [vertical setBeginPoint:NSMakePoint(x1, yson1)]; + [vertical setEndPoint:NSMakePoint(x1, yson2)]; + } + }]; +} + + +- (void)reloadData{ + + MFTree *theTree = [_trees objectAtIndex:[_selectionIndexes firstIndex]]; + [self updateFrameSize:theTree]; + [self calculateBranchScaler:theTree]; + + if( _scaleBarShown ){ + [self updateScaleBar]; + } + [self updateBranchAttributes]; + [self updateGraphicsForTree:theTree]; + + [self setNeedsDisplay:YES]; +} + +#pragma mark *** Drawing *** + +- (void)drawRect:(NSRect)dirtyRect{ + + // Draw background + [_backgroundColor set]; + NSRectFill(dirtyRect); + + if([_trees count] == 0 )return; + + if( _showXAxis ){ + + } + + if( _scaleBarShown ){ + CGFloat x = self.frame.size.width * 0.3; + MFTree *theTree = [_trees objectAtIndex:[_selectionIndexes firstIndex]]; + CGFloat y = (_taxonSpacing + _taxonHeight) * [theTree taxonCount]; + + NSPoint p = NSMakePoint(x, y); + if ( NSPointInRect(p, dirtyRect)) { + [_scaleBarValue drawAtPoint:p]; + + y += _scaleBarSpace + _scaleBarFontHeight; + NSBezierPath *path = [NSBezierPath bezierPath]; + [path moveToPoint: NSMakePoint(x, y)]; + [path lineToPoint:NSMakePoint(x+_scaleBarWidth, y)]; + [path stroke]; + } + } + + // Draw taxa + if( _taxaShown ){ + + for (NSValue *vnode in [_graphicTaxa allKeys]) { + MFTextGraphic *graphic = [_graphicTaxa objectForKey:vnode]; + MFNode *node = [vnode nonretainedObjectValue]; + + NSRect graphicDrawingBounds = [graphic drawingBounds]; + NSGraphicsContext *currentContext = [NSGraphicsContext currentContext]; + + if (NSIntersectsRect(dirtyRect, graphicDrawingBounds)) { + + [currentContext saveGraphicsState]; + BOOL isSelected = _selectionMode == 0 && [_selectedNodes indexOfObject:node] != NSNotFound; + + [graphic drawContentsInView:self isSelected:isSelected]; + + [currentContext restoreGraphicsState]; + } + } + } + + // Draw branches + for (NSValue *vnode in [_graphicBranch allKeys]) { + MFGraphic *graphic = [_graphicBranch objectForKey:vnode]; + MFNode *node = [vnode nonretainedObjectValue]; + + NSRect graphicDrawingBounds = [graphic drawingBounds]; + NSGraphicsContext *currentContext = [NSGraphicsContext currentContext]; + + if (NSIntersectsRect(dirtyRect, graphicDrawingBounds)) { + + [currentContext saveGraphicsState]; + + [NSBezierPath clipRect:graphicDrawingBounds]; + + BOOL isSelected = _selectionMode != 0 && [_selectedNodes indexOfObject:node] != NSNotFound; + [graphic drawContentsInView:self isSelected:isSelected]; + + [currentContext restoreGraphicsState]; + } + } + + // Draw vertical branches + for (MFGraphic *graphic in [_graphicVerticalBranch allValues]) { + NSRect graphicDrawingBounds = [graphic drawingBounds]; + NSGraphicsContext *currentContext = [NSGraphicsContext currentContext]; + + if (NSIntersectsRect(dirtyRect, graphicDrawingBounds)) { + + [currentContext saveGraphicsState]; + [NSBezierPath clipRect:graphicDrawingBounds]; + [graphic drawContentsInView:self isSelected:NO]; + + [currentContext restoreGraphicsState]; + } + } + + // Draw branch attribute + if ( ![_branchAttribute isEqualToString:@""]) { + for (MFTextGraphic *graphic in [_graphicBranchLabel allValues]) { + if( [[[graphic contents] string] isEmpty] ) continue; + + NSRect graphicDrawingBounds = [graphic drawingBounds]; + NSGraphicsContext *currentContext = [NSGraphicsContext currentContext]; + + if (NSIntersectsRect(dirtyRect, graphicDrawingBounds)) { + + [currentContext saveGraphicsState]; + [NSBezierPath clipRect:graphicDrawingBounds]; + [graphic drawContentsInView:self isSelected:NO]; + + [currentContext restoreGraphicsState]; + } + } + } + + // If the user is in the middle of selecting draw the selection rectangle. + if (!NSEqualRects(_marqueeSelectionBounds, NSZeroRect)) { + [[NSColor knobColor] set]; + NSFrameRect(_marqueeSelectionBounds); + } + +} + +#pragma mark *** Resizing Methods **** + +- (void)superviewResized:(NSNotification *)notification{ + if( [_selectionIndexes count] > 0 ){ + NSSize treeviewSize = self.frame.size; + MFTree *theTree = [_trees objectAtIndex:[_selectionIndexes firstIndex]]; + [self updateFrameSize:theTree]; + + if (treeviewSize.width != self.frame.size.width) { + [self calculateBranchScaler:theTree]; + if(_scaleBarShown){ + [self updateScaleBar]; + } + } + [self updateGraphicsForTree:theTree]; + + [self setNeedsDisplay:YES]; + } +} + +- (void)updateFrameSize:(MFTree*)theTree{ + NSSize superviewSize = [[self superview]frame].size; + NSSize treeViewSize = [[self superview]frame].size; + + CGFloat proposedHeight = _taxonSpacing*([theTree taxonCount]-1) + _taxonHeight*[theTree taxonCount] + _bottomSpace; + if( _scaleBarShown ){ + proposedHeight += _scaleBarFontHeight + _taxonSpacing + _scaleBarSpace; + } + + if( superviewSize.height > proposedHeight ){ + if( _scaleBarShown ){ + _taxonSpacing = (superviewSize.height - [theTree taxonCount]*_taxonHeight-_scaleBarFontHeight-_scaleBarSpace-_bottomSpace)/[theTree taxonCount]; + } + else { + _taxonSpacing = (superviewSize.height - [theTree taxonCount]*_taxonHeight-_bottomSpace)/ ([theTree taxonCount]-1); + } + } + else { + treeViewSize.height = proposedHeight; + } + + // Width + CGFloat width = _maximumNameLength + _rootOffset + _rightMargin + _branchTipSpace + 10; + if( width >= superviewSize.width ){ + treeViewSize.width = width; + } + else if( treeViewSize.width < superviewSize.width ){ + treeViewSize.width = superviewSize.width; + } + + if( !NSEqualSizes([self frame].size, treeViewSize) ){ + [self setFrameSize:treeViewSize]; + } +} + +// inc can be negative too +-(void)incrementTaxonSpacing:(CGFloat)inc{ + + MFTree *theTree = [_trees objectAtIndex:[_selectionIndexes firstIndex]]; + NSSize superviewSize = [[self superview]frame].size; + + CGFloat proposedHeight = (_taxonSpacing+inc)*([theTree taxonCount]-1) + _taxonHeight*[theTree taxonCount] + _bottomSpace; + if( _scaleBarShown ){ + proposedHeight += _scaleBarFontHeight + _taxonSpacing + inc + _scaleBarSpace; + } + + // The proposed increment creates a TreeView bigger than its superview so we accept this change + if( superviewSize.height < proposedHeight ){ + _taxonSpacing += inc; + NSSize size = self.frame.size; + size.height = proposedHeight; + [self setFrameSize:size]; + + [self updateGraphicsForTree:theTree]; + [self setNeedsDisplay:YES]; + } + // The proposed increment would create a TreeView smaller than its superview and we don't want this + else { + CGFloat taxonSpacing; + if( _scaleBarShown ){ + taxonSpacing = (superviewSize.height - [theTree taxonCount]*_taxonHeight-_scaleBarFontHeight-_scaleBarSpace-_bottomSpace)/[theTree taxonCount]; + } + else { + taxonSpacing = (superviewSize.height - [theTree taxonCount]*_taxonHeight-_bottomSpace)/ ([theTree taxonCount]-1); + } + if( taxonSpacing != _taxonSpacing ){ + _taxonSpacing = taxonSpacing; + [self setFrameSize:superviewSize]; + + [self updateGraphicsForTree:theTree]; + [self setNeedsDisplay:YES]; + } + } +} + +- (void)incrementWidth:(CGFloat)inc{ + MFTree *theTree = [_trees objectAtIndex:[_selectionIndexes firstIndex]]; + NSSize superviewSize = [[self superview]frame].size; + NSSize treeviewSize = self.frame.size; + + if ( treeviewSize.width+inc > superviewSize.width) { + treeviewSize.width += inc; + } + else if( treeviewSize.width != superviewSize.width ){ + treeviewSize.width = superviewSize.width; + } + + if ( [self frame].size.width != treeviewSize.width ) { + [self setFrameSize:treeviewSize]; + [self calculateBranchScaler:theTree]; + + if( _scaleBarShown ){ + [self updateScaleBar]; + } + [self updateGraphicsForTree:theTree]; + [self setNeedsDisplay:YES]; + } +} + +- (void)setDefaultSize{ + MFTree *theTree = [_trees objectAtIndex:[_selectionIndexes firstIndex]]; + NSSize superviewSize = [[self superview]frame].size; + NSSize treeviewSize = [self frame].size; + + // Height + CGFloat proposedHeight = _taxonSpacingDefault*([theTree taxonCount]-1) + _taxonHeight*[theTree taxonCount] + _bottomSpace; + if( _scaleBarShown ){ + proposedHeight += _scaleBarFontHeight + _taxonSpacingDefault + _scaleBarSpace; + } + + if( superviewSize.height < proposedHeight ){ + _taxonSpacing = _taxonSpacingDefault; + treeviewSize.height = proposedHeight; + } + else { + if( _scaleBarShown ){ + _taxonSpacing = (superviewSize.height - [theTree taxonCount]*_taxonHeight-_scaleBarFontHeight-_scaleBarSpace-_bottomSpace)/[theTree taxonCount]; + } + else { + _taxonSpacing = (superviewSize.height - [theTree taxonCount]*_taxonHeight-_bottomSpace)/ ([theTree taxonCount]-1); + } + treeviewSize.height = superviewSize.height; + } + + // Width + CGFloat width = _maximumNameLength + _rootOffset + _rightMargin + _branchTipSpace + 10; + if( width >= superviewSize.width ){ + treeviewSize.width = width; + } + else { + treeviewSize.width = superviewSize.width; + } + + if ( !NSEqualSizes([self frame].size, treeviewSize) ) { + [self setFrameSize:treeviewSize]; + [self calculateBranchScaler:theTree]; + + if( _scaleBarShown ){ + [self updateScaleBar]; + } + [self updateGraphicsForTree:theTree]; + [self setNeedsDisplay:YES]; + } +} + +- (void)setSelectedNodes:(NSArray*)nodes{ + + if( nodes != _selectedNodes ){ + [_selectedNodes release]; + _selectedNodes = [nodes retain]; + [self setNeedsDisplay:YES]; + } +} + +- (NSArray*)selectedNodes{ + return _selectedNodes; +} + +- (void)setSelectionMode:(NSInteger)mode{ + if ( mode != _selectionMode) { + _selectionMode = mode; + [self setNeedsDisplay:YES]; + } +} + +-(NSInteger)selectionMode{ + return _selectionMode; +} + +-(BOOL)taxaShown{ + return _taxaShown; +} + +-(void)setTaxaShown:(BOOL)isTaxaShown{ + if( isTaxaShown != _taxaShown){ + _taxaShown = isTaxaShown; + MFTree *theTree = [_trees objectAtIndex:[_selectionIndexes firstIndex]]; + [self calculateBranchScaler:theTree]; + if(_scaleBarShown){ + [self updateScaleBar]; + } + [self updateGraphicsForTree:theTree]; + [self setNeedsDisplay:YES]; + } +} + +-(BOOL)scaleBarShown{ + return _scaleBarShown; +} + +-(void)setScalBarShown:(BOOL)isScaleBarShown{ + if( _scaleBarShown != isScaleBarShown){ + _scaleBarShown = isScaleBarShown; + MFTree *theTree = [_trees objectAtIndex:[_selectionIndexes firstIndex]]; + [self calculateBranchScaler:theTree]; + if(_scaleBarShown){ + [self updateScaleBar]; + } + [self updateGraphicsForTree:theTree]; + [self setNeedsDisplay:YES]; + } +} + + +- (NSArray *)nodesOfGraphics:(NSDictionary*)graphics intersectingRect:(NSRect)rect { + NSMutableArray *nodeArrayToReturn = [NSMutableArray array]; + for ( NSValue *vnode in [graphics allKeys] ) { + MFGraphic *graphic = [graphics objectForKey:vnode]; + if (NSIntersectsRect(rect, [graphic drawingBounds])) { + [nodeArrayToReturn addObject:[vnode nonretainedObjectValue]]; + } + } + return nodeArrayToReturn; +} + +- (NSArray *)nodesOfGraphics:(NSDictionary*)graphics atPoint:(NSPoint)point { + NSMutableArray *nodeArrayToReturn = [NSMutableArray array]; + for ( NSValue *vnode in [graphics allKeys] ) { + MFGraphic *graphic = [graphics objectForKey:vnode]; + if (NSPointInRect(point, [graphic drawingBounds])) { + [nodeArrayToReturn addObject:[vnode nonretainedObjectValue]]; + } + } + return nodeArrayToReturn; +} + +- (BOOL)setColorForSelected:(NSColor*)color{ + BOOL done = NO; + + if ( [_selectedNodes count] > 0 ) { + if ( _selectionMode == 0 ) { + for (NSValue *vnode in [_graphicTaxa allKeys]) { + MFNode *node = [vnode nonretainedObjectValue]; + + if ( [_selectedNodes indexOfObject:node] != NSNotFound ) { + MFGraphic *graphic = [_graphicTaxa objectForKey:vnode]; + [graphic setColor:color]; + } + } + //[[_graphicTaxa allValues] makeObjectsPerformSelector:@selector(setColor:)withObject:color]; + } + else{ + for (NSValue *vnode in [_graphicBranch allKeys]) { + MFNode *node = [vnode nonretainedObjectValue]; + if ( [_selectedNodes indexOfObject:node] != NSNotFound ) { + MFGraphic *graphic = [_graphicBranch objectForKey:vnode]; + [graphic setColor:color]; + } + } + } + [self setNeedsDisplay:YES]; + } + return done; +} + +- (void)setFontSize:(CGFloat)fontSize{ + _fontSize = fontSize; + + _font = [NSFont fontWithName:_fontName size:_fontSize]; + [_attsDict setObject:_font forKey:NSFontAttributeName]; + [_attributedString setAttributes:_attsDict range:NSMakeRange(0, [_attributedString length])]; + + MFTree *theTree = [_trees objectAtIndex:[_selectionIndexes firstIndex]]; + + [self updateFrameSize:theTree]; + [self calculateBranchScaler:theTree]; + + if( _scaleBarShown ){ + [self updateScaleBar]; + } + + for (NSValue *vnode in [_graphicTaxa allKeys]) { + MFTextGraphic *graphic = [_graphicTaxa objectForKey:vnode]; + [[graphic contents]setFont:_font]; + } + [self updateGraphicsForTree:theTree]; + [self setNeedsDisplay:YES]; +} + +#pragma mark *** Tree Manipulation *** + +- (void)rotateSelectedBranch{ + if( [_selectedNodes count] == 1 && _selectionMode > 0 ){ + MFNode *node = [_selectedNodes objectAtIndex:0]; + [node rotate]; + MFTree *theTree = [_trees objectAtIndex:[_selectionIndexes firstIndex]]; + [self updateBranchAttributes]; + [self updateGraphicsForTree:theTree]; + + [self setNeedsDisplay:YES]; + } +} + +- (void)rootAtSelectedBranch{ + if( [_selectedNodes count] == 1 && _selectionMode > 0 ){ + MFTree *theTree = [_trees objectAtIndex:[_selectionIndexes firstIndex]]; + [theTree setRootAtNode:[_selectedNodes objectAtIndex:0]]; + [self updateFrameSize:theTree]; + [self calculateBranchScaler:theTree]; + + if( _scaleBarShown ){ + [self updateScaleBar]; + } + [self updateBranchAttributes]; + [self updateGraphicsForTree:theTree]; + [self setNeedsDisplay:YES]; + } +} + + +#pragma mark *** Mouse Event Handling *** + +- (void)mouseDown:(NSEvent *)event { + + if( [_trees count] == 0 ) return; + + NSPoint originalMouseLocation = [self convertPoint:[event locationInWindow] fromView:nil]; + while ( [event type] != NSLeftMouseUp ) { + event = [[self window] nextEventMatchingMask:(NSLeftMouseDraggedMask | NSLeftMouseUpMask)]; + [self autoscroll:event]; + NSPoint currentMouseLocation = [self convertPoint:[event locationInWindow] fromView:nil]; + + // Figure out a new a selection rectangle based on the mouse location. + NSRect newMarqueeSelectionBounds = NSMakeRect(fmin(originalMouseLocation.x, currentMouseLocation.x), fmin(originalMouseLocation.y, currentMouseLocation.y), fabs(currentMouseLocation.x - originalMouseLocation.x), fabs(currentMouseLocation.y - originalMouseLocation.y)); + + if( NSEqualPoints(originalMouseLocation, currentMouseLocation) ){ + NSArray *nodes; + if( _selectionMode == 0 ){ + nodes = [self nodesOfGraphics: _graphicTaxa atPoint:currentMouseLocation]; + } + else{ + nodes = [self nodesOfGraphics: _graphicBranch atPoint:currentMouseLocation]; + } + NSMutableArray *mut = [NSMutableArray array]; + if( [nodes count] > 0 ){ + if ( [_selectedNodes count] > 0 && [event modifierFlags] & NSCommandKeyMask ) { + [mut addObjectsFromArray:_selectedNodes]; + for (NSObject *node in nodes) { + if ( [_selectedNodes indexOfObject:node] == NSNotFound ) { + [mut addObject:node]; + } + else { + [mut removeObject:node]; + } + } + } + else if ( [_selectedNodes count] > 0 ) { + + for (MFNode *node in nodes) { + if ( [_selectedNodes indexOfObject:node] == NSNotFound ) { + [mut addObject:node]; + } + } + } + else{ + [mut addObjectsFromArray:nodes]; + } + } + [self setSelectedNodes:mut]; + [self propagateValue:mut forBinding:@"selectedNodes"]; + } + else if (!NSEqualRects(newMarqueeSelectionBounds, _marqueeSelectionBounds)) { + + // Erase the old selection rectangle and draw the new one. + [self setNeedsDisplayInRect:_marqueeSelectionBounds]; + _marqueeSelectionBounds = newMarqueeSelectionBounds; + [self setNeedsDisplayInRect:_marqueeSelectionBounds]; + + NSArray *nodesOfGraphicsInRubberBand; + + if( _selectionMode == 0 ){ + nodesOfGraphicsInRubberBand = [self nodesOfGraphics:_graphicTaxa intersectingRect:_marqueeSelectionBounds]; + } + else { + nodesOfGraphicsInRubberBand = [self nodesOfGraphics:_graphicBranch intersectingRect:_marqueeSelectionBounds]; + } + [self setSelectedNodes:nodesOfGraphicsInRubberBand]; + [self propagateValue:nodesOfGraphicsInRubberBand forBinding:@"selectedNodes"]; + } + } + + // Schedule the drawing of the place wherew the rubber band isn't anymore. + if( !NSIsEmptyRect(_marqueeSelectionBounds) ){ + [self setNeedsDisplayInRect:_marqueeSelectionBounds]; + // Make it not there. + _marqueeSelectionBounds = NSZeroRect; + } +} + +- (void)mouseMoved:(NSEvent *)theEvent { +// NSLog(@"mouse moved"); +// NSPoint point = [self convertPoint:[theEvent locationInWindow] fromView:nil]; +// +// for ( NSValue *vnode in [_graphicTaxa allKeys] ) { +// MFGraphic *graphic = [_graphicTaxa objectForKey:vnode]; +// MFNode *node = [vnode nonretainedObjectValue]; +// if (NSPointInRect(point, [graphic drawingBounds])) { +// NSLog(@"TAXA %@",node); +// } +// } +// +// for ( NSValue *vnode in [_graphicBranch allKeys] ) { +// MFGraphic *graphic = [_graphicBranch objectForKey:vnode]; +// MFNode *node = [vnode nonretainedObjectValue]; +// if (NSPointInRect(point, [graphic drawingBounds])) { +// NSLog(@"BRANCH %@",node); +// } +// } +// +// for ( NSValue *vnode in [_graphicVerticalBranch allKeys] ) { +// MFGraphic *graphic = [_graphicVerticalBranch objectForKey:vnode]; +// MFNode *node = [vnode nonretainedObjectValue]; +// if (NSPointInRect(point, [graphic drawingBounds])) { +// NSLog(@"BRANCH V %@",node); +// } +// } +} + +// http://stackoverflow.com/questions/8979639/mouseexited-isnt-called-when-mouse-leaves-trackingarea-while-scrolling#comment34491426_9107224 +- (void) createTrackingArea{ + int opts = (NSTrackingMouseEnteredAndExited|NSTrackingMouseMoved | NSTrackingActiveAlways); + _trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds] + options:opts + owner:self + userInfo:nil]; + [self addTrackingArea:_trackingArea]; + + NSPoint mouseLocation = [[self window] mouseLocationOutsideOfEventStream]; + mouseLocation = [self convertPoint: mouseLocation + fromView: nil]; + + if (NSPointInRect(mouseLocation, [self bounds]) ){ + [self mouseEntered: nil]; + } + else{ + [self mouseExited: nil]; + } +} + +- (void)updateTrackingAreas { + [self removeTrackingArea:_trackingArea]; + [_trackingArea release]; + [self createTrackingArea]; + [super updateTrackingAreas]; +} + +@end diff --git a/Seqotron/MFTreeWindow.xib b/Seqotron/MFTreeWindow.xib new file mode 100644 index 0000000..e553835 --- /dev/null +++ b/Seqotron/MFTreeWindow.xib @@ -0,0 +1,246 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Item 1 + Item 2 + Item 3 + + + + + + + + + + + + + + + +YnBsaXN0MDDUAQIDBAUGW1xYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoK8QFwcI +ERYbHCc0NTY3ODk6O0JDRElMT1VYVSRudWxs1AkKCwwNDg8QViRjbGFzc1xOU0ltYWdlRmxhZ3NWTlNS +ZXBzV05TQ29sb3KAFhIAwwAAgAKAFNISCRMVWk5TLm9iamVjdHOhFIADgBPSEgkXGqIYGYAEgAWAEhAA +1h0eHwkgISIbIyQlJlZOU1NpemVfEA9OU0JpdHNQZXJTYW1wbGVfEBBOU0NvbG9yU3BhY2VOYW1lWk5T +SGFzQWxwaGFfEBROU0NvcmVVSUltYWdlT3B0aW9uc4APgBCAEQmABtMoEgkpLjNXTlMua2V5c6QqKywt +gAeACIAJgAqkLxgxMoALgASADIANgA5Uc2l6ZVV2YWx1ZVVzdGF0ZVZ3aWRnZXRXcmVndWxhclZub3Jt +YWxYY2hlY2tib3jSPD0+P1okY2xhc3NuYW1lWCRjbGFzc2VzXxATTlNNdXRhYmxlRGljdGlvbmFyeaM+ +QEFcTlNEaWN0aW9uYXJ5WE5TT2JqZWN0WHsxOCwgMTh9XxAZTlNDYWxpYnJhdGVkUkdCQ29sb3JTcGFj +ZdI8PUVGXxAQTlNDb3JlVUlJbWFnZVJlcKNHSEFfEBBOU0NvcmVVSUltYWdlUmVwWk5TSW1hZ2VSZXDS +PD1KS1dOU0FycmF5okpB0jw9TU5eTlNNdXRhYmxlQXJyYXmjTUpB01BRCVJTVFdOU1doaXRlXE5TQ29s +b3JTcGFjZUQwIDAAEAOAFdI8PVZXV05TQ29sb3KiVkHSPD1ZWldOU0ltYWdlollBXxAPTlNLZXllZEFy +Y2hpdmVy0V1eVHJvb3SAAQAIABEAGgAjAC0AMgA3AFEAVwBgAGcAdAB7AIMAhQCKAIwAjgCTAJ4AoACi +AKQAqQCsAK4AsACyALQAwQDIANoA7QD4AQ8BEQETARUBFgEYAR8BJwEsAS4BMAEyATQBOQE7AT0BPwFB +AUMBSAFOAVQBWwFjAWoBcwF4AYMBjAGiAaYBswG8AcUB4QHmAfkB/QIQAhsCIAIoAisCMAI/AkMCSgJS +Al8CZAJmAmgCbQJ1AngCfQKFAogCmgKdAqIAAAAAAAACAQAAAAAAAABfAAAAAAAAAAAAAAAAAAACpA + + + + + + diff --git a/Seqotron/MFTreeWindowController.h b/Seqotron/MFTreeWindowController.h new file mode 100644 index 0000000..c14293c --- /dev/null +++ b/Seqotron/MFTreeWindowController.h @@ -0,0 +1,62 @@ +// +// MFTreeDocument.h +// Seqotron +// +// Created by Mathieu Fourment on 14/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFTreeView.h" + +@interface MFTreeWindowController : NSWindowController { + IBOutlet NSView *_arrowsView; + IBOutlet NSScrollView *_scrollView; + IBOutlet NSArrayController *_treesController; + IBOutlet MFTreeView *_treeView; + IBOutlet NSSearchField *_searchField; + IBOutlet NSPopUpButton *_popUpButtonInternalNodes; + + IBOutlet NSView *_saveDialogCustomView; + IBOutlet NSComboBox *_saveFileFormat; + + NSMutableArray *_nodeAttributes; + + NSString *_searchKey; + NSMutableArray *_searchNodes; + + NSFont *_font; + CGFloat _fontSize; + NSString *_fontName; + + NSArray *_selectedNodes; +} + +@property (retain, readwrite) NSMutableArray *nodeAttributes; + +@property(nonatomic,assign) NSView *saveDialogCustomView; +@property(nonatomic,assign) NSComboBox *saveFileFormat; +@property (readwrite) BOOL taxaShown; +@property (readwrite) NSInteger selectionMode; + +- (void)setSelectedNodes:(NSArray *)selectedNodes; + +-(NSArray*)selectedNodes; + +@end diff --git a/Seqotron/MFTreeWindowController.m b/Seqotron/MFTreeWindowController.m new file mode 100644 index 0000000..da1c04e --- /dev/null +++ b/Seqotron/MFTreeWindowController.m @@ -0,0 +1,595 @@ +// +// MFTreeDocument.m +// Seqotron +// +// Created by Mathieu Fourment on 14/12/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFTreeWindowController.h" + +#define kArrowsToolbarItemID @"Arrows" + +@implementation MFTreeWindowController + +@synthesize taxaShown, selectionMode; +@synthesize saveDialogCustomView = _saveDialogCustomView; +@synthesize saveFileFormat = _saveFileFormat; +@synthesize nodeAttributes = _nodeAttributes; + + +- (id)init{ + if (self = [super initWithWindowNibName:@"MFTreeWindow"]) { + NSLog(@"MFTreeDocument initWithWindowNibName"); + taxaShown = YES; + _searchNodes = [[NSMutableArray alloc]init]; + _nodeAttributes = [[NSMutableArray alloc]initWithObjects:@" - None - ",@"Branch Length", @"Name", nil]; + _fontSize = [[[NSUserDefaults standardUserDefaults] objectForKey:@"MFDefaultTreeFontSize"]floatValue]; + _fontName = [[[NSUserDefaults standardUserDefaults] objectForKey:@"MFDefaultTreeFontName"]copy]; + _font = [NSFont fontWithName:_fontName size:_fontSize]; + _selectedNodes = [[NSArray alloc] init]; + } + return self; +} + +- (void)dealloc { + NSLog(@"MFTreeWindowController dealloc"); + [[NSNotificationCenter defaultCenter]removeObserver:self]; + [_fontName release]; + [_searchNodes release]; + [_selectedNodes release]; + [_nodeAttributes release]; + [_treeView unbind:@"trees"]; + [_treeView unbind:@"selectionIndexes"]; + [_treeView unbind:@"taxaShown"]; + [_treeView unbind:@"selectionMode"]; + [_treeView release]; + [super dealloc]; +} + +- (void)windowDidLoad { + [super windowDidLoad]; + NSLog(@"MFTreeWindowController windowDidLoad"); + + _treeView = [[MFTreeView alloc]initWithFrame:[[_scrollView contentView] frame]]; + + //_treeView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + [_scrollView setDocumentView:_treeView]; + + [_treeView bind:@"trees" toObject:_treesController withKeyPath:@"arrangedObjects" options:nil]; + [_treeView bind:@"selectionIndexes" toObject:_treesController withKeyPath:@"selectionIndexes" options:nil]; + [_treeView bind:@"taxaShown" toObject:self withKeyPath:@"taxaShown" options:nil]; + [_treeView bind:@"selectionMode" toObject:self withKeyPath:@"selectionMode" options:nil]; + [_treeView bind:@"selectedNodes" toObject:self withKeyPath:@"selectedNodes" options:nil]; + + [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(selectionNotification:) name:@"MFGlobalNameSelection" object:nil]; + + if( [[_treesController arrangedObjects] count]){ + NSArray *attributes = [self attributesFromSelectedTree]; + [self willChangeValueForKey:@"nodeAttributes"]; + [_nodeAttributes addObjectsFromArray:attributes]; + [self didChangeValueForKey:@"nodeAttributes"]; + } + +} + +#pragma mark *** Notification Methods *** + +- (void)selectionNotification:(NSNotification *)notification { + + if( [[_treesController arrangedObjects] count]){ + if( [notification object] != self ){ + NSDictionary *info = [notification userInfo]; + NSArray *selectedNames = [info objectForKey:@"selection"]; + NSMutableArray *selectedNodes = [[NSMutableArray alloc]init]; + [[[_treesController selectedObjects]objectAtIndex:0] enumerateNodesWithAlgorithm:MFTreeTraverseAlgorithmPreorder usingBlock:^(MFNode *node){ + if([node isLeaf] && [selectedNames indexOfObject:[node name]] != NSNotFound )[selectedNodes addObject:node]; + }]; + [self willChangeValueForKey:@"selectedNodes"]; + [_selectedNodes release]; + _selectedNodes = [selectedNodes retain]; + [self didChangeValueForKey:@"selectedNodes"]; + [selectedNodes release]; + } + } +} + +- (void)setSelectedNodes:(NSArray *)selectedNodes{ + + if(_selectedNodes != selectedNodes){ + [self willChangeValueForKey:@"selectedNodes"]; + [_selectedNodes release]; + _selectedNodes = [selectedNodes retain]; + [self didChangeValueForKey:@"selectedNodes"]; + + if( self.selectionMode == 0 ){ + NSMutableArray *arrayMut = [[NSMutableArray alloc]init]; + if( [selectedNodes count] ){ + [[[_treesController selectedObjects]objectAtIndex:0] enumerateNodesWithAlgorithm:MFTreeTraverseAlgorithmPreorder usingBlock:^(MFNode *node){ + if([node isLeaf] && [_selectedNodes indexOfObject:node] != NSNotFound )[arrayMut addObject: [node name]]; + }]; + } + NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:arrayMut,@"selection", nil]; + [[NSNotificationCenter defaultCenter]postNotificationName:@"MFGlobalNameSelection" object:self userInfo:dict]; + [arrayMut release]; + } + } +} + +-(NSArray*)selectedNodes{ + return _selectedNodes; +} + + +#pragma mark *** Action Methods *** + +- (BOOL)validateMenuItem:(NSMenuItem *)menuItem { + + BOOL enabled = NO; + SEL action = [menuItem action]; + + if (action==@selector(zoomIn:)) { + enabled = ([[_treesController arrangedObjects]count] > 0); + } + else if (action==@selector(zoomOut:)) { + enabled = ([[_treesController arrangedObjects]count] > 0); + + } + else if (action==@selector(zoomInWidth:)) { + enabled = ([[_treesController arrangedObjects]count] > 0); + } + else if (action==@selector(zoomOutWidth:)) { + enabled = ([[_treesController arrangedObjects]count] > 0); + + } + else if (action==@selector(zoomImageToActualSize:)) { + enabled = ([[_treesController arrangedObjects]count] > 0); + + } + else if ( action==@selector(increaseFont:) || action==@selector(defaultFontSize:)) { + enabled = ([[_treesController arrangedObjects]count] > 0); + + } + else if (action==@selector(decreaseFont:)) { + enabled = ([[_treesController arrangedObjects]count] > 0 && _fontSize > 1); + + } + else if ( action == @selector(selectAll:)){ + if( [[_treesController arrangedObjects] count] > 0 ){ + MFTree *tree = [[_treesController selectedObjects]objectAtIndex:0]; + enabled = [tree nodeCount] != [[_treeView selectedNodes]count]; + } + } + else if ( action == @selector(selectNone:)){ + if( [[_treesController arrangedObjects] count] > 0 ){ + enabled = [[_treeView selectedNodes]count] != 0; + } + } + else if ( action == @selector(invertSelection:)){ + enabled = ([[_treesController arrangedObjects]count] > 0); + } + else if ( action == @selector(rotateNodeAction:)){ + enabled = ([[_treesController arrangedObjects]count] > 0 && self.selectionMode > 0 && [_selectedNodes count]==1 ); + } + else if ( action == @selector(rerootAction:)){ + enabled = ([[_treesController arrangedObjects]count] > 0 && self.selectionMode > 0 && [_selectedNodes count]==1 ); + } + else if ( action == @selector(ladderize:)){ + enabled = [[_treesController arrangedObjects]count] > 0; + } + else if (action == @selector(find:) ){ + enabled = [[_searchField stringValue] length] > 0 && [[_treesController arrangedObjects] count] > 0; + } + else if (action == @selector(popUpButtonInternalNodesAction:) ){ + enabled = YES; + } + else if (action == @selector(copy:) ){ + enabled = [[_treesController arrangedObjects] count] > 0; + } + else if (action == @selector(printDocument:) ){ + enabled = [[_treesController arrangedObjects] count] > 0; + } + else if (action == @selector(exportPDF:) ){ + enabled = [[_treesController arrangedObjects] count] > 0; + } + else if (action==@selector(newDocumentWindow:)) { + // Give the menu item that creates new sibling windows for this document a reasonably descriptive title. It's important to use the document's "display name" in places like this; it takes things like file name extension hiding into account. We could do a better job with the punctuation! + [menuItem setTitle:[NSString stringWithFormat:NSLocalizedStringFromTable(@"New window for '%@'", @"MenuItems", @"Formatter string for the new document window menu item. Argument is a the display name of the document."), [[self document] displayName]]]; + enabled = YES; + + } + else { + enabled = [super validateMenuItem:menuItem]; + } + return enabled; + +} + + +- (IBAction)newDocumentWindow:(id)sender { + + // Do the same thing that a typical override of -[NSDocument makeWindowControllers] would do, but then also show the window. + // This is here instead of in MFDocument, though it would work there too, with one small alteration, because it's really view-layer code. + MFTreeWindowController *windowController = [[MFTreeWindowController alloc] init]; + [[self document] addWindowController:windowController]; + [windowController showWindow:self]; + [windowController release]; + +} + + +- (IBAction)selectAll:(id)sender { + NSMutableArray *array = [[NSMutableArray alloc]init]; + if( self.selectionMode == 0 ){ + [[[_treesController selectedObjects]objectAtIndex:0] enumerateNodesWithAlgorithm:MFTreeTraverseAlgorithmPreorder usingBlock:^(MFNode *node){ + if([node isLeaf]) [array addObject:node]; + }]; + } + else { + [[[_treesController selectedObjects]objectAtIndex:0] enumerateNodesWithAlgorithm:MFTreeTraverseAlgorithmPreorder usingBlock:^(MFNode *node){ + [array addObject:node]; + }]; + } + [self setSelectedNodes: array]; + [array release]; +} + +- (IBAction)selectNone:(id)sender { + [self setSelectedNodes: [NSArray array]]; +} + +-(IBAction)invertSelection:(id)sender{ + NSArray *selection = [_treeView selectedNodes]; + NSMutableArray *array = [[NSMutableArray alloc]init]; + if( self.selectionMode == 0 ){ + [[[_treesController selectedObjects]objectAtIndex:0] enumerateNodesWithAlgorithm:MFTreeTraverseAlgorithmPreorder usingBlock:^(MFNode *node){ + if( [node isLeaf] && [selection indexOfObject:node] == NSNotFound )[array addObject:node]; + }]; + } + else{ + [[[_treesController selectedObjects]objectAtIndex:0] enumerateNodesWithAlgorithm:MFTreeTraverseAlgorithmPreorder usingBlock:^(MFNode *node){ + if( [selection indexOfObject:node] == NSNotFound )[array addObject:node]; + }]; + } + [self setSelectedNodes:array]; + [array release]; +} + +- (IBAction)zoomIn:(id)sender { + [_treeView incrementTaxonSpacing:1]; +} + +- (IBAction)zoomOut:(id)sender { + [_treeView incrementTaxonSpacing:-1]; +} + +- (IBAction)zoomInWidth:(id)sender { + [_treeView incrementWidth:5]; +} + +- (IBAction)zoomOutWidth:(id)sender { + [_treeView incrementWidth:-5]; +} + +- (IBAction)zoomImageToActualSize:(id)sender { + //[_treeView defaultTaxonSpacing]; + [_treeView setDefaultSize]; +} + +-(IBAction)toggleSowTaxa:(id)sender{ + +} + +-(IBAction)popUpButtonInternalNodesAction:(id)sender{ + if([sender indexOfSelectedItem] == 0 ){ + [_treeView showBranchAttribute:@""]; + } + else { + [_treeView showBranchAttribute:[sender titleOfSelectedItem]]; + } +} + +- (IBAction)rotateNodeAction:(id)sender{ + [_treeView rotateSelectedBranch]; +} + +- (IBAction)rerootAction:(id)sender{ + [_treeView rootAtSelectedBranch]; +} + +- (IBAction)find:(id)sender { + NSString *searchKey = [_searchField stringValue]; + if( [searchKey length] > 0 ){ + MFTree *tree = [[_treesController selectedObjects]objectAtIndex:0]; + NSMutableArray *selection = [self findNodes:tree withString:searchKey]; + [self setSelectedNodes:selection]; + } + else { + [_searchNodes removeAllObjects]; + } + + // _searchField gives up focus so we can change attributes of nodes (e.g. background color...) + dispatch_async(dispatch_get_main_queue(), ^{[_searchField.window makeFirstResponder:nil];}); +} + +-(void)findNodes:(MFNode*)node nodes:(NSMutableArray*)nodes withString:(NSString*)str{ + if( ![node isLeaf]){ + for ( NSUInteger i = 0; i < [node childCount]; i++ ) { + [self findNodes:[node childAtIndex:i] nodes:nodes withString:str]; + } + } + else { + NSRange match = [[node name] rangeOfString:str options:NSCaseInsensitiveSearch range:NSMakeRange(0, [[node name] length])]; + if(match.location != NSNotFound){ + [nodes addObject:node]; + } + } +} + +-(NSMutableArray*)findNodes:(MFTree*)tree withString:(NSString*)str{ + NSMutableArray *selection = [[NSMutableArray alloc]init]; + [self findNodes:[tree root] nodes:selection withString:str]; + return [selection autorelease]; +} + +- (void)controlTextDidChange:(NSNotification *) notification { + self.selectionMode = 0; +} + + +- (NSArray*)attributesFromSelectedTree{ + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + MFTree *tree = [[_treesController selectedObjects] objectAtIndex:0]; + + [tree enumerateNodesWithAlgorithm:MFTreeTraverseAlgorithmPostorder usingBlock:^(MFNode *node){ + for (NSString *key in [[node attributes]allKeys]) { + if ( ![key hasPrefix:@"?"]) { + [dict setObject:@"" forKey:key]; + } + } + }]; + return [dict allKeys]; +} + + +// Conformance to the NSObject(NSColorPanelResponderMethod) informal protocol. +- (void)changeColor:(id)sender { + // Change the color of every selected graphic. + [_treeView setColorForSelected:[sender color]]; + +} + +- (IBAction)defaultFontSize:(id)sender{ + [_treeView setFontSize:[[[NSUserDefaults standardUserDefaults] objectForKey:@"MFDefaultTreeFontSize"]floatValue]]; +} + +- (IBAction)increaseFont:(id)sender{ + _fontSize++; + [_treeView setFontSize:_fontSize]; +} + + +- (IBAction)decreaseFont:(id)sender{ + if(_fontSize > 1){ + _fontSize--; + [_treeView setFontSize:_fontSize]; + } +} + +// do not use selectedFont with changeFont: +// https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSFontManager_Class/Reference/Reference.html#//apple_ref/occ/instm/NSObject/changeFont%3a +- (void)changeFont:(id)sender { + _font = [sender convertFont:_font]; + // Change the color of every selected graphic. + //[[self selectedGraphics] makeObjectsPerformSelector:@selector(setColor:) withObject:[sender color]]; + +} + +//- (void)changeAttributes:(id)sender { +// //NSFontEffectsBox *box = sender; +// NSDictionary *dict; +// NSDictionary* newAttrs = [sender convertAttributes:dict]; +// NSLog(@"attributes %@", newAttrs); +//} + + +-(IBAction)copy:(id)sender{ + if( [[_treesController selectedObjects] count] > 0 ){ + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + [pasteboard clearContents]; + MFTree *tree = [[_treesController selectedObjects]objectAtIndex:0]; + NSArray *array = [NSArray arrayWithObject: [tree newick]]; + [pasteboard writeObjects:array]; + } +} + +- (IBAction)ladderize:(id)sender{ + if( [[_treesController selectedObjects] count] > 0 ){ + MFTree *tree = [[_treesController selectedObjects]objectAtIndex:0]; + [tree ladderize]; + [_treeView reloadData]; + } +} + +- (IBAction)printDocument:(id)sender{ + NSPrintOperation *op = [NSPrintOperation printOperationWithView:_treeView]; + if (op){ + [op runOperation]; + } +} + +- (IBAction)exportPDF:(id)sender{ + + NSString *name = [self.document fileURL].lastPathComponent; + NSString* newName = @"Untitled.pdf"; + if ( name ) { + newName = [[name stringByDeletingPathExtension] stringByAppendingPathExtension:@"pdf"]; + } + + // Set the default name for the file and show the panel. + NSSavePanel* panel = [NSSavePanel savePanel]; + [panel setNameFieldStringValue:newName]; + [panel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result){ + if (result == NSFileHandlingPanelOKButton){ + NSURL* theFile = [panel URL]; + NSRect r = [_treeView bounds]; + NSData *data = [_treeView dataWithPDFInsideRect:r]; + + [data writeToURL:theFile atomically:YES]; + } + }]; +} + +#pragma mark *** Toolbar *** + + +//-------------------------------------------------------------------------------------------------- +// Factory method to create autoreleased NSToolbarItems. +// +// All NSToolbarItems have a unique identifer associated with them, used to tell your delegate/controller +// what toolbar items to initialize and return at various points. Typically, for a given identifier, +// you need to generate a copy of your "master" toolbar item, and return it autoreleased. The function +// creates an NSToolbarItem with a bunch of NSToolbarItem paramenters. +// +// It's easy to call this function repeatedly to generate lots of NSToolbarItems for your toolbar. +// +// The label, palettelabel, toolTip, action, and menu can all be nil, depending upon what you want +// the item to do. +//-------------------------------------------------------------------------------------------------- +- (NSToolbarItem *)toolbarItemWithIdentifier:(NSString *)identifier + label:(NSString *)label + paleteLabel:(NSString *)paletteLabel + toolTip:(NSString *)toolTip + target:(id)target + itemContent:(id)imageOrView + action:(SEL)action + menu:(NSMenu *)menu +{ + // here we create the NSToolbarItem and setup its attributes in line with the parameters + NSToolbarItem *item = [[[NSToolbarItem alloc] initWithItemIdentifier:identifier] autorelease]; + + [item setLabel:label]; + [item setPaletteLabel:paletteLabel]; + [item setToolTip:toolTip]; + [item setTarget:target]; + [item setAction:action]; + + // Set the right attribute, depending on if we were given an image or a view + if([imageOrView isKindOfClass:[NSImage class]]){ + [item setImage:imageOrView]; + } else if ([imageOrView isKindOfClass:[NSView class]]){ + [item setView:imageOrView]; + }else { + assert(!"Invalid itemContent: object"); + } + + + // If this NSToolbarItem is supposed to have a menu "form representation" associated with it + // (for text-only mode), we set it up here. Actually, you have to hand an NSMenuItem + // (not a complete NSMenu) to the toolbar item, so we create a dummy NSMenuItem that has our real + // menu as a submenu. + // + if (menu != nil) + { + // we actually need an NSMenuItem here, so we construct one + NSMenuItem *mItem = [[[NSMenuItem alloc] init] autorelease]; + [mItem setSubmenu:menu]; + [mItem setTitle:label]; + [item setMenuFormRepresentation:mItem]; + } + + return item; +} + +#pragma mark - +#pragma mark NSToolbarDelegate + +//-------------------------------------------------------------------------------------------------- +// This is an optional delegate method, called when a new item is about to be added to the toolbar. +// This is a good spot to set up initial state information for toolbar items, particularly ones +// that you don't directly control yourself (like with NSToolbarPrintItemIdentifier here). +// The notification's object is the toolbar, and the @"item" key in the userInfo is the toolbar item +// being added. +//-------------------------------------------------------------------------------------------------- +- (void)toolbarWillAddItem:(NSNotification *)notif { + NSToolbarItem *addedItem = [[notif userInfo] objectForKey:@"item"]; + + // Is this the printing toolbar item? If so, then we want to redirect it's action to ourselves + // so we can handle the printing properly; hence, we give it a new target. + // + if ([[addedItem itemIdentifier] isEqual: NSToolbarPrintItemIdentifier]) { + [addedItem setToolTip:@"Print your document"]; + [addedItem setTarget:self]; + } +} + +//-------------------------------------------------------------------------------------------------- +// This method is required of NSToolbar delegates. +// It takes an identifier, and returns the matching NSToolbarItem. It also takes a parameter telling +// whether this toolbar item is going into an actual toolbar, or whether it's going to be displayed +// in a customization palette. +//-------------------------------------------------------------------------------------------------- +- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag { + NSToolbarItem *toolbarItem = nil; + + // We create and autorelease a new NSToolbarItem, and then go through the process of setting up its + // attributes from the master toolbar item matching that identifier in our dictionary of items. + if ([itemIdentifier isEqualToString:kArrowsToolbarItemID]) + { + // 1) Navigation toolbar item + toolbarItem = [self toolbarItemWithIdentifier:kArrowsToolbarItemID + label:@"Previous/Next" + paleteLabel:@"Navigation" + toolTip:@"Tree selection" + target:self + itemContent:_arrowsView + action:nil + menu:nil]; + } + + + return toolbarItem; +} + +//-------------------------------------------------------------------------------------------------- +// This method is required of NSToolbar delegates. It returns an array holding identifiers for the default +// set of toolbar items. It can also be called by the customization palette to display the default toolbar. +//-------------------------------------------------------------------------------------------------- +- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar { + return [NSArray arrayWithObjects: kArrowsToolbarItemID, + nil]; + // note: + // that since our toolbar is defined from Interface Builder, an additional separator and customize + // toolbar items will be automatically added to the "default" list of items. +} + +//-------------------------------------------------------------------------------------------------- +// This method is required of NSToolbar delegates. It returns an array holding identifiers for all allowed +// toolbar items in this toolbar. Any not listed here will not be available in the customization palette. +//-------------------------------------------------------------------------------------------------- +- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar +{ + return [NSArray arrayWithObjects: kArrowsToolbarItemID, + nil]; + // note: + // that since our toolbar is defined from Interface Builder, an additional separator and customize + // toolbar items will be automatically added to the "default" list of items. +} + + +@end diff --git a/Seqotron/MFTreeWriter.h b/Seqotron/MFTreeWriter.h new file mode 100644 index 0000000..e173c12 --- /dev/null +++ b/Seqotron/MFTreeWriter.h @@ -0,0 +1,53 @@ +// +// MFTreeWriter.h +// Seqotron +// +// Created by Mathieu Fourment on 10/03/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFDefines.h" + +@interface MFTreeWriter : NSObject + + ++ (NSData *) data:(NSArray *) inTrees withFormat:(MFTreeFormat)format attributes:(NSDictionary*)attrs; + ++ (NSString *) string:(NSArray *) inTrees withFormat:(MFTreeFormat)format attributes:(NSDictionary*)attrs; + + +#pragma mark Newick + ++ (void)writeNewick:(NSArray *) inTrees toFile:(NSString *)file attributes:(NSDictionary*)attrs; + ++ (NSString*) stringNewick:(NSArray *) inTrees attributes:(NSDictionary*)attrs; + ++ (NSData*) dataNewick:(NSArray *) inTrees attributes:(NSDictionary*)attrs; + + +#pragma mark Nexus + ++ (void)writeNexus:(NSArray *) inTrees toFile:(NSString *)file attributes:(NSDictionary*)attrs; + ++ (NSString*) stringNexus:(NSArray *) inTrees attributes:(NSDictionary*)attrs; + ++ (NSData*) dataNexus:(NSArray *) inTrees attributes:(NSDictionary*)attrs; + +@end diff --git a/Seqotron/MFTreeWriter.m b/Seqotron/MFTreeWriter.m new file mode 100644 index 0000000..b389a91 --- /dev/null +++ b/Seqotron/MFTreeWriter.m @@ -0,0 +1,113 @@ +// +// MFTreeWriter.m +// Seqotron +// +// Created by Mathieu Fourment on 10/03/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFTreeWriter.h" + +#import "MFNewickExporter.h" +#import "MFNexusExporter.h" + +@implementation MFTreeWriter + ++ (NSData *) data:(NSArray *) inTrees withFormat:(MFTreeFormat)format attributes:(NSDictionary*)attrs{ + NSData *data = nil; + + NSArray *formats = [[NSArray alloc] initWithObjects:MFTreeFormatArray]; + switch (format) { + case MFTreeFormatNEWICK: + data = [MFTreeWriter dataNewick:inTrees attributes:attrs]; + break; + case MFTreeFormatNEXUS: + data = [MFTreeWriter dataNexus:inTrees attributes:attrs]; + break; + default: + NSLog(@"Format not recognized"); + break; + } + + [formats release]; + return data; +} + ++ (NSString *) string:(NSArray *) inTrees withFormat:(MFTreeFormat)format attributes:(NSDictionary*)attrs{ + NSString *data = nil; + + NSArray *formats = [[NSArray alloc] initWithObjects:MFTreeFormatArray]; + switch (format) { + case MFTreeFormatNEWICK: + data = [MFTreeWriter stringNewick:inTrees attributes:attrs]; + break; + case MFTreeFormatNEXUS: + data = [MFTreeWriter stringNexus:inTrees attributes:attrs]; + break; + default: + NSLog(@"Format not recognized"); + break; + } + + [formats release]; + return data; +} + + +#pragma mark Newick + ++ (void)writeNewick:(NSArray *) inTrees toFile:(NSString *)file attributes:(NSDictionary*)attrs{ +// for (MFTree *tree in inTrees) { +// NSString * t = [tree newick]; +// } +} + ++ (NSString*) stringNewick:(NSArray *) inTrees attributes:(NSDictionary*)attrs{ + MFNewickExporter *exporter = [[MFNewickExporter alloc]initWithTrees:inTrees options:attrs]; + NSString *string = [exporter string]; + [exporter release]; + return string; +} + ++ (NSData*) dataNewick:(NSArray *) inTrees attributes:(NSDictionary*)attrs{ + NSString *string = [MFTreeWriter stringNewick:inTrees attributes:attrs]; + NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; + return data; +} + + +#pragma mark Nexus + ++ (void)writeNexus:(NSArray *) inTrees toFile:(NSString *)file attributes:(NSDictionary*)attrs{ + +} + ++ (NSString*) stringNexus:(NSArray *) inTrees attributes:(NSDictionary*)attrs{ + MFNexusExporter *exporter = [[MFNexusExporter alloc]initWithTrees:inTrees options:attrs]; + NSString *string = [exporter string]; + [exporter release]; + return string; +} + ++ (NSData*) dataNexus:(NSArray *) inTrees attributes:(NSDictionary*)attrs{ + NSString *string = [MFTreeWriter stringNexus:inTrees attributes:attrs]; + NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; + return data; +} + +@end diff --git a/Seqotron/MFWindowController.h b/Seqotron/MFWindowController.h new file mode 100755 index 0000000..8323c0e --- /dev/null +++ b/Seqotron/MFWindowController.h @@ -0,0 +1,114 @@ +// +// MFWindowController.h +// Seqotron +// +// Created by Mathieu Fourment on 6/08/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +#import "MFSequencesView.h" +#import "MFNamesView.h" +#import "MFRulerView.h" +#import "MFSyncronizedScrollView.h" + +#import "MFTreeBuilderController.h" +#import "MFAlignerController.h" +#import "MFDistanceMatrixOperation.h" + + +@interface MFWindowController : NSWindowController{ +@private + + // The values underlying the key-value coding (KVC) and observing (KVO) compliance described below. + IBOutlet NSArrayController *_sequencesController; + + // Other objects we expect to find in the nib. + IBOutlet MFSequencesView *_sequencesView; + IBOutlet MFNamesView *_namesView; + IBOutlet MFRulerView *_rulerView; + + IBOutlet MFSyncronizedScrollView *_sequencesScrollView; + IBOutlet MFSyncronizedScrollView *_namesScrollView; + + IBOutlet NSPopUpButton *_dataTypePopUp; + IBOutlet NSPopUpButton *_geneticCodePopUp; + + IBOutlet NSArrayController *_coloringController; + IBOutlet NSArrayController *_dataTypeController; + IBOutlet NSArrayController *_geneticCodeController; + + IBOutlet NSSearchField *_searchField; + + NSArray *_dataTypes; + + NSMutableArray *_colorSchemes; + + IBOutlet NSView *_saveDialogCustomView; + IBOutlet NSComboBox *_saveFileFormat; + + NSString *_nucleotideColoringDescription; + NSString *_aaColoringDescription; + + NSFont *_font; + CGFloat _rowSpacing; + + NSMutableDictionary *_geneticTable; + NSArray *_geneticCodes; + + NSInteger _searchSequence; + NSUInteger _searchSite; + NSInteger _currentSearchTag; + + NSSpeechSynthesizer *_speechSynthesizer; + + MFTreeBuilderController *_treeBuilderController; + MFAlignerController *_alignerController; + NSMapTable *_progressControllers; + + NSMutableArray *_windowControllers; + + NSString *_coloringFolder; + +} + +@property(nonatomic,assign) MFSequencesView *sequencesView; +@property(nonatomic,assign) MFNamesView *namesView; +@property(nonatomic,assign) MFRulerView *rulerView; + +@property(nonatomic,assign) NSView *saveDialogCustomView; +@property(nonatomic,assign) NSComboBox *saveFileFormat; + +@property (retain,readwrite) NSArrayController *coloringController; +@property (retain,readwrite) NSArrayController *dataTypeController; + +@property (retain, readonly) NSArray *dataTypes; + +@property (retain, readwrite) NSMutableArray *colorSchemes; + + +@property (nonatomic,readwrite, retain) NSDictionary *foregroundColor; +@property (nonatomic,readwrite, retain) NSDictionary *backgroundColor; + +@property (readwrite) CGFloat fontSize; +@property (readwrite) CGFloat rowSpacing; +@property (readwrite, retain) NSString *fontName; + + +@end diff --git a/Seqotron/MFWindowController.m b/Seqotron/MFWindowController.m new file mode 100755 index 0000000..6db018a --- /dev/null +++ b/Seqotron/MFWindowController.m @@ -0,0 +1,2349 @@ +// +// MFWindowController.m +// Seqotron +// +// Created by Mathieu Fourment on 6/08/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "MFWindowController.h" + +#import "MFSequencesView.h" +#import "MFDocument.h" +#import "MFSequenceReader.h" +#import "MFSequenceWriter.h" +#import "MFSequence.h" +#import "MFSequenceUtils.h" +#import "MFNucleotide.h" +#import "MFProtein.h" +#import "MFSequenceSet.h" + +#import "MFRulerView.h" +#import "MFSyncronizedScrollView.h" + +#import "MFString.h" + +#import "MFDistanceMatrix.h" +#import "MFDistanceMatrixOperation.h" +#import "MFJukeCantorDistanceMatrix.h" +#import "MFNeighborJoining.h" + +#import "MFTreeDocument.h" +#import "MFExternalOperation.h" +#import "MFAppDelegate.h" +#import "MFProgressController.h" +#import "MFDistanceWindowController.h" + +#import "MFColorManager.h" + +#define kgeneticToolbarItemID @"Genetic code" + + +@implementation MFWindowController + +@synthesize sequencesView = _sequencesView; +@synthesize namesView = _namesView; +@synthesize rulerView = _rulerView; +@synthesize saveDialogCustomView = _saveDialogCustomView; +@synthesize saveFileFormat = _saveFileFormat; +@synthesize coloringController = _coloringController; +@synthesize dataTypeController = _dataTypeController; +@synthesize colorSchemes = _colorSchemes; +@synthesize dataTypes = _dataTypes; +@synthesize fontSize, fontName; +@synthesize foregroundColor, backgroundColor; + +- (id)init { + if ( self = [super initWithWindowNibName:@"MFDocument"] ) { + NSLog(@"Init MFWindowController"); + + // Colors + _coloringFolder = [[NSUserDefaults standardUserDefaults] objectForKey:@"MFColoringDirectory"]; + + foregroundColor = [[NSMutableDictionary alloc] init]; + backgroundColor = [[NSMutableDictionary alloc] init]; + + _colorSchemes = [[self loadColorSchemes:@"Nucleotide"]retain]; + _nucleotideColoringDescription = [[_colorSchemes objectAtIndex:0] copy]; + + NSArray *protSchemes = [self loadColorSchemes:@"Amino Acid"]; + _aaColoringDescription = [[protSchemes objectAtIndex:0]copy]; + + + // Genetic code + _geneticTable = [[NSMutableDictionary alloc] init]; + _geneticCodes = [[self loadGeneticCodes]retain]; + + _dataTypes = [NSArray arrayWithObjects:[[[MFNucleotide alloc]init]autorelease],[[[MFProtein alloc]init]autorelease], nil]; + + // Variables for views + fontSize = [[[NSUserDefaults standardUserDefaults] objectForKey:@"MFDefaultSequenceFontSize"]floatValue]; + fontName = [[[NSUserDefaults standardUserDefaults] objectForKey:@"MFDefaultSequenceFontName"]copy]; + _font = [NSFont fontWithName:fontName size:fontSize]; + _rowSpacing = [[[NSUserDefaults standardUserDefaults] objectForKey:@"MFDefaultSequenceRowSpacing"]floatValue]; + + // Search + _searchSite = NSNotFound; + _searchSequence = NSNotFound; + _currentSearchTag = 1; + + //Speach + _speechSynthesizer = [NSSpeechSynthesizer new]; + [_speechSynthesizer setDelegate:self]; + [_speechSynthesizer setVoice:@"com.apple.speech.synthesis.voice.Alex" ]; + NSLog(@"speech rate %f", [_speechSynthesizer rate]); + NSLog(@"voice %@", [_speechSynthesizer voice]); + [_speechSynthesizer setRate: [_speechSynthesizer rate]/2.5]; + //NSLog(@"%@",[NSSpeechSynthesizer availableVoices]); + + // Phylogenetics, Alignment, other operations + _treeBuilderController = nil; + _alignerController = nil; + _progressControllers = [[NSMapTable alloc]initWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableStrongMemory capacity:5]; + + _windowControllers = [[NSMutableArray alloc]init]; + } + return self; + +} + + +- (void)dealloc { + NSLog(@"MFWindowController dealloc"); + [foregroundColor release]; + [backgroundColor release]; + + [_colorSchemes release]; + [_nucleotideColoringDescription release]; + [_aaColoringDescription release]; + + [_geneticTable release]; + [_geneticCodes release]; + + [fontName release]; + + [_speechSynthesizer release]; + + [self removeObserver:self forKeyPath:@"dataTypeController.selectionIndex"]; + [self removeObserver:self forKeyPath:@"geneticCodeController.selectionIndex"]; + [self removeObserver:self forKeyPath:@"coloringController.selectionIndex"]; + [self removeObserver:_rulerView forKeyPath:@"residueWidth"]; + [_sequencesController removeObserver:self forKeyPath:@"selectionIndexes"]; + + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [_rulerView stopSynchronizing]; + [_namesScrollView stopSynchronizing]; + [_sequencesScrollView stopSynchronizing]; + + [_progressControllers release]; + [_treeBuilderController release]; + [_alignerController release]; + [_windowControllers release]; + [super dealloc]; +} + +- (void)windowDidLoad { + + [super windowDidLoad]; + NSLog(@"MFWindowController windowDidLoad"); + + // Bind the graphic view's selection indexes to the controller's selection indexes. The graphics controller's content array is bound to the document's graphics in the nib, so it knows when graphics are added and remove, so it can keep the selection indexes consistent. + [_sequencesView bind:MFSequenceViewSelectionIndexesBindingName toObject:_sequencesController withKeyPath:@"selectionIndexes" options:nil]; + [_sequencesView bind:MFSequenceViewSequencesBindingName toObject:self withKeyPath:[NSString stringWithFormat:@"%@.%@", @"document", MFDocumentSequencesKey] options:nil]; + [_sequencesView bind:MFSequenceViewForegroundColorBindingName toObject:self withKeyPath:@"foregroundColor" options:nil]; + [_sequencesView bind:MFSequenceViewBackgroundColorBindingName toObject:self withKeyPath:@"backgroundColor" options:nil]; + [_sequencesView bind:MFSequenceViewFontSizeBindingName toObject:self withKeyPath:@"fontSize" options:nil]; + + // Name View + [_namesView bind:@"selectionIndexes" toObject:_sequencesController withKeyPath:@"selectionIndexes" options:nil]; + [_namesView bind:@"sequences" toObject:_sequencesController withKeyPath:@"arrangedObjects.name" options:nil]; + [_namesView bind:@"fontSize" toObject:self withKeyPath:@"fontSize" options:nil]; + [_namesView bind:@"fontName" toObject:self withKeyPath:@"fontName" options:nil]; + [_namesView bind:@"rowSpacing" toObject:self withKeyPath:@"rowSpacing" options:nil]; + + // Scroll Views + [[_namesScrollView verticalScroller] setControlSize:1]; // cannot use setHidden:YES as it makes the view unscrollable + [_namesScrollView setSynchronizedScrollView:_sequencesScrollView onVertical:YES]; + [_sequencesScrollView setSynchronizedScrollView:_namesScrollView onVertical:YES]; //_sequenceView listens to _namesView + [_rulerView setSynchronizedScrollView:_sequencesScrollView]; + + [self.window registerForDraggedTypes:[self acceptableDragTypes]]; + + [self dataTypeDidChange]; + + if( [[_sequencesController arrangedObjects]count] > 0 ){ + NSString *desc = [[[[_sequencesController arrangedObjects]objectAtIndex:0]dataType]description]; + [_dataTypeController setSelectionIndex:[_dataTypePopUp indexOfItemWithTitle:desc]]; + } + else { + [_dataTypeController setSelectionIndex:0]; + } + + [self addObserver:self forKeyPath:@"dataTypeController.selectionIndex" options:(NSKeyValueObservingOptionNew) context:NULL]; + [self addObserver:self forKeyPath:@"geneticCodeController.selectionIndex" options:(NSKeyValueObservingOptionNew) context:NULL]; + [self addObserver:self forKeyPath:@"coloringController.selectionIndex" options:(NSKeyValueObservingOptionNew) context:NULL]; + [self addObserver:_rulerView forKeyPath:@"residueWidth" options:NSKeyValueObservingOptionNew context:NULL]; + [_sequencesController addObserver:self forKeyPath:@"selectionIndexes" options:(NSKeyValueObservingOptionNew) context:NULL]; + + // Notifications + [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(coloringNotification:) name:@"MFColoringDidChange" object:nil]; + [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(selectionNotification:) name:@"MFGlobalNameSelection" object:nil]; + +// MFAppDelegate *del = [NSApp delegate]; +// // Set up the color scheme menu +// NSInteger tag = 0; +// for (NSString *gc in _colorSchemes) { +// NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:gc action:@selector(colorSchemeAction:) keyEquivalent:@""]; +// [item setTarget:self]; +// [item setTag:tag]; +// [[del coloringMenu] addItem:item]; +// [item release]; +// tag++; +// } + + [_geneticCodeController setSelectedObjects:[NSArray arrayWithObject:@"Standard"]]; + for (MFSequence *seq in [_sequencesController arrangedObjects]) { + [seq setGeneticTable:_geneticTable]; + } + + [_rulerView setNeedsDisplay:YES]; +} + +// There is a bug in cocoa that prevent getting selectionIndex and selectionIndexes in the change NSDictionary +// http://www.cocoabuilder.com/archive/cocoa/231886-problem-observing-selectionindex-of-an-array-controller.html + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + + if ([keyPath isEqual:@"coloringController.selectionIndex"]) { + [self setUpColoring:[[_coloringController selectedObjects]objectAtIndex:0]]; + + } + else if ([keyPath isEqual:@"dataTypeController.selectionIndex"]) { + [self setSequencesDataType:[[_dataTypeController selectedObjects]objectAtIndex:0]]; + } + else if ([keyPath isEqual:@"geneticCodeController.selectionIndex"]) { + + [self setUpGeneticTable]; + for (MFSequence *sequence in [_sequencesController arrangedObjects] ) { + [sequence setGeneticTable:_geneticTable]; + } + [_sequencesView setNeedsDisplay:YES]; + + } + else if ([keyPath isEqual:@"selectionIndexes"]) { + NSMutableArray *names = [[NSMutableArray alloc]init]; + for (MFSequence *sequence in [_sequencesController selectedObjects]) { + [names addObject:[sequence name]]; + } + + NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:names, @"selection", nil]; + [[NSNotificationCenter defaultCenter]postNotificationName:@"MFGlobalNameSelection" object:self userInfo:dict]; + [names release]; + } + else if( [keyPath isEqualToString:@"isFinished"]){ + if ( [object isKindOfClass:[MFDistanceMatrixOperation class]] ) { + MFDistanceMatrixOperation *op = (MFDistanceMatrixOperation *) object; + [self performSelectorOnMainThread:@selector(distanceMatrixOperationDone:) withObject:op waitUntilDone:NO]; + } + else { + MFExternalOperation *op = (MFExternalOperation *) object; + [self performSelectorOnMainThread:@selector(operationDone:) withObject:op waitUntilDone:NO]; + } + } + else { + [super observeValueForKeyPath: keyPath + ofObject: object + change: change + context: context]; + } +} + + +#pragma mark *** Overrides of NSWindowController Methods *** + +- (void)setDocument:(NSDocument *)document { + + // Cocoa Bindings makes many things easier. Unfortunately, one of the things it makes easier is creation of reference counting cycles. In Mac OS 10.4 and later NSWindowController has a feature that keeps bindings to File's Owner, when File's Owner is a window controller, from retaining the window controller in a way that would prevent its deallocation. We're setting up bindings programmatically in -windowDidLoad though, so that feature doesn't kick in, and we have to explicitly unbind to make sure this window controller and everything in the nib it owns get deallocated. We do this here instead of in an override of -[NSWindowController close] because window controllers aren't sent -close messages for every kind of window closing. Fortunately, window controllers are sent -setDocument:nil messages during window closing. + if (!document) { + NSLog(@"setDocument"); + [_sequencesView unbind:MFSequenceViewSequencesBindingName]; + [_sequencesView unbind:MFSequenceViewForegroundColorBindingName]; + [_sequencesView unbind:MFSequenceViewBackgroundColorBindingName]; + [_sequencesView unbind:MFSequenceViewFontSizeBindingName]; + [_sequencesView unbind:MFSequenceViewSelectionIndexesBindingName]; + + [_namesView unbind:@"selectionIndexes"]; + [_namesView unbind:@"sequences"]; + [_namesView unbind:@"fontSize"]; + [_namesView unbind:@"fontName"]; + [_namesView unbind:@"rowSpacing"]; + + _speechSynthesizer.delegate = nil; // avoid circular references + } + + [super setDocument:document]; +} + + + + +#pragma mark *** Actions *** + +// Conformance to the NSObject(NSMenuValidation) informal protocol. +- (BOOL)validateMenuItem:(NSMenuItem *)menuItem { + + BOOL enabled = NO; + SEL action = [menuItem action]; + + if (action == @selector(newDocumentWindow:)) { + // Give the menu item that creates new sibling windows for this document a reasonably descriptive title. It's important to use the document's "display name" in places like this; it takes things like file name extension hiding into account. We could do a better job with the punctuation! + [menuItem setTitle:[NSString stringWithFormat:NSLocalizedStringFromTable(@"New window for '%@'", @"MenuItems", @"Formatter string for the new document window menu item. Argument is a the display name of the document."), [[self document] displayName]]]; + enabled = YES; + + } + else if (action == @selector(increaseFont:) ) { + enabled = YES; + } + else if ( action == @selector(decreaseFont:) ) { + enabled = fontSize > 1; + } + else if (action == @selector(defaultFontSize:) ) { + enabled = (fontSize != [[[NSUserDefaults standardUserDefaults] objectForKey:@"MFDefaultSequenceFontSize"]floatValue]); + } + else if (action == @selector(complement:) || action==@selector(reverse:) ) { + if( [[_sequencesController arrangedObjects] count] > 0 && [[_sequencesController selectedObjects]count] > 0 ){ + MFSequence *sequence = [[_sequencesController arrangedObjects]objectAtIndex:0]; + if( [[sequence dataType ] isKindOfClass:[MFNucleotide class]] && ![sequence translated] ){ + enabled = YES; + } + } + } + else if (action == @selector(convertUT:) ) { + if( [[_sequencesController arrangedObjects] count] > 0 ){ + MFSequence *sequence = [[_sequencesController arrangedObjects]objectAtIndex:0]; + if( [[sequence dataType] isKindOfClass:[MFNucleotide class]] && ![sequence translated] ){ + enabled = YES; + } + } + } + else if (action == @selector(translate:) ) { + if( [[_sequencesController arrangedObjects] count] > 0 ){ + MFSequence *sequence = [[_sequencesController arrangedObjects]objectAtIndex:0]; + if( [[sequence dataType] isKindOfClass:[MFNucleotide class]] && ![sequence translated] ){ + enabled = YES; + } + } + } + else if (action == @selector(toggleTranslated:) ) { + enabled = (([[_sequencesController arrangedObjects] count] > 0) && ([[[[_sequencesController arrangedObjects] objectAtIndex:0] dataType] isKindOfClass:[MFNucleotide class]] )); + } + else if (action == @selector(shiftRight:) ) { + if( [[_sequencesController arrangedObjects] count] > 0 ){ + enabled = ![[[_sequencesController arrangedObjects]objectAtIndex:0]translated]; + } + } + else if (action == @selector(shiftLeft:) ) { + if( [[_sequencesController arrangedObjects] count] > 0 ){ + enabled = ![[[_sequencesController arrangedObjects]objectAtIndex:0]translated]; + } + } + else if (action == @selector(selectAll:) ) { + enabled = [[_sequencesController arrangedObjects] count] > 0; + } + else if (action == @selector(selectNone:) || action==@selector(invertSelection:) ) { + enabled = [[_sequencesController selectionIndexes] count] > 0; + } + else if (action == @selector(startSpeaking:) ) { + enabled = (_sequencesView.rangeSelection.x.length != 0 && _sequencesView.rangeSelection.y.length == 1) || [[_sequencesController selectedObjects] count] == 1; + } + else if (action == @selector(stopSpeaking:) ) { + enabled = [_speechSynthesizer isSpeaking]; + } + else if (action == @selector(rateSpeaking:) ) { + enabled = (_sequencesView.rangeSelection.x.length != 0 && _sequencesView.rangeSelection.y.length == 1) || [[_sequencesController selectedObjects] count] == 1; + } + else if (action == @selector(paste:) ) { + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + NSArray *objects = [pasteboard readObjectsForClasses:[NSArray arrayWithObject:[NSString class]] options:nil]; + enabled = [objects count] > 0; + } + else if (action == @selector(copy:) || action==@selector(cut:) || action==@selector(delete:) ) { + enabled = [[_sequencesController selectedObjects] count] > 0 || !MFIsEmpty2DRange(_sequencesView.rangeSelection); + } + else if (action == @selector(deleteEmptyColumns:) ) { + enabled = [[_sequencesController arrangedObjects] count] > 0; + } + else if (action == @selector(calculateDistanceMatrix:) ) { + enabled = [[_sequencesController arrangedObjects] count] > 2 && !( [menuItem tag] > 0 && [self isAminoAcidFromFirstSequence]); + } + else if (action == @selector(neighborjoining:) ) { + enabled = [[_sequencesController arrangedObjects] count] > 3; + } + else if (action == @selector(showBuildTreeSheet:) ) { + enabled = [[_sequencesController arrangedObjects] count] > 3; + } + else if (action == @selector(showAlignerSheet:) ) { + enabled = [[_sequencesController arrangedObjects] count] > 1; + } + else if (action == @selector(performFindPanelAction:) ){ + enabled = YES; + } + else if (action == @selector(find:) ){ + enabled = [[_searchField stringValue] length] > 0 && [[_sequencesController arrangedObjects] count] > 0; + // deactivate Find All if we are in sequence mode + if ( enabled && [menuItem tag] == 4 && _currentSearchTag == 1 ) { + enabled = NO; + } + } + else if (action == @selector(findFromSelection:) ){ + enabled = [[_searchField stringValue] length] > 0 && _currentSearchTag == 1 && [[_sequencesController arrangedObjects] count] > 0 && !MFIsEmpty2DRange(_sequencesView.rangeSelection) && _sequencesView.rangeSelection.y.length == 1; + } + else if (action == @selector(searchTypeMenuItem:) ) { + [menuItem setState:([menuItem tag] == _currentSearchTag) ? NSOnState : NSOffState]; + enabled = [[_sequencesController arrangedObjects] count] > 0; + } + else if ([menuItem action] == @selector(datatypeAction:)){ + [menuItem setState:([menuItem tag] == [_dataTypeController selectionIndex]) ? NSOnState : NSOffState]; + if( [[_sequencesController arrangedObjects] count] > 0 ){ + MFSequence *sequence = [[_sequencesController arrangedObjects]objectAtIndex:0]; + enabled = !([[sequence dataType] isKindOfClass:[MFNucleotide class]] && [sequence translated]); + } + } + else if ([menuItem action] == @selector(geneticCodeAction:)){ + [menuItem setState:([menuItem tag] == [_geneticCodeController selectionIndex]) ? NSOnState : NSOffState]; + enabled = YES; + } + else if ([menuItem action] == @selector(colorSchemeAction:)){ + if( [[_sequencesController arrangedObjects]count] > 0 ){ + [menuItem setState:([menuItem tag] == [_coloringController selectionIndex]) ? NSOnState : NSOffState]; + NSString *parentItemTitle = [[menuItem parentItem] title]; + NSString *currentDataType = [[[_dataTypeController selectedObjects]objectAtIndex:0]description]; + + if([currentDataType isEqualToString:@"Nucleotide"] && [[[_sequencesController arrangedObjects]objectAtIndex:0]translated] && [parentItemTitle isEqualToString:@"Protein"]){ + enabled = YES; + } + else if([parentItemTitle isEqualToString:currentDataType] && !([currentDataType isEqualToString:@"Nucleotide"] && [[[_sequencesController arrangedObjects]objectAtIndex:0]translated])){ + enabled = YES; + } + } + + } + else if (action == @selector(exportPDF:) ){ + enabled = [[_coloringController arrangedObjects] count] > 0; + } + else { + enabled = [super validateMenuItem:menuItem]; + } + return enabled; + +} + +- (IBAction)newDocumentWindow:(id)sender { + + // Do the same thing that a typical override of -[NSDocument makeWindowControllers] would do, but then also show the window. + // This is here instead of in MFDocument, though it would work there too, with one small alteration, because it's really view-layer code. + MFWindowController *windowController = [[MFWindowController alloc] init]; + [[self document] addWindowController:windowController]; + [windowController showWindow:self]; + [windowController release]; + +} + +- (IBAction)increaseFont:(id)sender { + self.fontSize = fontSize+1; +} + +- (IBAction)decreaseFont:(id)sender { + if(fontSize > 1){ + self.fontSize = fontSize-1; + } +} + +- (IBAction)defaultFontSize:(id)sender { + self.fontSize = [[[NSUserDefaults standardUserDefaults] objectForKey:@"MFDefaultSequenceFontSize"]floatValue]; + +} + +- (IBAction)selectAll:(id)sender { + NSUInteger count = [[_sequencesController arrangedObjects] count]; + if( count > 0 ){ + [_sequencesController setSelectionIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, count)]]; + } +} + +- (IBAction)selectNone:(id)sender { + NSUInteger count = [[_sequencesController selectionIndexes] count]; + if( count > 0 ){ + [_sequencesController setSelectionIndexes:[NSIndexSet indexSet]]; + } +} + +-(IBAction)invertSelection:(id)sender{ + if([[_sequencesController selectedObjects] count] > 0 ){ + NSIndexSet *indexes = [_sequencesController selectionIndexes]; + NSMutableIndexSet *newIndexes = [[NSMutableIndexSet alloc]init]; + + for (NSUInteger i = 0; i < [[_sequencesController arrangedObjects]count]; i++) { + if( ![indexes containsIndex:i] ){ + [newIndexes addIndex:i]; + } + } + + [_sequencesController setSelectionIndexes:newIndexes]; + [newIndexes release]; + } +} + +- (IBAction)deleteEmptyColumns:(id)sender { + + NSArray *sequences = [_sequencesController arrangedObjects]; + NSUInteger count = [sequences count]; + if( count > 0 ){ + + NSMutableIndexSet *indexes = [[NSMutableIndexSet alloc] init]; + NSUInteger nSites = [MFSequenceUtils maxLength:sequences]; + for (NSUInteger i = 0; i < nSites; i++) { + NSUInteger j = 0; + for (MFSequence *sequence in sequences) { + if( [sequence residueAt:i] != '-' ){ + break; + } + j++; + } + if( j == [sequences count] ){ + [indexes addIndex:i]; + } + } + [self deleteGaps:[_sequencesController arrangedObjects] atIndexes:indexes]; + [indexes release]; + _sequencesView.rangeSelection = MFMakeEmpty2DRange(); + } +} + +- (IBAction)reverse:(id)sender { + if( [[_sequencesController arrangedObjects]count] > 0 && ![[[_sequencesController arrangedObjects]objectAtIndex:0]translated] ){ + NSUInteger count = [[_sequencesController selectionIndexes] count]; + if( count > 0 ){ + NSUndoManager *undoManager = [[self document]undoManager]; + NSIndexSet *indexes = [_sequencesController selectionIndexes]; + [self reverseSequencesAtIndexes: indexes]; + [[undoManager prepareWithInvocationTarget:self] reverseSequencesAtIndexes:indexes]; + [undoManager setActionName:@"Reverse"]; + } + } +} + +- (IBAction)complement:(id)sender { + if( [[_sequencesController arrangedObjects]count] > 0 && ![[[_sequencesController arrangedObjects]objectAtIndex:0]translated] ){ + NSUInteger count = [[_sequencesController selectionIndexes] count]; + if( count > 0 ){ + NSUndoManager *undoManager = [[self document]undoManager]; + NSIndexSet *indexes = [_sequencesController selectionIndexes]; + [self complementSequencesAtIndexes: indexes]; + + [[undoManager prepareWithInvocationTarget:self] complementSequencesAtIndexes:indexes]; + [undoManager setActionName:@"Complement"]; + } + } +} + +-(IBAction)convertUT:(id)sender{ + if( [[_sequencesController arrangedObjects]count] > 0 && ![[[_sequencesController arrangedObjects]objectAtIndex:0]translated] ){ + + NSUndoManager *undoManager = [[self document]undoManager]; + // T -> U + if( [sender tag] == 0 ){ + [self convertTtoU]; + [[undoManager prepareWithInvocationTarget:self]convertUtoT]; + [undoManager setActionName:@"Convert T -> U"]; + } + // U -> T + else { + [self convertUtoT]; + [[undoManager prepareWithInvocationTarget:self]convertTtoU]; + [undoManager setActionName:@"Convert U -> T"]; + + } + } +} + +- (IBAction)shiftRight:(id)sender { + + NSUInteger count = [[_sequencesController arrangedObjects] count]; + if( count > 0 ){ + NSUInteger numberOfEmpties = [MFSequenceUtils numberOfEmptyColumnsAtFront:[_sequencesController arrangedObjects]]; + if( numberOfEmpties < 2 ){ + [self sequencesView:_sequencesView insertGaps:1 inSequenceRange:NSMakeRange(0, count) atIndex:0]; + } + } +} + +- (IBAction)shiftLeft:(id)sender { + + NSUInteger count = [[_sequencesController arrangedObjects] count]; + if( count > 0 && [MFSequenceUtils isStartingWithOneGap:[_sequencesController arrangedObjects]] ){ + [self sequencesView:_sequencesView deleteGaps:NSMakeRange(0, 1) inSequenceRange:NSMakeRange(0, count)]; + } +} + +-(IBAction)startSpeaking:(id)sender{ + if( [_speechSynthesizer isSpeaking] ){ + [_speechSynthesizer stopSpeaking]; + } + else { + if( _sequencesView.rangeSelection.x.length != 0 && _sequencesView.rangeSelection.y.length == 1 ){ + MFSequence *sequence = [[_sequencesController arrangedObjects] objectAtIndex:_sequencesView.rangeSelection.y.location]; + [_speechSynthesizer startSpeakingString: [sequence subSequenceWithRange:_sequencesView.rangeSelection.x]]; + //NSLog(@"%@",[sequence subSequenceWithRange:_sequencesView.rangeSelection.x]); + } + else if ( [[_sequencesController selectedObjects] count] == 1 ){ + MFSequence *sequence = [[_sequencesController arrangedObjects] objectAtIndex:[_sequencesController selectionIndex]]; + [_speechSynthesizer startSpeakingString: [sequence subSequenceWithRange:NSMakeRange(0, [sequence length])]]; + //NSLog(@"%@",[sequence subSequenceWithRange:NSMakeRange(0, [sequence length])]); + } + } +} + + +-(IBAction)stopSpeaking:(id)sender{ + [_speechSynthesizer stopSpeaking]; +} + +-(IBAction)rateSpeaking:(id)sender{ + // Slower + if ( [sender tag] == 0) { + [_speechSynthesizer setRate: [_speechSynthesizer rate]-10.0]; + } + else{ + [_speechSynthesizer setRate: [_speechSynthesizer rate]+10.0]; + } +} + +- (IBAction)searchTypeMenuItem:(id)sender{ + if( [sender tag] != _currentSearchTag ){ + _searchSite = NSNotFound; + _searchSequence = NSNotFound; + _currentSearchTag = [sender tag]; + } +} + +-(IBAction)performFindPanelAction:(id)sender{ + +} + +// Use selection for find +-(IBAction)findFromSelection:(id)sender{ + +} + +- (IBAction)find:(id)sender { + NSString *searchKey = [_searchField stringValue]; + + // Find All + if ( [sender tag] == 4 && _currentSearchTag == 0) { + [self findKeyInAllNames:searchKey]; + } + // Find + else if( [sender tag] == 1 ){ + return; + } + // Find Next || Previous in sequence names + else if( ([sender tag] == 2 || [sender tag] == 3 || [sender isKindOfClass:[NSSearchField class]]) && _currentSearchTag == 0 ){ + BOOL success; + // Find Next + if ( [sender tag] == 2 || [sender isKindOfClass:[NSSearchField class]] ) { + success = [self findKeyInNames:searchKey]; + } + // Find Previous + else{ + success = [self findKeyInNamesBackwards:searchKey]; + } + if( success ){ + [_sequencesController setSelectionIndex:_searchSequence]; + } + } + // Find Next || Previous in sequences + else if( ([sender tag] == 2 || [sender tag] == 3 || [sender isKindOfClass:[NSSearchField class]]) && _currentSearchTag == 1 ){ + BOOL success; + // Find Next + if ( [sender tag] == 2 || [sender isKindOfClass:[NSSearchField class]] ) { + success = [self findKeyInSequence:searchKey]; + } + // Find Previous + else{ + success = [self findKeyInSequencePrevious:searchKey]; + } + + if( success ){ + MF2DRange rangeSelection = MFMake2DRange(_searchSite, [searchKey length], _searchSequence, 1); + _sequencesView.rangeSelection = rangeSelection; + NSPoint point; + point.x = _searchSite*_sequencesView.residueWidth; + point.y = _searchSequence*(_sequencesView.residueHeight+_rowSpacing)+_rowSpacing; + + NSSize viewSize = [[_sequencesScrollView contentView] bounds].size; + + NSUInteger sizeWidth = _sequencesView.residueWidth * [MFSequenceUtils maxLength:[_sequencesController arrangedObjects]]; + NSUInteger sizeHeight = [[_sequencesController arrangedObjects]count]*(_sequencesView.residueHeight+_rowSpacing)+_rowSpacing; + + // there are not enough sequences to fill the contentView + if(sizeHeight <= viewSize.height){ + point.y = 0; + } + // too close to the top to be centered + else if( point.y < viewSize.height/2){ + point.y = 0; + } + // too close to the bottom to be centered + else if( viewSize.height > sizeHeight - point.y ){ + point.y -= viewSize.height - sizeHeight + point.y; + } + // can be centered + else { + point.y -= viewSize.height/2; + } + + // there are not enough sites to fill the contentView + if(sizeWidth <= viewSize.width){ + point.x = 0; + } + // too close to the left side to be centered + else if( point.x < viewSize.width/2){ + point.x = 0; + } + // too close to the end to be centered + else if( viewSize.width > sizeWidth - point.x ){ + point.x -= viewSize.width - sizeWidth + point.x; + } + // can be centered + else { + point.x -= viewSize.width/2; + } + [_sequencesView setNeedsDisplay:YES]; + [[_sequencesScrollView contentView] scrollToPoint:point]; + // we have to tell the NSScrollView to update its scrollers + [_sequencesScrollView reflectScrolledClipView:[_sequencesScrollView contentView]]; + } + } +} + +-(IBAction)copy:(id)sender{ + if( [[_sequencesController selectedObjects] count] > 0 ){ + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + [pasteboard clearContents]; + MFSequenceSet *set = [[MFSequenceSet alloc]initWithSequences:[_sequencesController selectedObjects]]; + NSString *str = [MFSequenceWriter string:set withFormat:MFSequenceFormatNEXUS attributes:nil]; + NSArray *array = [NSArray arrayWithObject:str]; + [pasteboard writeObjects:array]; + [set release]; + } +} + +- (IBAction)cut:(id)sender { + [self copy:sender]; + [self delete:sender]; + [[self undoManager] setActionName:NSLocalizedStringFromTable(@"Cut", @"UndoStrings", @"Action name for cut.")]; +} + +-(IBAction)paste:(id)sender{ + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + NSDictionary *options = [NSDictionary dictionary]; + + NSArray *classArrayNSURL = [NSArray arrayWithObject:[NSURL class]]; + NSArray *classArrayNSString = [NSArray arrayWithObject:[NSString class]]; + + + NSMutableArray *alignments = [[NSMutableArray alloc]init]; + + // URL + if( [pasteboard canReadObjectForClasses:classArrayNSURL options:options] ){ + NSArray *objects = [pasteboard readObjectsForClasses:classArrayNSURL options:options]; + + for ( NSURL *url in objects) { + MFSequenceSet *sequenceSet = [MFSequenceReader readSequencesFromURL:url]; + if( sequenceSet != nil ){ + [[self document] setFileFormat:[sequenceSet annotationForKey:MFSequenceFileFormat]]; + [alignments addObject:[sequenceSet sequences]]; + } + } + } + // NSString + else if( [pasteboard canReadObjectForClasses:classArrayNSString options:options] ){ + NSArray *objects = [pasteboard readObjectsForClasses:classArrayNSString options:options]; + + for ( NSString *str in objects) { + MFSequenceSet *sequenceSet = [MFSequenceReader readSequencesFromString:str]; + if( sequenceSet != nil ){ + [[self document] setFileFormat:[sequenceSet annotationForKey:MFSequenceFileFormat]]; + [alignments addObject:[sequenceSet sequences]]; + } + } + } + if( [alignments count] > 0 ){ + [self loadAlignments:alignments]; + } + + [alignments release]; +} + +- (IBAction)delete:(id)sender { + if( [[_sequencesController selectedObjects] count] != 0 ){ + [_sequencesController removeObjectsAtArrangedObjectIndexes:[_sequencesController selectionIndexes]]; + //[[self document]removeSequencesAtIndexes:[_sequencesController selectionIndexes]]; + [[[self document] undoManager] setActionName:NSLocalizedStringFromTable(@"Delete", @"UndoStrings", @"Action name for deletions.")]; + } + // else if( [_siteSelectionIndexes count] != 0 ){ + // + // } + else if( !MFIsEmpty2DRange(_sequencesView.rangeSelection) ){ + MF2DRange rangeSelection = _sequencesView.rangeSelection; + // the whole sequence is selected + if(rangeSelection.x.length == [self numberOfSites] ){ + [_sequencesController removeObjectsAtArrangedObjectIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(rangeSelection.y.location, rangeSelection.y.length)]]; + [[[self document] undoManager] setActionName:NSLocalizedStringFromTable(@"Delete", @"UndoStrings", @"Action name for deletions.")]; + } + else{ + [self sequencesView:_sequencesView removeSitesInRange:rangeSelection.x inSequenceRange:rangeSelection.y]; + [_sequencesView updateFrameSize]; + } + _sequencesView.rangeSelection = MFMakeEmpty2DRange(); + } +} + +// translate the alignment (not reversible) +- (IBAction)translate:(id)sender { + if( [[_sequencesController arrangedObjects]count] > 0 && [[[[_sequencesController arrangedObjects]objectAtIndex:0]dataType] isKindOfClass:[MFNucleotide class]] ){ + for (MFSequence *sequence in [_sequencesController arrangedObjects] ) { + sequence.dataType = [_dataTypes objectAtIndex:1]; + [sequence setGeneticTable:_geneticTable]; + [sequence setTranslated:NO ]; + [sequence translateFinal]; + } + [_dataTypeController setSelectionIndex:1]; + + NSMutableArray *newSchemes = [self loadColorSchemes:@"Amino acid"]; + [self setUpColoring:_aaColoringDescription datatype:@"Amino acid"]; + + [self willChangeValueForKey:@"colorSchemes"]; + [_colorSchemes replaceObjectsInRange:NSMakeRange(0, [_colorSchemes count]) withObjectsFromArray:newSchemes]; + [self didChangeValueForKey:@"colorSchemes"]; + + [_coloringController setSelectionIndex:[_colorSchemes indexOfObject:_aaColoringDescription]]; + + [[[self document]undoManager] removeAllActions]; + [[self document] updateChangeCount:NSChangeDone]; + } +} + + +- (IBAction)toggleTranslated:(id)sender { + // new value + BOOL isTranslated = ![[[_sequencesController arrangedObjects]objectAtIndex:0]translated]; + [self setTranslated:isTranslated]; +} + +- (IBAction)datatypeAction:(id)sender{ + [_dataTypeController setSelectionIndex:[sender tag]]; +} + +- (IBAction)geneticCodeAction:(id)sender{ + [_geneticCodeController setSelectionIndex:[sender tag]]; +} + +- (IBAction)colorSchemeAction:(id)sender{ + [_coloringController setSelectionIndex:[sender tag]]; +} + + +- (IBAction)exportPDF:(id)sender{ + + NSString *name = [self.document fileURL].lastPathComponent; + NSString* newName = @"Untitled.pdf"; + if ( name ) { + newName = [[name stringByDeletingPathExtension] stringByAppendingPathExtension:@"pdf"]; + } + + // Set the default name for the file and show the panel. + NSSavePanel* panel = [NSSavePanel savePanel]; + [panel setNameFieldStringValue:newName]; + [panel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result){ + if (result == NSFileHandlingPanelOKButton){ + NSURL* theFile = [panel URL]; + NSRect r = [_sequencesView bounds]; + NSData *data = [_sequencesView dataWithPDFInsideRect:r]; + + [data writeToURL:theFile atomically:YES]; + } + }]; +} + +- (IBAction)showGeneticCode:(id)sender{ + NSAlert *alert = [[NSAlert alloc] init]; + [alert setAlertStyle:NSInformationalAlertStyle]; + [alert setMessageText:@"Select a genetic code"]; + [alert addButtonWithTitle:@"Close"]; + + NSPopUpButton * tmpPopup = [[NSPopUpButton alloc] initWithFrame:NSMakeRect(0, 0, 200, 24)]; + + [tmpPopup bind:@"selectedIndex" toObject:_geneticCodeController withKeyPath:@"selectionIndex" options:nil]; + [tmpPopup bind:@"content" toObject:_geneticCodeController withKeyPath:@"arrangedObjects" options:nil]; + + [alert setAccessoryView:tmpPopup]; + [tmpPopup release]; + + [alert beginSheetModalForWindow:[NSApp mainWindow] completionHandler:^(NSInteger result) { + if (result == NSAlertFirstButtonReturn) { + [tmpPopup unbind:@"selectedIndex"]; + [tmpPopup unbind:@"content"]; + } + }]; + [alert release]; + +} + +#pragma mark *** Private Methods *** + +-(void)deleteGaps:(NSArray*)sequences atIndexes:(NSIndexSet*)indexes{ + NSUndoManager *undoManager = [[self document]undoManager]; + + for (MFSequence *sequence in sequences ) { + [sequence deleteResiduesAtIndexes:indexes]; + } + [[undoManager prepareWithInvocationTarget:self] insertGaps:sequences atIndexes:indexes]; + [undoManager setActionName:@"Deletion"]; + + [_sequencesView updateFrameSize]; + [_sequencesView setNeedsDisplay:YES]; +} + +-(void)insertGaps:(NSArray*)sequences atIndexes:(NSIndexSet*)indexes{ + NSUndoManager *undoManager = [[self document]undoManager]; + + [indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { + for (MFSequence *sequence in sequences ) { + [sequence insertGaps:1 AtIndex:idx]; + } + }]; + [[undoManager prepareWithInvocationTarget:self] deleteGaps:sequences atIndexes:indexes]; + [undoManager setActionName:@"Insertion"]; + + [_sequencesView updateFrameSize]; + [_sequencesView setNeedsDisplay:YES]; +} + +-(void)reverseSequencesAtIndexes:(NSIndexSet*) indexes{ + [indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { + [[[_sequencesController arrangedObjects] objectAtIndex:idx] reverse]; + }]; + [_sequencesView setNeedsDisplay:YES]; +} + +-(void)complementSequencesAtIndexes:(NSIndexSet*) indexes{ + [indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { + [[[_sequencesController arrangedObjects] objectAtIndex:idx] complement]; + }]; + [_sequencesView setNeedsDisplay:YES]; +} + +-(void)convertTtoU{ + if( [[_sequencesController arrangedObjects]count] > 0 ){ + NSRange range = NSMakeRange(0, [[[_sequencesController arrangedObjects]objectAtIndex:0]length]); + [[_sequencesController arrangedObjects]enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + MFSequence *seq = obj; + [seq replaceOccurencesOfString:@"T" withString:@"U" options:NSCaseInsensitiveSearch range:range]; + }]; + [_sequencesView setNeedsDisplay:YES]; + } +} + +-(void)convertUtoT{ + if( [[_sequencesController arrangedObjects]count] > 0 ){ + NSRange range = NSMakeRange(0, [[[_sequencesController arrangedObjects]objectAtIndex:0]length]); + [[_sequencesController arrangedObjects]enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + MFSequence *seq = obj; + [seq replaceOccurencesOfString:@"U" withString:@"T" options:NSCaseInsensitiveSearch range:range]; + }]; + [_sequencesView setNeedsDisplay:YES]; + } +} + +-(void)loadAlignments:(NSArray*)alignments{ + + MFDataType *dataType = nil; + if( [[_sequencesController arrangedObjects]count] > 0 ){ + dataType = [[[_sequencesController arrangedObjects]objectAtIndex:0]dataType]; + } + //empty window + else { + MFSequenceSet *set = [[MFSequenceSet alloc]initWithSequences:[alignments objectAtIndex:0]]; + + for (MFSequence *sequence in [set sequences]) { + if( [sequence dataType] != nil ){ + dataType = [sequence dataType]; + break; + } + } + if( dataType == nil ){ + dataType = [set guessDataType]; + } + [set release]; + } + + if( ([alignments count] >= 1 && [[_sequencesController arrangedObjects]count] > 0) + || ([alignments count] > 1 && [[_sequencesController arrangedObjects]count] == 0) ){ + + NSAlert *alert = [[NSAlert alloc] init]; + [alert setInformativeText:[NSString stringWithFormat:@"Do you want to append or concatenate these %tu files?", [alignments count]]]; + [alert setMessageText:@"Reading multiple files"]; + [alert addButtonWithTitle:@"Cancel"]; + [alert addButtonWithTitle:@"Append"]; + if ( [self isConcatenable:alignments])[alert addButtonWithTitle:@"Concatenate"]; + + + [alert beginSheetModalForWindow:[self window] completionHandler:^(NSInteger result) { + // Cancel + if (result == NSAlertFirstButtonReturn) { + //NSLog(@"Cancel"); + } + // Append + else if (result == NSAlertSecondButtonReturn) { + + for (NSArray *sequences in alignments) { + NSUInteger count = [[_sequencesController arrangedObjects]count]; + [_sequencesController insertObjects:sequences atArrangedObjectIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(count, [sequences count])]]; + } + [MFSequenceUtils pad:[_sequencesController arrangedObjects]]; + + //[self setSequencesDataTypeByIndex:[[dataType index]intValue]]; + [self setSequencesDataType:dataType]; + } + else if (result == NSAlertThirdButtonReturn) { + [self concatenate:alignments]; + [MFSequenceUtils pad:[_sequencesController arrangedObjects]]; + + //[self setSequencesDataTypeByIndex:[[dataType index]intValue]]; + [self setSequencesDataType:dataType]; + } + }]; + + [alert release]; + } + else { + NSUInteger count = [[_sequencesController arrangedObjects]count]; + NSArray *sequences = [alignments objectAtIndex:0]; + [MFSequenceUtils pad:sequences]; + [_sequencesController insertObjects:sequences atArrangedObjectIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(count, [sequences count])]]; + + + //[self setSequencesDataTypeByIndex:[[dataType index]intValue]]; + [self setSequencesDataType:dataType]; + } +} + +-(BOOL)isConcatenable:(NSArray*)alignments{ + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; + + NSUInteger i = 0; + if( [[_sequencesController arrangedObjects]count] > 0 ){ + for (MFSequence *sequence in [_sequencesController arrangedObjects]) { + [dict setObject:sequence forKey:[sequence name]]; + } + + } + else { + for (MFSequence *sequence in [alignments objectAtIndex:0]) { + [dict setObject:sequence forKey:[sequence name]]; + } + i = 1; + } + + for ( ; i < [alignments count]; i++ ) { + for (MFSequence *sequence in [alignments objectAtIndex:i]) { + if( ![dict objectForKey:[sequence name]] ){ + [dict release]; + return NO; + } + } + } + + [dict release]; + return YES; +} + +// concatenate sequence sets in alignment +-(void)concatenate:(NSArray*)alignments{ + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; + + if( [[_sequencesController arrangedObjects]count] == 0 ){ + NSArray *sequences = [alignments objectAtIndex:0]; + + for (MFSequence *sequence in sequences) { + [dict setObject:sequence forKey:[sequence name]]; + } + + for ( NSUInteger i = 1; i < [alignments count]; i++ ) { + for ( MFSequence *sequence in [alignments objectAtIndex:i] ) { + MFSequence *seq = [dict objectForKey:[sequence name]]; + [seq concatenateString:[sequence sequenceString]]; + } + } + + [_sequencesController insertObjects:sequences atArrangedObjectIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [sequences count])]]; + [[[self document] undoManager] setActionName:NSLocalizedStringFromTable(@"Paste", @"UndoStrings", @"Action name for paste.")]; + } + else { + + for (MFSequence *sequence in [_sequencesController arrangedObjects]) { + [dict setObject:sequence forKey:[sequence name]]; + } + NSUInteger len = [[[_sequencesController arrangedObjects] objectAtIndex:0]length]; + NSUInteger len2 = 0; + for ( NSUInteger i = 0; i < [alignments count]; i++ ) { + len2 += [[[alignments objectAtIndex:0]objectAtIndex:0]length]; + for ( MFSequence *sequence in [alignments objectAtIndex:i] ) { + MFSequence *seq = [dict objectForKey:[sequence name]]; + [seq concatenateString:[sequence sequenceString]]; + } + } + [[[[self document] undoManager] prepareWithInvocationTarget:self] sequencesView:nil removeSitesInRange: NSMakeRange(len, len2) inSequenceRange: NSMakeRange(0, [[_sequencesController arrangedObjects] count])]; + [[[self document] undoManager] setActionName:@"Remove concatenated sequences"]; + // need update of the views + } + + [dict release]; +} + +// Overrides of the NSResponder(NSStandardKeyBindingMethods) methods. +- (void)deleteBackward:(id)sender { + [self delete:sender]; +} + +- (void)deleteForward:(id)sender { + [self delete:sender]; +} + +-(NSUInteger)numberOfSites{ + NSUInteger maxLength = 0; + for (MFSequence *sequence in [_sequencesController arrangedObjects] ) { + if( [sequence length] > maxLength ){ + maxLength = [sequence length]; + } + } + return maxLength; +} + +-(BOOL)isNucleotideFromFirstSequence{ + return [[[[_sequencesController arrangedObjects]objectAtIndex:0]dataType] isKindOfClass:[MFNucleotide class]]; +} + +-(BOOL)isAminoAcidFromFirstSequence{ + return [[[[_sequencesController arrangedObjects]objectAtIndex:0]dataType] isKindOfClass:[MFProtein class]]; +} + +#pragma mark Search + +-(BOOL)findKeyInSequence:(NSString*)searchKey{ + if( [searchKey length] > 0 ){ + if( _searchSequence == [[_sequencesController arrangedObjects] count] || (_searchSite == NSNotFound && _searchSequence == NSNotFound) ){ + _searchSequence = 0; + _searchSite = 0; + } + else { + if ( _sequencesView.rangeSelection.x.length != 0 ) { + _searchSite = _sequencesView.rangeSelection.x.location; + _searchSequence = _sequencesView.rangeSelection.y.location; + } + _searchSite++; + } + + NSArray *sequences = [_sequencesController arrangedObjects]; + for ( ; _searchSequence < [sequences count]; _searchSequence++ ) { + MFSequence *sequence = [sequences objectAtIndex:_searchSequence]; + + NSRange match = [[sequence sequenceString] rangeOfString:searchKey options:NSCaseInsensitiveSearch range:NSMakeRange(_searchSite, [sequence length] -_searchSite)]; + if(match.location != NSNotFound){ + _searchSite = match.location; + return YES; + } + _searchSite = 0; + } + } + _searchSite = NSNotFound; + _searchSequence = NSNotFound; + return NO; +} + +-(BOOL)findKeyInSequencePrevious:(NSString*)searchKey{ + if( [searchKey length] > 0 ){ + NSArray *sequences = [_sequencesController arrangedObjects]; + + if( _searchSequence == -1 || (_searchSite == NSNotFound && _searchSequence == NSNotFound) ){ + _searchSequence = [sequences count]-1; + _searchSite = [self numberOfSites]; + } + else { + if ( _sequencesView.rangeSelection.x.length != 0 ) { + _searchSite = _sequencesView.rangeSelection.x.location; + _searchSequence = _sequencesView.rangeSelection.y.location; + } + _searchSite--; + } + + for ( ; _searchSequence >= 0; _searchSequence-- ) { + MFSequence *sequence = [sequences objectAtIndex:_searchSequence]; + + NSRange match = [[sequence sequenceString] rangeOfString:searchKey options:(NSBackwardsSearch|NSCaseInsensitiveSearch) range:NSMakeRange(0, _searchSite)]; + if(match.location != NSNotFound){ + _searchSite = match.location; + return YES; + } + _searchSite = 0; + } + } + _searchSite = NSNotFound; + _searchSequence = NSNotFound; + + return NO; +} + +-(BOOL)findKeyInNames:(NSString*)searchKey{ + if( [searchKey length] > 0 ){ + if( _searchSequence == NSNotFound ){ + // we reach the end, restart from the beginning + if( _searchSequence == [[_sequencesController arrangedObjects] count] ){ + _searchSequence = 0; + } + // initial selection + else if( [[_sequencesController selectedObjects] count] == 1 ){ + _searchSequence = [_sequencesController selectionIndex]; + } + // no initial selection + else { + _searchSequence = 0; + } + } + else { + _searchSequence++; + } + + NSArray *sequences = [_sequencesController arrangedObjects]; + for ( ; _searchSequence < [sequences count]; _searchSequence++ ) { + MFSequence *sequence = [sequences objectAtIndex:_searchSequence]; + NSRange match = [[sequence name] rangeOfString:searchKey options:NSCaseInsensitiveSearch range:NSMakeRange(0, [[sequence name]length])]; + if(match.location != NSNotFound){ + return YES; + } + } + } + // empty selection + [_sequencesController setSelectionIndexes:[NSIndexSet indexSet]]; + _searchSite = NSNotFound; + _searchSequence = NSNotFound; + return NO; +} + +-(BOOL)findKeyInNamesBackwards:(NSString*)searchKey{ + if( [searchKey length] > 0 ){ + NSArray *sequences = [_sequencesController arrangedObjects]; + if( _searchSequence == NSNotFound ){ + // we reach the beginning, restart from the beginningend + if( _searchSequence == -1 ){ + _searchSequence = [sequences count]-1; + } + // initial selection + else if( [[_sequencesController selectedObjects] count] == 1 ){ + _searchSequence = [_sequencesController selectionIndex]; + } + // no initial selection + else { + _searchSequence = [sequences count]-1; + } + } + else { + _searchSequence--; + } + + for ( ; _searchSequence >= 0; _searchSequence-- ) { + MFSequence *sequence = [sequences objectAtIndex:_searchSequence]; + NSRange match = [[sequence name] rangeOfString:searchKey options:NSCaseInsensitiveSearch range:NSMakeRange(0, [[sequence name]length])]; + if(match.location != NSNotFound){ + return YES; + } + } + } + // empty selection + [_sequencesController setSelectionIndexes:[NSIndexSet indexSet]]; + _searchSite = NSNotFound; + _searchSequence = NSNotFound; + return NO; +} + +-(void)findKeyInAllNames:(NSString*)searchKey{ + + if( [searchKey length] > 0 ){ + NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet]; + NSArray *sequences = [_sequencesController arrangedObjects]; + for ( NSUInteger i =0 ; i < [sequences count]; i++ ) { + MFSequence *sequence = [sequences objectAtIndex:i]; + NSRange match = [[sequence name] rangeOfString:searchKey options:NSCaseInsensitiveSearch range:NSMakeRange(0, [[sequence name]length])]; + if(match.location != NSNotFound){ + [indexes addIndex:i]; + } + } + [_sequencesController setSelectionIndexes:indexes]; + } + _searchSite = NSNotFound; + _searchSequence = NSNotFound; +} + +- (void)controlTextDidChange:(NSNotification *)notification { + //NSSearchField *searchField = [notification object]; + //NSLog(@"%@", [searchField stringValue] ); + _searchSequence = NSNotFound; + _searchSite = NSNotFound; +} + +#pragma mark Translation + +// lazyly translate the alignment (reversible) +-(void)setTranslated:(BOOL)isTranslated{ + + // group undo + for (MFSequence *sequence in [_sequencesController arrangedObjects] ) { + [sequence setTranslated:isTranslated]; + [sequence setGeneticTable:_geneticTable]; // it might be not needed every time but it does not hurt + } + + if(!isTranslated) [MFSequenceUtils pad:[_sequencesController arrangedObjects] ]; + + + for (NSMenuItem *item in [_dataTypePopUp itemArray]) { + [item setEnabled:!isTranslated]; + } + + NSMutableArray *newSchemes; + NSString *scheme; + if( isTranslated ){ + newSchemes = [self loadColorSchemes:@"Amino acid"]; + scheme = _aaColoringDescription; + [self setUpColoring:_aaColoringDescription datatype:@"Amino acid"]; + } + else { + newSchemes = [self loadColorSchemes:@"Nucleotide"]; + scheme = _nucleotideColoringDescription; + [self setUpColoring:_nucleotideColoringDescription datatype:@"Nucleotide"]; + } + + [self willChangeValueForKey:@"colorSchemes"]; + [_colorSchemes replaceObjectsInRange:NSMakeRange(0, [_colorSchemes count]) withObjectsFromArray:newSchemes]; + [self didChangeValueForKey:@"colorSchemes"]; + + + [_coloringController setSelectionIndex:[_colorSchemes indexOfObject:scheme]]; + + // we want to be able to see the selection + + NSUInteger maximumNumberOfSites = [MFSequenceUtils maxLength:[_sequencesController arrangedObjects]]; + NSUInteger numberOfSequences = [[_sequencesController arrangedObjects] count]; + NSSize size; + size.width = _sequencesView.residueWidth * maximumNumberOfSites; + size.height = (_sequencesView.residueHeight+_rowSpacing) * numberOfSequences + _rowSpacing; + + [_sequencesView setFrameSize:size]; + + NSPoint point = [[_sequencesScrollView contentView] bounds].origin; + // From dna to amino acid + if( isTranslated ){ + point.x /= 3; + } + else{ + point.x *= 3; + } + + [[_sequencesScrollView contentView] scrollToPoint:point]; + // we have to tell the NSScrollView to update its scrollers + [_sequencesScrollView reflectScrolledClipView:[_sequencesScrollView contentView]]; + + + [[[[self document]undoManager] prepareWithInvocationTarget:self] setTranslated:!isTranslated]; + [[[self document]undoManager] setActionName:@"Translate"]; + + [[NSNotificationCenter defaultCenter] postNotificationName:MFTranslationDidChangeNotification object:self]; +} + +#pragma mark Genetic code + +// Called once when MFWindowController is init +-(NSArray*)loadGeneticCodes{ + NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"GeneticCodeTables"]; + NSArray *dirFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil]; + NSMutableArray *mutableArray = [[NSMutableArray alloc]init]; + + for ( NSString *file in dirFiles) { + if ( [file hasSuffix:@"plist"] ) { + NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:[path stringByAppendingPathComponent:file]]; + [mutableArray addObject:[dict objectForKey:@"Description"]]; + [dict release]; + } + } + return [mutableArray autorelease]; +} + +// Called every time we change the genetic code +-(void)setUpGeneticTable{ + NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"GeneticCodeTables"]; + NSArray *dirFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil]; + NSString *geneticCode = [[_geneticCodeController selectedObjects]objectAtIndex:0]; + for ( NSString *file in dirFiles) { + if ( [file hasSuffix:@"plist"] ) { + NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:[path stringByAppendingPathComponent:file]]; + if( [[dict objectForKey:@"Description"] isEqualToString:geneticCode]){ + [_geneticTable setDictionary:[dict objectForKey:@"Table"]]; + + // genetic codes in plists are only set with ACTG and do not contain U + // add codons with Us to the _geneticTable dictionary + for (NSString *code in [_geneticTable allKeys]) { + if( [code rangeOfString:@"T" options:NSCaseInsensitiveSearch].location != NSNotFound ){ + NSString *ucode =[code stringByReplacingOccurrencesOfString:@"T" withString:@"U"]; + [_geneticTable setObject:[_geneticTable objectForKey:code] forKey:ucode]; + } + } + [dict release]; + break; + } + [dict release]; + } + } +} + + +#pragma mark Coloring + +-(NSMutableArray*)loadColorSchemes:(NSString*)type{ + NSMutableArray *colorSchemes = [[NSMutableArray alloc]init]; + NSString *userPath = [[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:_coloringFolder]stringByAppendingPathComponent:type]; + NSArray *schemes = [MFColorManager colorSchemesAtPath:userPath]; + [colorSchemes addObjectsFromArray:schemes]; + + NSError *error = nil; + NSURL *appSupportDir = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&error]; + if(!error){ + NSString *executableName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleExecutable"]; + NSString *appPath = [[[appSupportDir path] stringByAppendingPathComponent:[executableName stringByAppendingPathComponent:_coloringFolder]]stringByAppendingPathComponent:type]; + NSArray *schemes = [MFColorManager colorSchemesAtPath:appPath]; + [colorSchemes addObjectsFromArray:schemes]; + } + return [colorSchemes autorelease]; +} + +-(void)setUpColoring:(NSString *)scheme{ + + if( [[_sequencesController arrangedObjects] count] == 0 + || ( [[[[_sequencesController arrangedObjects]objectAtIndex:0]dataType ] isKindOfClass:[MFNucleotide class]] && ![[[_sequencesController arrangedObjects]objectAtIndex:0]translated]) ){ + [self setUpColoring:scheme datatype:@"Nucleotide"]; + } + else { + [self setUpColoring:scheme datatype:@"Amino acid"]; + } +} + +-(void)setUpColoring:(NSString*)scheme datatype:(NSString*)type{ + NSString *userPath = [[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:_coloringFolder]stringByAppendingPathComponent:type]; + + NSError *error; + NSURL *appSupportDir = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&error]; + NSString *executableName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleExecutable"]; + NSString *appPath = [[[appSupportDir path] stringByAppendingPathComponent:[executableName stringByAppendingPathComponent:_coloringFolder]]stringByAppendingPathComponent:type]; + + BOOL found = [self setUpColoring:scheme fromPath:userPath]; + if (!found) { + [self setUpColoring:scheme fromPath:appPath]; + } +} + +-(BOOL)setUpColoring:(NSString*)type fromPath:(NSString*)path{ + NSError *error = nil; + NSArray *dirFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error]; + + if(!error){ + for ( NSString *file in dirFiles) { + if ( [file hasSuffix:@"plist"] ) { + NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:[path stringByAppendingPathComponent:file]]; + if ( [[dict objectForKey:@"Description"] isEqualToString:type] ) { + + NSDictionary *temp = [dict objectForKey:@"Foreground"]; + if ( temp != nil ) { + NSMutableDictionary *foreground = [[NSMutableDictionary alloc]init]; + for (NSString *key in [temp keyEnumerator]) { + NSArray *colors = [temp valueForKey:key]; + CGFloat red = [[colors objectAtIndex:0] floatValue]; + CGFloat green = [[colors objectAtIndex:1] floatValue]; + CGFloat blue = [[colors objectAtIndex:2] floatValue]; + CGFloat alpha = 1; + if( [colors count] == 4 ) [[colors objectAtIndex:3] floatValue]; + + NSColor *color = [NSColor colorWithDeviceRed:red green:green blue:blue alpha:alpha]; + [foreground setValue: color forKey:key]; + } + [self setForegroundColor: foreground]; + [foreground release]; + } + + temp = [dict objectForKey:@"Background"]; + if ( temp != nil ) { + NSMutableDictionary *background = [[NSMutableDictionary alloc]init]; + for (NSString *key in [temp keyEnumerator]) { + NSArray *colors = [temp valueForKey:key]; + CGFloat red = [[colors objectAtIndex:0] floatValue]; + CGFloat green = [[colors objectAtIndex:1] floatValue]; + CGFloat blue = [[colors objectAtIndex:2] floatValue]; + CGFloat alpha = 1; + if( [colors count] == 4 ) [[colors objectAtIndex:3] floatValue]; + + NSColor *color = [NSColor colorWithDeviceRed:red green:green blue:blue alpha:alpha]; + //[_backgroundColor setValue: color forKey:key]; + [background setValue: color forKey:key]; + } + [self setBackgroundColor: background]; + [background release]; + } + // Background is optional but we need to clear the background dictionary otherwise it will keep the previous colors + else { + [self setBackgroundColor: [NSMutableDictionary dictionary]]; + } + [dict release]; + return true; + } + [dict release]; + } + } + } + return false; +} + +#pragma mark Data Type + +-(void)dataTypeDidChange{ + + if( [[_sequencesController arrangedObjects] count] > 0 ){ + + NSMutableArray *newSchemes; + NSString *scheme; + if( [[[[_sequencesController arrangedObjects]objectAtIndex:0]dataType ] isKindOfClass:[MFNucleotide class]] ){ + newSchemes = [self loadColorSchemes:@"Nucleotide"]; + scheme = _nucleotideColoringDescription; + //[self setUpColoring:_nucleotideColoringDescription]; + [self setUpColoring:scheme datatype:@"Nucleotide"]; + for (NSMenuItem *item in [_geneticCodePopUp itemArray]) { + [item setEnabled:YES]; + } + } + else { + newSchemes = [self loadColorSchemes:@"Amino acid"]; + scheme = _aaColoringDescription; + [self setUpColoring:scheme datatype:@"Amino acid"]; + for (NSMenuItem *item in [_geneticCodePopUp itemArray]) { + [item setEnabled:NO]; + } + } + [self willChangeValueForKey:@"colorSchemes"]; + [_colorSchemes replaceObjectsInRange:NSMakeRange(0, [_colorSchemes count]) withObjectsFromArray:newSchemes]; + [self didChangeValueForKey:@"colorSchemes"]; + + [_coloringController setSelectionIndex:[_colorSchemes indexOfObject:scheme]]; + } + +} + +-(void)setSequencesDataType:(MFDataType*)dataType{ + for (MFSequence *sequence in [_sequencesController arrangedObjects] ) { + sequence.dataType = dataType; + } + [self dataTypeDidChange]; +} + + + +#pragma mark *** Dragging *** + +- (NSArray *)acceptableDragTypes{ + return [NSArray arrayWithObjects:NSFilenamesPboardType,nil]; +} + +- (void)registerTypesForView:(NSView *)view{ + [view registerForDraggedTypes:[self acceptableDragTypes]]; +} + +- (NSDragOperation)draggingEntered:(id )sender { + if ((NSDragOperationGeneric & [sender draggingSourceOperationMask]) + == NSDragOperationGeneric) { + + return NSDragOperationGeneric; + + } + return NSDragOperationNone; +} + +- (BOOL)prepareForDragOperation:(id )sender { + return YES; +} + + +- (BOOL)performDragOperation:(id )sender { + + NSPasteboard *pboard = [sender draggingPasteboard]; + NSString *type = [pboard availableTypeFromArray:[self acceptableDragTypes]]; + BOOL loaded = NO; + + if (type) { + if ([type isEqualToString:NSFilenamesPboardType]) { + NSArray *files = [pboard propertyListForType:NSFilenamesPboardType]; + if( [files count] > 0 ){ + + NSMutableArray *alignments = [[NSMutableArray alloc]init]; + for (NSString *file in files) { + MFSequenceSet *sequenceSet = [MFSequenceReader readSequencesFromFile:file]; + + if ( sequenceSet != nil) { + [[self document] setFileFormat:[sequenceSet annotationForKey:MFSequenceFileFormat]]; + [alignments addObject:[sequenceSet sequences]]; + } + } + + [self loadAlignments:alignments]; + loaded = YES; + [alignments release]; + } + } + } + return loaded; +} + +#pragma mark *** Binding Methods *** + ++ (NSSet *)keyPathsForValuesAffectingResidueWidth { + return [NSSet setWithObjects:@"fontSize", @"fontName", nil]; +} + +-(CGFloat)residueWidth{ + NSMutableDictionary *attsDict = [[NSMutableDictionary alloc] init]; + NSFont *font = [NSFont fontWithName:fontName size:fontSize]; + [attsDict setObject:font forKey:NSFontAttributeName]; + + NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:@"A" attributes:attsDict]; + CGFloat resWidth = [string size].width; + [string release]; + [attsDict release]; + return resWidth; +} + + + +#pragma mark *** MFSequencesView Delegate Methods *** + +- (void)sequencesView:(MFSequencesView *)inSequenceView insertGaps:(NSUInteger)nGaps inSequenceRange:(NSRange)sequenceRange atIndex:(NSUInteger)index{ + NSUndoManager *undoManager = [[self document]undoManager]; + [undoManager beginUndoGrouping]; + + NSArray *sequences = [[_sequencesController arrangedObjects] subarrayWithRange:sequenceRange]; + NSRange range = NSMakeRange(index, nGaps); + for (MFSequence *sequence in sequences ) { + [sequence insertGaps:nGaps AtIndex:index]; + } + + [[undoManager prepareWithInvocationTarget:self] sequencesView:inSequenceView deleteGaps: NSMakeRange(index, nGaps) inSequenceRange: sequenceRange]; + + // We don't allow alignment with more than 2 empty columns at the fromt + NSUInteger numberOfEmpties = [MFSequenceUtils numberOfEmptyColumnsAtFront:[_sequencesController arrangedObjects]]; + //NSUInteger numberOfSequences = [[_sequencesController arrangedObjects] count]; + + if ( numberOfEmpties > 2 ) { + for (MFSequence *sequence in [_sequencesController arrangedObjects] ) { + [sequence deleteResiduesInRange:NSMakeRange(0, numberOfEmpties)]; + [[undoManager prepareWithInvocationTarget:sequence] insertGaps:numberOfEmpties AtIndex:0]; + } + //[[undoManager prepareWithInvocationTarget:self]sequencesView:inSequenceView insertGaps:numberOfEmpties inSequenceRange:NSMakeRange(0, numberOfSequences) atIndex:0]; + } + + NSUInteger maxLength = [MFSequenceUtils maxLength:[_sequencesController arrangedObjects]]; + + for (MFSequence *sequence in [_sequencesController arrangedObjects] ) { + // should be the block that we moved right if it was shorter than others + if( [sequence length] > maxLength ){ + range.location = maxLength; + range.length = [sequence length] - maxLength; + [sequence deleteResiduesInRange:range]; + [[undoManager prepareWithInvocationTarget:sequence] appendGaps:range.length]; + } + // should be the other sequences + else if( [sequence length] < maxLength ){ + NSUInteger nGaps = maxLength - [sequence length]; + [sequence appendGaps:nGaps]; + range.location = maxLength-nGaps; + range.length = nGaps; + [[undoManager prepareWithInvocationTarget:sequence] deleteResiduesInRange: range]; + } + } + [undoManager endUndoGrouping]; + [undoManager setActionName:@"Insertion"]; + + [_sequencesView updateFrameSize]; + [_sequencesView setNeedsDisplay:YES]; +} + +// UNDO only +- (void)sequencesView:(MFSequencesView *)inSequenceView deleteGaps:(NSRange)gapRange inSequenceRange:(NSRange)sequenceRange{ + NSUndoManager *undoManager = [[self document]undoManager]; + [undoManager beginUndoGrouping]; + + MF2DRange rangeSelection = _sequencesView.rangeSelection; + rangeSelection.x.location -= gapRange.length; + [_sequencesView setRangeSelection:rangeSelection]; + + NSArray *sequences = [[_sequencesController arrangedObjects] subarrayWithRange:sequenceRange]; + for (MFSequence *sequence in sequences ) { + [sequence deleteResiduesInRange:gapRange]; + } + [[undoManager prepareWithInvocationTarget:self] sequencesView:inSequenceView insertGaps: gapRange.length inSequenceRange: sequenceRange atIndex:gapRange.location]; + + NSUInteger maxLength = [MFSequenceUtils maxLength:[_sequencesController arrangedObjects]]; + + NSRange range; + for (MFSequence *sequence in [_sequencesController arrangedObjects] ) { + // should be the block that we moved right if it was shorter than others + if( [sequence length] > maxLength ){ + range.location = maxLength; + range.length = [sequence length] - maxLength; + [sequence deleteResiduesInRange:range]; + [[undoManager prepareWithInvocationTarget:sequence] appendGaps:range.length]; + } + // should be the other sequences + else if( [sequence length] < maxLength ){ + NSUInteger nGaps = maxLength - [sequence length]; + [sequence appendGaps:nGaps]; + range.location = maxLength-nGaps; + range.length = nGaps; + [[undoManager prepareWithInvocationTarget:sequence] deleteResiduesInRange: range]; + } + } + + //[[undoManager prepareWithInvocationTarget:self] sequencesView:inSequenceView insertGaps: gapRange.length inSequenceRange: sequenceRange atIndex:gapRange.location]; + + [undoManager endUndoGrouping]; + [undoManager setActionName:@"Deletion"]; + + [_sequencesView updateFrameSize]; + [_sequencesView setNeedsDisplay:YES]; +} + + +// UNDO only +- (void)sequencesView:(MFSequencesView *)inSequenceView insertSites:(NSArray*)sites atIndex:(NSUInteger)index inSequenceRange:(NSRange)sequenceRange{ + NSUndoManager *undoManager = [[self document]undoManager]; + + NSArray *sequences = [[_sequencesController arrangedObjects] subarrayWithRange:sequenceRange]; + NSEnumerator *arrayEnum = [sites objectEnumerator]; + for (MFSequence *sequence in sequences ) { + NSString *insertSite = [arrayEnum nextObject]; + [sequence insertResidues:insertSite AtIndex:index]; + } + + MF2DRange rangeSelection = MFMake2DRange(index, [[sites objectAtIndex:0]length], sequenceRange.location, sequenceRange.length); + [_sequencesView setRangeSelection:rangeSelection]; + + NSRange range; + range.location = index; + range.length = [[sites objectAtIndex:0] length]; + + [[undoManager prepareWithInvocationTarget:self] sequencesView:inSequenceView removeSitesInRange: range inSequenceRange: sequenceRange]; + [undoManager setActionName:@"Delete"]; + + [_sequencesView updateFrameSize]; + [_sequencesView setNeedsDisplay:YES]; +} + + + +- (void)sequencesView:(MFSequencesView *)inSequenceView removeSitesInRange:(NSRange)siteRange inSequenceRange:(NSRange)sequenceRange{ + NSUndoManager *undoManager = [[self document]undoManager]; + [undoManager beginUndoGrouping]; + NSMutableArray *sites = [[NSMutableArray alloc] initWithCapacity:sequenceRange.length]; + NSUInteger numberOfSequences = [[_sequencesController arrangedObjects] count]; + + NSArray *sequences = [[_sequencesController arrangedObjects] subarrayWithRange:sequenceRange]; + if( [[sequences objectAtIndex:0]translated] ){ + for (MFSequence *sequence in sequences ) { + [sites addObject: [sequence subCodonSequenceWithRange:siteRange]]; + [sequence deleteResiduesInRange:siteRange]; + } + } + else{ + for (MFSequence *sequence in sequences ) { + [sites addObject: [sequence subSequenceWithRange:siteRange]]; + [sequence deleteResiduesInRange:siteRange]; + } + } + + [[undoManager prepareWithInvocationTarget:self] sequencesView:inSequenceView insertSites: sites atIndex:siteRange.location inSequenceRange: sequenceRange]; + [sites release]; + + // We don't allow alignment with more than 2 empty columns at the fromt + NSUInteger numberOfEmpties = [MFSequenceUtils numberOfEmptyColumnsAtFront:[_sequencesController arrangedObjects]]; + + if ( numberOfEmpties > 2 ) { + for (MFSequence *sequence in [_sequencesController arrangedObjects] ) { + [sequence deleteResiduesInRange:NSMakeRange(0, numberOfEmpties)]; + } + [[undoManager prepareWithInvocationTarget:self]sequencesView:inSequenceView insertGaps:numberOfEmpties inSequenceRange:NSMakeRange(0, numberOfSequences) atIndex:0]; + } + + NSUInteger maxLength = [MFSequenceUtils maxLength:[_sequencesController arrangedObjects]]; + NSRange range; + for (MFSequence *sequence in [_sequencesController arrangedObjects] ) { + // the alignment can be shorter if other sequences around selction are gaps + if( [sequence length] > maxLength ){ + range.location = maxLength; + range.length = [sequence length] - maxLength; + [sequence deleteResiduesInRange:range]; + [[undoManager prepareWithInvocationTarget:sequence] appendGaps:range.length]; + } + // Classic case + else if( [sequence length] < maxLength ){ + NSUInteger nGaps = maxLength - [sequence length]; + [sequence appendGaps:nGaps]; + range.location = maxLength-nGaps; + range.length = nGaps; + [[undoManager prepareWithInvocationTarget:sequence] deleteResiduesInRange: range]; + } + } + + [undoManager endUndoGrouping]; + [undoManager setActionName:@"Insert"]; + + [_sequencesView updateFrameSize]; + [_sequencesView setNeedsDisplay:YES]; +} + +// only left, remove gaps only +- (void)sequencesView:(MFSequencesView *)inSequenceView slideSitesLeftInRange:(NSRange)siteRange inSequenceRange:(NSRange)sequenceRange by:(NSUInteger)amount{ + NSUndoManager *undoManager = [[self document]undoManager]; + + NSArray *sequences = [[_sequencesController arrangedObjects] subarrayWithRange:sequenceRange]; + for (MFSequence *sequence in sequences ) { + [sequence deleteResiduesInRange:NSMakeRange(siteRange.location, siteRange.length)]; + [sequence insertGaps:siteRange.length AtIndex:siteRange.location+amount]; + } + + [[undoManager prepareWithInvocationTarget:self] sequencesView:inSequenceView slideSitesRightInRange: siteRange inSequenceRange:sequenceRange by: amount]; + [undoManager setActionName:@"Shift"]; + + [_sequencesView updateFrameSize]; + [_sequencesView setNeedsDisplay:YES]; +} + +// UNDO only +- (void)sequencesView:(MFSequencesView *)inSequenceView slideSitesRightInRange:(NSRange)siteRange inSequenceRange:(NSRange)sequenceRange by:(NSUInteger)amount{ + NSUndoManager *undoManager = [[self document]undoManager]; + + NSArray *sequences = [[_sequencesController arrangedObjects] subarrayWithRange:sequenceRange]; + for (MFSequence *sequence in sequences ) { + [sequence deleteResiduesInRange:NSMakeRange(siteRange.location+amount, siteRange.length)]; + [sequence insertGaps:siteRange.length AtIndex:siteRange.location]; + } + MF2DRange rangeSelection = _sequencesView.rangeSelection; + rangeSelection.x.location += siteRange.length; + [_sequencesView setRangeSelection:rangeSelection]; + + [[undoManager prepareWithInvocationTarget:self] sequencesView:inSequenceView slideSitesLeftInRange: siteRange inSequenceRange:sequenceRange by: amount]; + [undoManager setActionName:@"Shift"]; + + [_sequencesView updateFrameSize]; + [_sequencesView setNeedsDisplay:YES]; +} + + +// replace +- (void)sequencesView:(MFSequencesView *)inSequenceView replaceResiduesInRange:(NSRange)range atSequenceIndex:(NSUInteger)sequenceIndex withString:(NSString*)string{ + NSUndoManager *undoManager = [[self document]undoManager]; + + MFSequence *sequence = [[_sequencesController arrangedObjects]objectAtIndex:sequenceIndex]; + NSString *replacedString = [sequence subSequenceWithRange:range]; + [sequence replaceCharactersInRange:range withString:string]; + + [[undoManager prepareWithInvocationTarget:self] sequencesView:inSequenceView replaceResiduesInRange: range atSequenceIndex:sequenceIndex withString: replacedString]; + [undoManager setActionName:@"Edit"]; + + [_sequencesView setNeedsDisplay:YES]; +} + +// UNDO only +// not tested +- (void)sequencesView:(MFSequencesView *)inSequenceView insertSites:(NSArray*)sites atIndexes:(NSIndexSet*)indexes inSequenceRange:(NSRange)sequenceRange{ + NSUndoManager *undoManager = [[self document]undoManager]; + + NSArray *sequences = [[_sequencesController arrangedObjects] subarrayWithRange:sequenceRange]; + NSEnumerator *arrayEnum = [sites objectEnumerator]; + for (MFSequence *sequence in sequences ) { + NSArray *insertResidues = [arrayEnum nextObject]; + NSUInteger index = [indexes firstIndex]; + for (NSString *res in insertResidues) { + [sequence insertResidues:res AtIndex:index]; + index = [indexes indexGreaterThanIndex: index]; + } + } + + // MF2DRange rangeSelection = MFMake2DRange(index, [[sites objectAtIndex:0]length], sequenceRange.location, sequenceRange.length); + // [_sequencesView setRangeSelection:rangeSelection]; + // + // NSRange range; + // range.location = index; + // range.length = [[sites objectAtIndex:0] length]; + + [[undoManager prepareWithInvocationTarget:self] sequencesView:inSequenceView removeSitesAtIndexes: indexes inSequenceRange: sequenceRange]; + [undoManager setActionName:@"Delete"]; + + [_sequencesView updateFrameSize]; + [_sequencesView setNeedsDisplay:YES]; +} + +// not tested +- (void)sequencesView:(MFSequencesView *)inSequenceView removeSitesAtIndexes:(NSIndexSet*)siteIndexes inSequenceRange:(NSRange)sequenceRange{ + NSUndoManager *undoManager = [[self document]undoManager]; + [undoManager beginUndoGrouping]; + NSMutableArray *sites = [NSMutableArray arrayWithCapacity:sequenceRange.length]; + + NSArray *sequences = [[_sequencesController arrangedObjects] subarrayWithRange:sequenceRange]; + if( [[sequences objectAtIndex:0]translated] ){ + for (MFSequence *sequence in sequences ) { + NSMutableArray *s = [NSMutableArray arrayWithCapacity:[siteIndexes count]]; + [siteIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { + [s addObject: [sequence subCodonSequenceWithRange:NSMakeRange(idx, 1)]]; + }]; + [sites addObject:s]; + + [sequence deleteResiduesAtIndexes:siteIndexes]; + } + } + else{ + for (MFSequence *sequence in sequences ) { + NSMutableArray *s = [NSMutableArray arrayWithCapacity:[siteIndexes count]]; + [siteIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { + [s addObject: [sequence subSequenceWithRange:NSMakeRange(idx, 1)]]; + }]; + [sites addObject:s]; + + [sequence deleteResiduesAtIndexes:siteIndexes]; + } + } + + [[undoManager prepareWithInvocationTarget:self] sequencesView:inSequenceView insertSites: sites atIndexes:siteIndexes inSequenceRange: sequenceRange]; + + + NSUInteger maxLength = [MFSequenceUtils maxLength:[_sequencesController arrangedObjects]]; + NSRange range; + for (MFSequence *sequence in [_sequencesController arrangedObjects] ) { + // the alignment can be shorter if other sequences around selction are gaps + if( [sequence length] > maxLength ){ + range.location = maxLength; + range.length = [sequence length] - maxLength; + [sequence deleteResiduesInRange:range]; + [[undoManager prepareWithInvocationTarget:sequence] appendGaps:range.length]; + } + // Classic case + else if( [sequence length] < maxLength ){ + NSUInteger nGaps = maxLength - [sequence length]; + [sequence appendGaps:nGaps]; + range.location = maxLength-nGaps; + range.length = nGaps; + [[undoManager prepareWithInvocationTarget:sequence] deleteResiduesInRange: range]; + } + } + + [undoManager endUndoGrouping]; + [undoManager setActionName:@"Insert"]; + + [_sequencesView updateFrameSize]; + [_sequencesView setNeedsDisplay:YES]; +} + +#pragma mark *** NSSplitView Delegate Methods *** + +- (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)dividerIndex{ + + if ( dividerIndex == 0 ) { + return [_namesView frame].size.width+20; + } + return proposedMax; +} + +#pragma mark *** Notification methods *** + +- (void)selectionNotification:(NSNotification *)notification { + + if( [notification object] != self ){ + NSDictionary *info = [notification userInfo]; + NSArray *selection = [info objectForKey:@"selection"]; + + NSMutableArray *objects = [[NSMutableArray alloc]init]; + for (MFSequence *sequence in [_sequencesController arrangedObjects]) { + if( [selection indexOfObject:[sequence name]] != NSNotFound ){ + [objects addObject:sequence]; + } + } + [_sequencesController setSelectedObjects:objects]; + [objects release]; + } +} + +//TODO: undos +- (void)coloringNotification:(NSNotification *)notification { + + NSArray *sequences = [_sequencesController arrangedObjects]; + if ( [sequences count] == 0 ) return; + + NSDictionary *info = [notification userInfo]; + + MFDataType *datatype = [[_dataTypeController selectedObjects]objectAtIndex:0]; + + + + if( ([[info objectForKey:@"datatype"] isEqualToString:@"Amino acid"] && [datatype isKindOfClass:[MFNucleotide class]] && ![[sequences objectAtIndex:0]translated]) + || ([[info objectForKey:@"datatype"] isEqualToString:@"Nucleotide"] && [datatype isKindOfClass:[MFNucleotide class]] && [[sequences objectAtIndex:0]translated]) ){ + return; + } + + NSString *file = [info objectForKey:@"file"]; + + if( [[info objectForKey:@"type"] isEqualTo:@"add"] ){ + [self willChangeValueForKey:@"colorSchemes"]; + [_colorSchemes addObject:file]; + [self didChangeValueForKey:@"colorSchemes"]; + } + else if( [[info objectForKey:@"type"] isEqualTo:@"delete"] ){ + // remove the scheme from the list and check that it is the scheme in use + [self willChangeValueForKey:@"colorSchemes"]; + [_colorSchemes removeObject:file]; + [self didChangeValueForKey:@"colorSchemes"]; + + NSString *selectedColorScheme = [[_coloringController selectedObjects]objectAtIndex:0]; + if([file isEqualToString:selectedColorScheme] ){ + [self setUpColoring:[_colorSchemes objectAtIndex:0]]; + for (NSMenuItem *item in [_geneticCodePopUp itemArray]) { + [item setEnabled:YES]; + } + [_coloringController setSelectionIndex:0]; + } + } + else if( [[info objectForKey:@"type"] isEqualTo:@"modify"] ){ + // update the colors + if( [datatype isKindOfClass:[MFNucleotide class]] && ![[sequences objectAtIndex:0]translated] ){ + [self setUpColoring:_nucleotideColoringDescription datatype:@"Nucleotide"]; + } + else { + [self setUpColoring:_aaColoringDescription datatype:@"Amino acid"]; + } + } + else if( [[info objectForKey:@"type"] isEqualTo:@"rename"] ){ + // update the list but not the colors + + if( [datatype isKindOfClass:[MFNucleotide class]] && ![[sequences objectAtIndex:0]translated] ){ + NSMutableArray *schemes = [self loadColorSchemes:@"Nucleotide"]; + [self willChangeValueForKey:@"colorSchemes"]; + [_colorSchemes replaceObjectsInRange:NSMakeRange(0, [_colorSchemes count]) withObjectsFromArray:schemes]; + [self didChangeValueForKey:@"colorSchemes"]; + + if ( [_colorSchemes indexOfObject:_nucleotideColoringDescription] == NSNotFound) { + [_nucleotideColoringDescription release]; + _nucleotideColoringDescription = [file copy]; + [_coloringController setSelectionIndex:[_colorSchemes indexOfObject:_nucleotideColoringDescription]]; + } + } + else { + NSMutableArray *schemes = [self loadColorSchemes:@"Amino acid"]; + [self willChangeValueForKey:@"colorSchemes"]; + [_colorSchemes replaceObjectsInRange:NSMakeRange(0, [_colorSchemes count]) withObjectsFromArray:schemes]; + [self didChangeValueForKey:@"colorSchemes"]; + + if ( [schemes indexOfObject:_aaColoringDescription] == NSNotFound) { + [_aaColoringDescription release]; + _aaColoringDescription = [file copy]; + [_coloringController setSelectionIndex:[_colorSchemes indexOfObject:_aaColoringDescription]]; + } + } + + } + +} + +- (void) windowWillClose:(NSNotification *) notification { + NSLog(@"windowWillClose %@", notification); + + if ( self.window != [notification object] ) { + NSWindowController *controller = [[notification object] windowController]; + [[controller retain] autorelease]; + [_windowControllers removeObject: controller]; + } + // if we close the window we need to cancel any nsoperation + else { + [self performSelectorOnMainThread:@selector(cancelAllOperations:) withObject:nil waitUntilDone:YES]; + } +} + +-(void)cancelAllOperations:(NSObject*)object{ + NSLog(@"cancelAllOperations"); + NSEnumerator *enumerator = [_progressControllers keyEnumerator]; + NSOperation *op; + while ( op = [enumerator nextObject]) { + [op removeObserver:self forKeyPath:@"isFinished"]; + [op cancel]; + [[_progressControllers objectForKey:op] close]; + } + [_progressControllers removeAllObjects]; +} + +#pragma mark *** Phylogenetics *** + +- (IBAction)calculateDistanceMatrix:(id)sender{ + +// if( [[self document]isDocumentEdited] || [[self document]fileURL] == nil ){ +// NSAlert *alert = [[NSAlert alloc]init]; +// [alert setMessageText:@"You need to save the alignment"]; +// [alert addButtonWithTitle:@"Close"]; +// //[alert addButtonWithTitle:@"Save As"]; +// //[alert addButtonWithTitle:@"Save"]; +// +// [alert beginSheetModalForWindow:[NSApp mainWindow] completionHandler:^(NSInteger result) { +// if (result == NSAlertFirstButtonReturn) { +// +// } +// }]; +// [alert release]; +// } +// else { + MFDistanceMatrixOperation *op = [[MFDistanceMatrixOperation alloc]initWithSequenceArray:[_sequencesController arrangedObjects] model:[sender tag]]; + MFProgressController *progress = [[MFProgressController alloc]initWithOperation:op]; + //progress.primaryDescription = [operation.options objectForKey:MFExternalOperationDescriptionKey]; + progress.window.title = op.description; + [[progress window] center]; + [progress showWindow: self]; + [progress.progressIndicator startAnimation:nil]; + + MFAppDelegate *del = [[NSApplication sharedApplication]delegate]; + [[del sharedOperationQueue] addOperation:op]; + + [op addObserver:self forKeyPath:@"isFinished" options:NSKeyValueObservingOptionNew context:nil]; + + [_progressControllers setObject:progress forKey:op]; + [progress release]; + [op release]; +// } +} + +- (void)distanceMatrixDidFinish:(MFDistanceMatrixOperation *)operation{ + MFDistanceWindowController *controller = [[MFDistanceWindowController alloc]initWithDistanceMatrix:operation.matrix]; + controller.window.delegate = self; + [_windowControllers addObject:controller]; + + //[[controller window] center]; + [controller showWindow: self]; + [controller release]; +} + +- (IBAction)neighborjoining:(id)sender{ + MFDistanceMatrix *dm = [[MFDistanceMatrix alloc]initWithSequencesFromArray: [_sequencesController arrangedObjects]]; + [dm calculateDistances]; + MFNeighborJoining *nj = [[MFNeighborJoining alloc]initWithDistanceMatrix:dm]; + MFTree *tree = [nj inferTree]; + [dm release]; + [nj release]; + + MFTreeDocument *doc = [[MFTreeDocument alloc]initWithTrees:[NSArray arrayWithObject:tree]]; + + [[NSDocumentController sharedDocumentController] addDocument:doc]; + [doc makeWindowControllers]; + [doc showWindows]; + + [doc release]; + /*MFTreeController *controller = [[MFTreeController alloc] initWithTree:tree]; + + [[controller window] center]; + [controller showWindow: self];*/ + +} + + +- (IBAction)showBuildTreeSheet:(id)sender{ + +// if( [[self document]isDocumentEdited] || [[self document]fileURL] == nil ){ +// NSAlert *alert = [[NSAlert alloc]init]; +// [alert setMessageText:@"You need to save the alignment"]; +// [alert addButtonWithTitle:@"Close"]; +// //[alert addButtonWithTitle:@"Save As"]; +// //[alert addButtonWithTitle:@"Save"]; +// +// [alert beginSheetModalForWindow:[NSApp mainWindow] completionHandler:^(NSInteger result) { +// if (result == NSAlertFirstButtonReturn) { +// +// } +// }]; +// [alert release]; +// } +// else { + if( [[_sequencesController arrangedObjects] count] > 3 ){ + if( !_treeBuilderController ){ + _treeBuilderController = [[MFTreeBuilderController alloc]initWithSequences:[_sequencesController arrangedObjects] withName:[[[self document]fileURL]lastPathComponent]]; + } + [_treeBuilderController initType]; + [NSApp beginSheet: _treeBuilderController.window + modalForWindow: [NSApp mainWindow] + modalDelegate: self + didEndSelector: @selector(didEndSheet:returnCode:contextInfo:) + contextInfo: _treeBuilderController]; + } +// } + +} + +#pragma mark *** Alignment *** + +- (IBAction)showAlignerSheet:(id)sender{ + +// if( [[self document]isDocumentEdited] ){ +// NSAlert *alert = [[NSAlert alloc]init]; +// [alert setMessageText:@"You need to save the alignment"]; +// [alert addButtonWithTitle:@"Close"]; +// [alert beginSheetModalForWindow:[NSApp mainWindow] completionHandler:^(NSInteger result) { +// if (result == NSAlertFirstButtonReturn) { +// +// } +// }]; +// [alert release]; +// } +// else { + if( [[_sequencesController arrangedObjects] count] > 1 ){ + if( !_alignerController ){ + _alignerController = [[MFAlignerController alloc]initWithSequences:[_sequencesController arrangedObjects] withName:[[[self document]fileURL]lastPathComponent]]; + } + _alignerController.transalignEnabled = [[[[_sequencesController arrangedObjects] objectAtIndex:0] dataType ] isKindOfClass:[MFNucleotide class]] && ![[[_sequencesController arrangedObjects] objectAtIndex:0] translated]; + //[_alignerController setSelection: _sequencesView.rangeSelection]; + [NSApp beginSheet: _alignerController.window + modalForWindow: [NSApp mainWindow] + modalDelegate: self + didEndSelector: @selector(didEndSheet:returnCode:contextInfo:) + contextInfo: _alignerController]; + } +// } + +} + + +- (void)didEndSheet: (NSWindow*)sheet returnCode: (int)returnCode contextInfo: (void*)contextInfo { + if (returnCode != NSOKButton) return; + + idcontroller = contextInfo; + + NSArray *operations = [controller operations]; + + MFProgressController *progress = [[MFProgressController alloc]initWithOperations:operations]; + [[progress window] center]; + [progress showWindow: self]; + [progress.progressIndicator startAnimation:nil]; + + NSMapTable *map = [[NSMapTable alloc]init]; + for ( MFOperation *op in operations ) { + op.delegate = progress; + + for ( MFOperation *dep in [op dependencies] ) { + if( [map objectForKey:dep]){ + [map setObject:[NSNumber numberWithInt:[[map objectForKey:dep] intValue] + 1] forKey:dep]; + } + else { + [map setObject:[NSNumber numberWithInt:1] forKey:dep]; + } + } + } + NSOperation *operation = [operations objectAtIndex:0]; + for ( NSOperation *op in operations ) { + if( ![map objectForKey:op]){ + operation = op; + break; + } + } + [map release]; + [operation addObserver:self forKeyPath:@"isFinished" options:NSKeyValueObservingOptionNew context:nil]; + + MFAppDelegate *del = [[NSApplication sharedApplication]delegate]; + [[del sharedOperationQueue] addOperations:operations waitUntilFinished:NO]; + + [_progressControllers setObject:progress forKey:operation]; + [progress release]; +} + +- (void)operationDone:(MFOperation *)op { + NSLog(@"operationDone (%@) URL %@", (op.isCancelled?@"Cancelled":@"Success"), op.outputURL); + + [op removeObserver:self forKeyPath:@"isFinished"]; + + if( !op.isCancelled ){ + NSError *err; + if( [op.outputURL checkResourceIsReachableAndReturnError:&err] ){ + NSString *class = op.classType; + NSDocument *doc = [[NSClassFromString(class) alloc]init]; + + NSError *error = nil; + BOOL ok = [doc readFromURL:op.outputURL ofType:nil error:&error]; + + if(ok){ + [[NSDocumentController sharedDocumentController] addDocument:doc]; + [doc makeWindowControllers]; + [doc showWindows]; + } + [doc release]; + } + + } + + if( [_progressControllers objectForKey:op]){ + [_progressControllers removeObjectForKey:op]; + } +} + +- (void)distanceMatrixOperationDone:(MFDistanceMatrixOperation *)op { + NSLog(@"distanceMatrixOperationDone (%@)", (op.isCancelled?@"Cancelled":@"Success")); + + [op removeObserver:self forKeyPath:@"isFinished"]; + + if( !op.isCancelled ){ + MFDistanceWindowController *controller = [[MFDistanceWindowController alloc]initWithDistanceMatrix:op.matrix]; + controller.window.delegate = self; + [_windowControllers addObject:controller]; + + //[[controller window] center]; + [controller showWindow: self]; + [controller release]; + + } + + if( [_progressControllers objectForKey:op]){ + [_progressControllers removeObjectForKey:op]; + } +} + + +@end diff --git a/Seqotron/NSArray+IndexSetAddition.h b/Seqotron/NSArray+IndexSetAddition.h new file mode 100755 index 0000000..2963822 --- /dev/null +++ b/Seqotron/NSArray+IndexSetAddition.h @@ -0,0 +1,30 @@ +// +// NSArray+IndexSetAddition.h +// Seqotron +// +// Created by Mathieu on 24/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +@interface NSArray (IndexSetAddition) + +- (NSArray *)subarrayWithIndexes: (NSIndexSet *)indexes; + +@end diff --git a/Seqotron/NSArray+IndexSetAddition.m b/Seqotron/NSArray+IndexSetAddition.m new file mode 100755 index 0000000..2dc28f6 --- /dev/null +++ b/Seqotron/NSArray+IndexSetAddition.m @@ -0,0 +1,44 @@ +// +// NSArray+IndexSetAddition.m +// Seqotron +// +// Created by Mathieu on 24/11/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "NSArray+IndexSetAddition.h" + + +@implementation NSArray (IndexSetAddition) + +- (NSArray *) subarrayWithIndexes: (NSIndexSet *)indexes{ + NSMutableArray *targetArray = [NSMutableArray array]; + NSUInteger count = [self count]; + + NSUInteger index = [indexes firstIndex]; + while ( index != NSNotFound ) { + if ( index < count ) + [targetArray addObject: [self objectAtIndex: index]]; + + index = [indexes indexGreaterThanIndex: index]; + } + + return targetArray; +} + +@end \ No newline at end of file diff --git a/Seqotron/NSObject+TDBindings.h b/Seqotron/NSObject+TDBindings.h new file mode 100644 index 0000000..155f1b5 --- /dev/null +++ b/Seqotron/NSObject+TDBindings.h @@ -0,0 +1,30 @@ +// +// NSObject+TDBindings.h +// Seqotron +// +// Created by Mathieu Fourment on 25/03/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +@interface NSObject (TDBindings) + +-(void) propagateValue:(id)value forBinding:(NSString*)binding; + +@end diff --git a/Seqotron/NSObject+TDBindings.m b/Seqotron/NSObject+TDBindings.m new file mode 100644 index 0000000..7fa9aa6 --- /dev/null +++ b/Seqotron/NSObject+TDBindings.m @@ -0,0 +1,71 @@ +// +// NSObject+TDBindings.m +// Seqotron +// +// Created by Mathieu Fourment on 25/03/2015. +// Copyright (c) 2015 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import "NSObject+TDBindings.h" + +@implementation NSObject (TDBindings) + +-(void) propagateValue:(id)value forBinding:(NSString*)binding{ + NSParameterAssert(binding != nil); + + //WARNING: bindingInfo contains NSNull, so it must be accounted for + NSDictionary* bindingInfo = [self infoForBinding:binding]; + if(!bindingInfo) + return; //there is no binding + + //apply the value transformer, if one has been set + NSDictionary* bindingOptions = [bindingInfo objectForKey:NSOptionsKey]; + if(bindingOptions){ + NSValueTransformer* transformer = [bindingOptions valueForKey:NSValueTransformerBindingOption]; + if(!transformer || (id)transformer == [NSNull null]){ + NSString* transformerName = [bindingOptions valueForKey:NSValueTransformerNameBindingOption]; + if(transformerName && (id)transformerName != [NSNull null]){ + transformer = [NSValueTransformer valueTransformerForName:transformerName]; + } + } + + if(transformer && (id)transformer != [NSNull null]){ + if([[transformer class] allowsReverseTransformation]){ + value = [transformer reverseTransformedValue:value]; + } else { + NSLog(@"WARNING: binding \"%@\" has value transformer, but it doesn't allow reverse transformations in %s", binding, __PRETTY_FUNCTION__); + } + } + } + + id boundObject = [bindingInfo objectForKey:NSObservedObjectKey]; + if(!boundObject || boundObject == [NSNull null]){ + NSLog(@"ERROR: NSObservedObjectKey was nil for binding \"%@\" in %s", binding, __PRETTY_FUNCTION__); + return; + } + + NSString* boundKeyPath = [bindingInfo objectForKey:NSObservedKeyPathKey]; + if(!boundKeyPath || (id)boundKeyPath == [NSNull null]){ + NSLog(@"ERROR: NSObservedKeyPathKey was nil for binding \"%@\" in %s", binding, __PRETTY_FUNCTION__); + return; + } + + [boundObject setValue:value forKeyPath:boundKeyPath]; +} + +@end diff --git a/Seqotron/Seqotron-Info.plist b/Seqotron/Seqotron-Info.plist new file mode 100755 index 0000000..0b2dc5f --- /dev/null +++ b/Seqotron/Seqotron-Info.plist @@ -0,0 +1,69 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + * + + CFBundleTypeName + Alignment + CFBundleTypeRole + Editor + CFBundleTypesOSTypes + ???? + LSTypeIsPackage + 0 + NSDocumentClass + MFDocument + + + CFBundleTypeExtensions + + * + + CFBundleTypeName + Tree + CFBundleTypeRole + Editor + CFBundleTypesOSTypes + ???? + LSTypeIsPackage + 0 + NSDocumentClass + MFTreeDocument + + + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + AppIcon + CFBundleIdentifier + tk.phylogenetics.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSMinimumSystemVersion + ${MACOSX_DEPLOYMENT_TARGET} + NSHumanReadableCopyright + Copyright © 2015 Mathieu Fourment. All rights reserved. + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/Seqotron/Seqotron-Prefix.pch b/Seqotron/Seqotron-Prefix.pch new file mode 100755 index 0000000..35d7640 --- /dev/null +++ b/Seqotron/Seqotron-Prefix.pch @@ -0,0 +1,9 @@ +// +// Prefix header +// +// The contents of this file are implicitly included at the beginning of every source file. +// + +#ifdef __OBJC__ + #import +#endif diff --git a/Seqotron/en.lproj/Credits.rtf b/Seqotron/en.lproj/Credits.rtf new file mode 100755 index 0000000..cb4c10d --- /dev/null +++ b/Seqotron/en.lproj/Credits.rtf @@ -0,0 +1,32 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1347\cocoasubrtf570 +{\fonttbl\f0\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\paperw11900\paperh16840\vieww15040\viewh8400\viewkind0 +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural + +\f0\b\fs24 \cf0 Source code: +\b0 \ + {\field{\*\fldinst{HYPERLINK "https://www.github.com/4ment/seqotron"}}{\fldrslt https://www.github.com/4ment/seqotron}}\ +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720 + +\b \cf0 \ +Documentation: +\b0 \ +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural +\cf0 {\field{\*\fldinst{HYPERLINK "https://www.github.com/4ment/seqotron/wiki"}}{\fldrslt https://www.github.com/4ment/seqotron/wiki}} +\b \ +\ +License:\ + +\b0 GNU GPL\ + +\b \ +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720 +\cf0 Engineering: +\b0 \ + Mathieu Fourment\ +\ + +\b Human Interface Design: +\b0 \ + Mathieu Fourment} \ No newline at end of file diff --git a/Seqotron/en.lproj/InfoPlist.strings b/Seqotron/en.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ b/Seqotron/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/Seqotron/main.m b/Seqotron/main.m new file mode 100755 index 0000000..5d5bc25 --- /dev/null +++ b/Seqotron/main.m @@ -0,0 +1,29 @@ +// +// main.m +// Seqotron +// +// Created by Mathieu Fourment on 25/07/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// +// This file is part of Seqotron. +// +// Seqotron is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Seqotron is distributed in the hope that it will be useful, +// but Seqotron ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Seqotron. If not, see . +// + +#import + +int main(int argc, const char * argv[]) +{ + return NSApplicationMain(argc, argv); +} diff --git a/SeqotronQuicklook/SeqotronQuicklook.xcodeproj/project.pbxproj b/SeqotronQuicklook/SeqotronQuicklook.xcodeproj/project.pbxproj new file mode 100755 index 0000000..354a2a8 --- /dev/null +++ b/SeqotronQuicklook/SeqotronQuicklook.xcodeproj/project.pbxproj @@ -0,0 +1,443 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 4E0FB4B01AE4A8BB00FCDF82 /* MFReaderCluster.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E0FB4AE1AE4A8BB00FCDF82 /* MFReaderCluster.h */; }; + 4E0FB4B11AE4A8BB00FCDF82 /* MFReaderCluster.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E0FB4AF1AE4A8BB00FCDF82 /* MFReaderCluster.m */; }; + 4E0FB4B41AE4A90900FCDF82 /* MFString.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E0FB4B21AE4A90900FCDF82 /* MFString.h */; }; + 4E0FB4B51AE4A90900FCDF82 /* MFString.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E0FB4B31AE4A90900FCDF82 /* MFString.m */; }; + 4E0FB4BA1AE4C2AD00FCDF82 /* MFSequenceReader.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E0FB4B61AE4C2AD00FCDF82 /* MFSequenceReader.h */; }; + 4E0FB4BB1AE4C2AD00FCDF82 /* MFSequenceReader.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E0FB4B71AE4C2AD00FCDF82 /* MFSequenceReader.m */; }; + 4E0FB4BC1AE4C2AD00FCDF82 /* MFSequenceSet.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E0FB4B81AE4C2AD00FCDF82 /* MFSequenceSet.h */; }; + 4E0FB4BD1AE4C2AD00FCDF82 /* MFSequenceSet.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E0FB4B91AE4C2AD00FCDF82 /* MFSequenceSet.m */; }; + 4E0FB4C51AE4C65900FCDF82 /* MFMEGAImporter.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E0FB4BE1AE4C65900FCDF82 /* MFMEGAImporter.h */; }; + 4E0FB4C61AE4C65900FCDF82 /* MFMEGAImporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E0FB4BF1AE4C65900FCDF82 /* MFMEGAImporter.m */; }; + 4E0FB4C71AE4C65900FCDF82 /* MFNBRFImporter.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E0FB4C01AE4C65900FCDF82 /* MFNBRFImporter.h */; }; + 4E0FB4C81AE4C65900FCDF82 /* MFNBRFImporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E0FB4C11AE4C65900FCDF82 /* MFNBRFImporter.m */; }; + 4E0FB4C91AE4C65900FCDF82 /* MFNexusImporter.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E0FB4C21AE4C65900FCDF82 /* MFNexusImporter.h */; }; + 4E0FB4CA1AE4C65900FCDF82 /* MFNexusImporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E0FB4C31AE4C65900FCDF82 /* MFNexusImporter.m */; }; + 4E0FB4CB1AE4C65900FCDF82 /* MFSequenceImporter.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E0FB4C41AE4C65900FCDF82 /* MFSequenceImporter.h */; }; + 4E0FB4E51AE4C7D300FCDF82 /* MFClustalImporter.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E0FB4CC1AE4C7D300FCDF82 /* MFClustalImporter.h */; }; + 4E0FB4E61AE4C7D300FCDF82 /* MFClustalImporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E0FB4CD1AE4C7D300FCDF82 /* MFClustalImporter.m */; }; + 4E0FB4E71AE4C7D300FCDF82 /* MFDataType.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E0FB4CE1AE4C7D300FCDF82 /* MFDataType.h */; }; + 4E0FB4E81AE4C7D300FCDF82 /* MFDataType.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E0FB4CF1AE4C7D300FCDF82 /* MFDataType.m */; }; + 4E0FB4E91AE4C7D300FCDF82 /* MFFASTAImporter.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E0FB4D01AE4C7D300FCDF82 /* MFFASTAImporter.h */; }; + 4E0FB4EA1AE4C7D300FCDF82 /* MFFASTAImporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E0FB4D11AE4C7D300FCDF82 /* MFFASTAImporter.m */; }; + 4E0FB4EB1AE4C7D300FCDF82 /* MFFileReader.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E0FB4D21AE4C7D300FCDF82 /* MFFileReader.h */; }; + 4E0FB4EC1AE4C7D300FCDF82 /* MFFileReader.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E0FB4D31AE4C7D300FCDF82 /* MFFileReader.m */; }; + 4E0FB4ED1AE4C7D300FCDF82 /* MFGDEImporter.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E0FB4D41AE4C7D300FCDF82 /* MFGDEImporter.h */; }; + 4E0FB4EE1AE4C7D300FCDF82 /* MFGDEImporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E0FB4D51AE4C7D300FCDF82 /* MFGDEImporter.m */; }; + 4E0FB4EF1AE4C7D300FCDF82 /* MFNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E0FB4D61AE4C7D300FCDF82 /* MFNode.h */; }; + 4E0FB4F01AE4C7D300FCDF82 /* MFNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E0FB4D71AE4C7D300FCDF82 /* MFNode.m */; }; + 4E0FB4F11AE4C7D300FCDF82 /* MFNucleotide.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E0FB4D81AE4C7D300FCDF82 /* MFNucleotide.h */; }; + 4E0FB4F21AE4C7D300FCDF82 /* MFNucleotide.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E0FB4D91AE4C7D300FCDF82 /* MFNucleotide.m */; }; + 4E0FB4F31AE4C7D300FCDF82 /* MFPhylipImporter.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E0FB4DA1AE4C7D300FCDF82 /* MFPhylipImporter.h */; }; + 4E0FB4F41AE4C7D300FCDF82 /* MFPhylipImporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E0FB4DB1AE4C7D300FCDF82 /* MFPhylipImporter.m */; }; + 4E0FB4F51AE4C7D300FCDF82 /* MFProtein.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E0FB4DC1AE4C7D300FCDF82 /* MFProtein.h */; }; + 4E0FB4F61AE4C7D300FCDF82 /* MFProtein.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E0FB4DD1AE4C7D300FCDF82 /* MFProtein.m */; }; + 4E0FB4F71AE4C7D300FCDF82 /* MFSequence.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E0FB4DE1AE4C7D300FCDF82 /* MFSequence.h */; }; + 4E0FB4F81AE4C7D300FCDF82 /* MFSequence.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E0FB4DF1AE4C7D300FCDF82 /* MFSequence.m */; }; + 4E0FB4F91AE4C7D300FCDF82 /* MFStockholmImporter.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E0FB4E01AE4C7D300FCDF82 /* MFStockholmImporter.h */; }; + 4E0FB4FA1AE4C7D300FCDF82 /* MFStockholmImporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E0FB4E11AE4C7D300FCDF82 /* MFStockholmImporter.m */; }; + 4E0FB4FB1AE4C7D300FCDF82 /* MFStringReader.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E0FB4E21AE4C7D300FCDF82 /* MFStringReader.h */; }; + 4E0FB4FC1AE4C7D300FCDF82 /* MFStringReader.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E0FB4E31AE4C7D300FCDF82 /* MFStringReader.m */; }; + 4E0FB4FD1AE4C7D300FCDF82 /* MFTreeImporter.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E0FB4E41AE4C7D300FCDF82 /* MFTreeImporter.h */; }; + 4E0FB5001AE4C84500FCDF82 /* MFTree.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E0FB4FE1AE4C84500FCDF82 /* MFTree.h */; }; + 4E0FB5011AE4C84500FCDF82 /* MFTree.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E0FB4FF1AE4C84500FCDF82 /* MFTree.m */; }; + 4EE911B81A2D6777009A11D7 /* GenerateThumbnailForURL.c in Sources */ = {isa = PBXBuildFile; fileRef = 4EE911B71A2D6777009A11D7 /* GenerateThumbnailForURL.c */; }; + 4EE911BA1A2D6777009A11D7 /* GeneratePreviewForURL.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EE911B91A2D6777009A11D7 /* GeneratePreviewForURL.m */; }; + 4EE911BC1A2D6777009A11D7 /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = 4EE911BB1A2D6777009A11D7 /* main.c */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 4E0FB4AE1AE4A8BB00FCDF82 /* MFReaderCluster.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MFReaderCluster.h; path = ../../Seqotron/MFReaderCluster.h; sourceTree = ""; }; + 4E0FB4AF1AE4A8BB00FCDF82 /* MFReaderCluster.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MFReaderCluster.m; path = ../../Seqotron/MFReaderCluster.m; sourceTree = ""; }; + 4E0FB4B21AE4A90900FCDF82 /* MFString.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MFString.h; path = ../../Seqotron/MFString.h; sourceTree = ""; }; + 4E0FB4B31AE4A90900FCDF82 /* MFString.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MFString.m; path = ../../Seqotron/MFString.m; sourceTree = ""; }; + 4E0FB4B61AE4C2AD00FCDF82 /* MFSequenceReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MFSequenceReader.h; path = ../../Seqotron/MFSequenceReader.h; sourceTree = ""; }; + 4E0FB4B71AE4C2AD00FCDF82 /* MFSequenceReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MFSequenceReader.m; path = ../../Seqotron/MFSequenceReader.m; sourceTree = ""; }; + 4E0FB4B81AE4C2AD00FCDF82 /* MFSequenceSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MFSequenceSet.h; path = ../../Seqotron/MFSequenceSet.h; sourceTree = ""; }; + 4E0FB4B91AE4C2AD00FCDF82 /* MFSequenceSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MFSequenceSet.m; path = ../../Seqotron/MFSequenceSet.m; sourceTree = ""; }; + 4E0FB4BE1AE4C65900FCDF82 /* MFMEGAImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MFMEGAImporter.h; path = ../../Seqotron/MFMEGAImporter.h; sourceTree = ""; }; + 4E0FB4BF1AE4C65900FCDF82 /* MFMEGAImporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MFMEGAImporter.m; path = ../../Seqotron/MFMEGAImporter.m; sourceTree = ""; }; + 4E0FB4C01AE4C65900FCDF82 /* MFNBRFImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MFNBRFImporter.h; path = ../../Seqotron/MFNBRFImporter.h; sourceTree = ""; }; + 4E0FB4C11AE4C65900FCDF82 /* MFNBRFImporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MFNBRFImporter.m; path = ../../Seqotron/MFNBRFImporter.m; sourceTree = ""; }; + 4E0FB4C21AE4C65900FCDF82 /* MFNexusImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MFNexusImporter.h; path = ../../Seqotron/MFNexusImporter.h; sourceTree = ""; }; + 4E0FB4C31AE4C65900FCDF82 /* MFNexusImporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MFNexusImporter.m; path = ../../Seqotron/MFNexusImporter.m; sourceTree = ""; }; + 4E0FB4C41AE4C65900FCDF82 /* MFSequenceImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MFSequenceImporter.h; path = ../../Seqotron/MFSequenceImporter.h; sourceTree = ""; }; + 4E0FB4CC1AE4C7D300FCDF82 /* MFClustalImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MFClustalImporter.h; path = ../../Seqotron/MFClustalImporter.h; sourceTree = ""; }; + 4E0FB4CD1AE4C7D300FCDF82 /* MFClustalImporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MFClustalImporter.m; path = ../../Seqotron/MFClustalImporter.m; sourceTree = ""; }; + 4E0FB4CE1AE4C7D300FCDF82 /* MFDataType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MFDataType.h; path = ../../Seqotron/MFDataType.h; sourceTree = ""; }; + 4E0FB4CF1AE4C7D300FCDF82 /* MFDataType.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MFDataType.m; path = ../../Seqotron/MFDataType.m; sourceTree = ""; }; + 4E0FB4D01AE4C7D300FCDF82 /* MFFASTAImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MFFASTAImporter.h; path = ../../Seqotron/MFFASTAImporter.h; sourceTree = ""; }; + 4E0FB4D11AE4C7D300FCDF82 /* MFFASTAImporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MFFASTAImporter.m; path = ../../Seqotron/MFFASTAImporter.m; sourceTree = ""; }; + 4E0FB4D21AE4C7D300FCDF82 /* MFFileReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MFFileReader.h; path = ../../Seqotron/MFFileReader.h; sourceTree = ""; }; + 4E0FB4D31AE4C7D300FCDF82 /* MFFileReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MFFileReader.m; path = ../../Seqotron/MFFileReader.m; sourceTree = ""; }; + 4E0FB4D41AE4C7D300FCDF82 /* MFGDEImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MFGDEImporter.h; path = ../../Seqotron/MFGDEImporter.h; sourceTree = ""; }; + 4E0FB4D51AE4C7D300FCDF82 /* MFGDEImporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MFGDEImporter.m; path = ../../Seqotron/MFGDEImporter.m; sourceTree = ""; }; + 4E0FB4D61AE4C7D300FCDF82 /* MFNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MFNode.h; path = ../../Seqotron/MFNode.h; sourceTree = ""; }; + 4E0FB4D71AE4C7D300FCDF82 /* MFNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MFNode.m; path = ../../Seqotron/MFNode.m; sourceTree = ""; }; + 4E0FB4D81AE4C7D300FCDF82 /* MFNucleotide.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MFNucleotide.h; path = ../../Seqotron/MFNucleotide.h; sourceTree = ""; }; + 4E0FB4D91AE4C7D300FCDF82 /* MFNucleotide.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MFNucleotide.m; path = ../../Seqotron/MFNucleotide.m; sourceTree = ""; }; + 4E0FB4DA1AE4C7D300FCDF82 /* MFPhylipImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MFPhylipImporter.h; path = ../../Seqotron/MFPhylipImporter.h; sourceTree = ""; }; + 4E0FB4DB1AE4C7D300FCDF82 /* MFPhylipImporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MFPhylipImporter.m; path = ../../Seqotron/MFPhylipImporter.m; sourceTree = ""; }; + 4E0FB4DC1AE4C7D300FCDF82 /* MFProtein.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MFProtein.h; path = ../../Seqotron/MFProtein.h; sourceTree = ""; }; + 4E0FB4DD1AE4C7D300FCDF82 /* MFProtein.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MFProtein.m; path = ../../Seqotron/MFProtein.m; sourceTree = ""; }; + 4E0FB4DE1AE4C7D300FCDF82 /* MFSequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MFSequence.h; path = ../../Seqotron/MFSequence.h; sourceTree = ""; }; + 4E0FB4DF1AE4C7D300FCDF82 /* MFSequence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MFSequence.m; path = ../../Seqotron/MFSequence.m; sourceTree = ""; }; + 4E0FB4E01AE4C7D300FCDF82 /* MFStockholmImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MFStockholmImporter.h; path = ../../Seqotron/MFStockholmImporter.h; sourceTree = ""; }; + 4E0FB4E11AE4C7D300FCDF82 /* MFStockholmImporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MFStockholmImporter.m; path = ../../Seqotron/MFStockholmImporter.m; sourceTree = ""; }; + 4E0FB4E21AE4C7D300FCDF82 /* MFStringReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MFStringReader.h; path = ../../Seqotron/MFStringReader.h; sourceTree = ""; }; + 4E0FB4E31AE4C7D300FCDF82 /* MFStringReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MFStringReader.m; path = ../../Seqotron/MFStringReader.m; sourceTree = ""; }; + 4E0FB4E41AE4C7D300FCDF82 /* MFTreeImporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MFTreeImporter.h; path = ../../Seqotron/MFTreeImporter.h; sourceTree = ""; }; + 4E0FB4FE1AE4C84500FCDF82 /* MFTree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MFTree.h; path = ../../Seqotron/MFTree.h; sourceTree = ""; }; + 4E0FB4FF1AE4C84500FCDF82 /* MFTree.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MFTree.m; path = ../../Seqotron/MFTree.m; sourceTree = ""; }; + 4EE911B21A2D6777009A11D7 /* SeqotronQuicklook.qlgenerator */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SeqotronQuicklook.qlgenerator; sourceTree = BUILT_PRODUCTS_DIR; }; + 4EE911B61A2D6777009A11D7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 4EE911B71A2D6777009A11D7 /* GenerateThumbnailForURL.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = GenerateThumbnailForURL.c; sourceTree = ""; }; + 4EE911B91A2D6777009A11D7 /* GeneratePreviewForURL.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GeneratePreviewForURL.m; sourceTree = ""; }; + 4EE911BB1A2D6777009A11D7 /* main.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = main.c; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 4EE911AE1A2D6777009A11D7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 4EE911A81A2D6776009A11D7 = { + isa = PBXGroup; + children = ( + 4EE911B41A2D6777009A11D7 /* SeqotronQuicklook */, + 4EE911B31A2D6777009A11D7 /* Products */, + ); + sourceTree = ""; + }; + 4EE911B31A2D6777009A11D7 /* Products */ = { + isa = PBXGroup; + children = ( + 4EE911B21A2D6777009A11D7 /* SeqotronQuicklook.qlgenerator */, + ); + name = Products; + sourceTree = ""; + }; + 4EE911B41A2D6777009A11D7 /* SeqotronQuicklook */ = { + isa = PBXGroup; + children = ( + 4EE911B91A2D6777009A11D7 /* GeneratePreviewForURL.m */, + 4EE911B71A2D6777009A11D7 /* GenerateThumbnailForURL.c */, + 4EE911BB1A2D6777009A11D7 /* main.c */, + 4E0FB4CC1AE4C7D300FCDF82 /* MFClustalImporter.h */, + 4E0FB4CD1AE4C7D300FCDF82 /* MFClustalImporter.m */, + 4E0FB4CE1AE4C7D300FCDF82 /* MFDataType.h */, + 4E0FB4CF1AE4C7D300FCDF82 /* MFDataType.m */, + 4E0FB4D01AE4C7D300FCDF82 /* MFFASTAImporter.h */, + 4E0FB4D11AE4C7D300FCDF82 /* MFFASTAImporter.m */, + 4E0FB4D21AE4C7D300FCDF82 /* MFFileReader.h */, + 4E0FB4D31AE4C7D300FCDF82 /* MFFileReader.m */, + 4E0FB4D41AE4C7D300FCDF82 /* MFGDEImporter.h */, + 4E0FB4D51AE4C7D300FCDF82 /* MFGDEImporter.m */, + 4E0FB4BE1AE4C65900FCDF82 /* MFMEGAImporter.h */, + 4E0FB4BF1AE4C65900FCDF82 /* MFMEGAImporter.m */, + 4E0FB4C01AE4C65900FCDF82 /* MFNBRFImporter.h */, + 4E0FB4C11AE4C65900FCDF82 /* MFNBRFImporter.m */, + 4E0FB4C21AE4C65900FCDF82 /* MFNexusImporter.h */, + 4E0FB4C31AE4C65900FCDF82 /* MFNexusImporter.m */, + 4E0FB4D61AE4C7D300FCDF82 /* MFNode.h */, + 4E0FB4D71AE4C7D300FCDF82 /* MFNode.m */, + 4E0FB4D81AE4C7D300FCDF82 /* MFNucleotide.h */, + 4E0FB4D91AE4C7D300FCDF82 /* MFNucleotide.m */, + 4E0FB4DA1AE4C7D300FCDF82 /* MFPhylipImporter.h */, + 4E0FB4DB1AE4C7D300FCDF82 /* MFPhylipImporter.m */, + 4E0FB4DC1AE4C7D300FCDF82 /* MFProtein.h */, + 4E0FB4DD1AE4C7D300FCDF82 /* MFProtein.m */, + 4E0FB4AE1AE4A8BB00FCDF82 /* MFReaderCluster.h */, + 4E0FB4AF1AE4A8BB00FCDF82 /* MFReaderCluster.m */, + 4E0FB4DE1AE4C7D300FCDF82 /* MFSequence.h */, + 4E0FB4DF1AE4C7D300FCDF82 /* MFSequence.m */, + 4E0FB4C41AE4C65900FCDF82 /* MFSequenceImporter.h */, + 4E0FB4B61AE4C2AD00FCDF82 /* MFSequenceReader.h */, + 4E0FB4FE1AE4C84500FCDF82 /* MFTree.h */, + 4E0FB4FF1AE4C84500FCDF82 /* MFTree.m */, + 4E0FB4B71AE4C2AD00FCDF82 /* MFSequenceReader.m */, + 4E0FB4B81AE4C2AD00FCDF82 /* MFSequenceSet.h */, + 4E0FB4B91AE4C2AD00FCDF82 /* MFSequenceSet.m */, + 4E0FB4E01AE4C7D300FCDF82 /* MFStockholmImporter.h */, + 4E0FB4E11AE4C7D300FCDF82 /* MFStockholmImporter.m */, + 4E0FB4B21AE4A90900FCDF82 /* MFString.h */, + 4E0FB4B31AE4A90900FCDF82 /* MFString.m */, + 4E0FB4E21AE4C7D300FCDF82 /* MFStringReader.h */, + 4E0FB4E31AE4C7D300FCDF82 /* MFStringReader.m */, + 4E0FB4E41AE4C7D300FCDF82 /* MFTreeImporter.h */, + 4EE911B51A2D6777009A11D7 /* Supporting Files */, + ); + path = SeqotronQuicklook; + sourceTree = ""; + }; + 4EE911B51A2D6777009A11D7 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 4EE911B61A2D6777009A11D7 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 4EE911AF1A2D6777009A11D7 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 4E0FB4F71AE4C7D300FCDF82 /* MFSequence.h in Headers */, + 4E0FB4CB1AE4C65900FCDF82 /* MFSequenceImporter.h in Headers */, + 4E0FB4E71AE4C7D300FCDF82 /* MFDataType.h in Headers */, + 4E0FB4C51AE4C65900FCDF82 /* MFMEGAImporter.h in Headers */, + 4E0FB4F91AE4C7D300FCDF82 /* MFStockholmImporter.h in Headers */, + 4E0FB5001AE4C84500FCDF82 /* MFTree.h in Headers */, + 4E0FB4F31AE4C7D300FCDF82 /* MFPhylipImporter.h in Headers */, + 4E0FB4FD1AE4C7D300FCDF82 /* MFTreeImporter.h in Headers */, + 4E0FB4B41AE4A90900FCDF82 /* MFString.h in Headers */, + 4E0FB4E91AE4C7D300FCDF82 /* MFFASTAImporter.h in Headers */, + 4E0FB4F11AE4C7D300FCDF82 /* MFNucleotide.h in Headers */, + 4E0FB4BC1AE4C2AD00FCDF82 /* MFSequenceSet.h in Headers */, + 4E0FB4E51AE4C7D300FCDF82 /* MFClustalImporter.h in Headers */, + 4E0FB4C71AE4C65900FCDF82 /* MFNBRFImporter.h in Headers */, + 4E0FB4B01AE4A8BB00FCDF82 /* MFReaderCluster.h in Headers */, + 4E0FB4ED1AE4C7D300FCDF82 /* MFGDEImporter.h in Headers */, + 4E0FB4C91AE4C65900FCDF82 /* MFNexusImporter.h in Headers */, + 4E0FB4F51AE4C7D300FCDF82 /* MFProtein.h in Headers */, + 4E0FB4EF1AE4C7D300FCDF82 /* MFNode.h in Headers */, + 4E0FB4EB1AE4C7D300FCDF82 /* MFFileReader.h in Headers */, + 4E0FB4BA1AE4C2AD00FCDF82 /* MFSequenceReader.h in Headers */, + 4E0FB4FB1AE4C7D300FCDF82 /* MFStringReader.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 4EE911B11A2D6777009A11D7 /* SeqotronQuicklook */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4EE911BF1A2D6777009A11D7 /* Build configuration list for PBXNativeTarget "SeqotronQuicklook" */; + buildPhases = ( + 4EE911AD1A2D6777009A11D7 /* Sources */, + 4EE911AE1A2D6777009A11D7 /* Frameworks */, + 4EE911AF1A2D6777009A11D7 /* Headers */, + 4EE911B01A2D6777009A11D7 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SeqotronQuicklook; + productName = SeqotronQuicklook; + productReference = 4EE911B21A2D6777009A11D7 /* SeqotronQuicklook.qlgenerator */; + productType = "com.apple.product-type.bundle"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 4EE911A91A2D6777009A11D7 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0610; + ORGANIZATIONNAME = "University of Sydney"; + TargetAttributes = { + 4EE911B11A2D6777009A11D7 = { + CreatedOnToolsVersion = 6.1; + }; + }; + }; + buildConfigurationList = 4EE911AC1A2D6777009A11D7 /* Build configuration list for PBXProject "SeqotronQuicklook" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 4EE911A81A2D6776009A11D7; + productRefGroup = 4EE911B31A2D6777009A11D7 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 4EE911B11A2D6777009A11D7 /* SeqotronQuicklook */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 4EE911B01A2D6777009A11D7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 4EE911AD1A2D6777009A11D7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4E0FB4F21AE4C7D300FCDF82 /* MFNucleotide.m in Sources */, + 4E0FB4CA1AE4C65900FCDF82 /* MFNexusImporter.m in Sources */, + 4E0FB4BB1AE4C2AD00FCDF82 /* MFSequenceReader.m in Sources */, + 4E0FB4F81AE4C7D300FCDF82 /* MFSequence.m in Sources */, + 4E0FB4C81AE4C65900FCDF82 /* MFNBRFImporter.m in Sources */, + 4E0FB4E61AE4C7D300FCDF82 /* MFClustalImporter.m in Sources */, + 4E0FB4B11AE4A8BB00FCDF82 /* MFReaderCluster.m in Sources */, + 4E0FB4EE1AE4C7D300FCDF82 /* MFGDEImporter.m in Sources */, + 4E0FB4FC1AE4C7D300FCDF82 /* MFStringReader.m in Sources */, + 4E0FB4EC1AE4C7D300FCDF82 /* MFFileReader.m in Sources */, + 4E0FB4FA1AE4C7D300FCDF82 /* MFStockholmImporter.m in Sources */, + 4E0FB4C61AE4C65900FCDF82 /* MFMEGAImporter.m in Sources */, + 4EE911B81A2D6777009A11D7 /* GenerateThumbnailForURL.c in Sources */, + 4E0FB4F41AE4C7D300FCDF82 /* MFPhylipImporter.m in Sources */, + 4E0FB4BD1AE4C2AD00FCDF82 /* MFSequenceSet.m in Sources */, + 4EE911BA1A2D6777009A11D7 /* GeneratePreviewForURL.m in Sources */, + 4E0FB4B51AE4A90900FCDF82 /* MFString.m in Sources */, + 4E0FB4F01AE4C7D300FCDF82 /* MFNode.m in Sources */, + 4E0FB4F61AE4C7D300FCDF82 /* MFProtein.m in Sources */, + 4E0FB5011AE4C84500FCDF82 /* MFTree.m in Sources */, + 4E0FB4E81AE4C7D300FCDF82 /* MFDataType.m in Sources */, + 4E0FB4EA1AE4C7D300FCDF82 /* MFFASTAImporter.m in Sources */, + 4EE911BC1A2D6777009A11D7 /* main.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 4EE911BD1A2D6777009A11D7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = NO; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + 4EE911BE1A2D6777009A11D7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = NO; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + }; + name = Release; + }; + 4EE911C01A2D6777009A11D7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = SeqotronQuicklook/Info.plist; + INSTALL_PATH = /Library/QuickLook; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = qlgenerator; + }; + name = Debug; + }; + 4EE911C11A2D6777009A11D7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = SeqotronQuicklook/Info.plist; + INSTALL_PATH = /Library/QuickLook; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = qlgenerator; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 4EE911AC1A2D6777009A11D7 /* Build configuration list for PBXProject "SeqotronQuicklook" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4EE911BD1A2D6777009A11D7 /* Debug */, + 4EE911BE1A2D6777009A11D7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4EE911BF1A2D6777009A11D7 /* Build configuration list for PBXNativeTarget "SeqotronQuicklook" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4EE911C01A2D6777009A11D7 /* Debug */, + 4EE911C11A2D6777009A11D7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 4EE911A91A2D6777009A11D7 /* Project object */; +} diff --git a/SeqotronQuicklook/SeqotronQuicklook.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/SeqotronQuicklook/SeqotronQuicklook.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100755 index 0000000..918ec52 --- /dev/null +++ b/SeqotronQuicklook/SeqotronQuicklook.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/SeqotronQuicklook/SeqotronQuicklook.xcodeproj/project.xcworkspace/xcshareddata/SeqotronQuicklook.xccheckout b/SeqotronQuicklook/SeqotronQuicklook.xcodeproj/project.xcworkspace/xcshareddata/SeqotronQuicklook.xccheckout new file mode 100644 index 0000000..7eb41f2 --- /dev/null +++ b/SeqotronQuicklook/SeqotronQuicklook.xcodeproj/project.xcworkspace/xcshareddata/SeqotronQuicklook.xccheckout @@ -0,0 +1,41 @@ + + + + + IDESourceControlProjectFavoriteDictionaryKey + + IDESourceControlProjectIdentifier + 901513B2-DC86-4DDB-B643-FB5E9FEE7D08 + IDESourceControlProjectName + SeqotronQuicklook + IDESourceControlProjectOriginsDictionary + + 7DEF6595D8273C7A70F1ACB151468426C18DDA8A + https://bitbucket.org/4ment/Seqotron + + IDESourceControlProjectPath + SeqotronQuicklook/SeqotronQuicklook.xcodeproj + IDESourceControlProjectRelativeInstallPathDictionary + + 7DEF6595D8273C7A70F1ACB151468426C18DDA8A + ../../.. + + IDESourceControlProjectURL + https://bitbucket.org/4ment/Seqotron + IDESourceControlProjectVersion + 111 + IDESourceControlProjectWCCIdentifier + 7DEF6595D8273C7A70F1ACB151468426C18DDA8A + IDESourceControlProjectWCConfigurations + + + IDESourceControlRepositoryExtensionIdentifierKey + public.vcs.git + IDESourceControlWCCIdentifierKey + 7DEF6595D8273C7A70F1ACB151468426C18DDA8A + IDESourceControlWCCName + Seqotron + + + + diff --git a/SeqotronQuicklook/SeqotronQuicklook/GeneratePreviewForURL.m b/SeqotronQuicklook/SeqotronQuicklook/GeneratePreviewForURL.m new file mode 100755 index 0000000..14e943e --- /dev/null +++ b/SeqotronQuicklook/SeqotronQuicklook/GeneratePreviewForURL.m @@ -0,0 +1,152 @@ +#import +#import +#import +#import + +#import "MFReaderCluster.h" +#import "MFString.h" +#import "MFSequenceReader.h" +#import "MFSequenceSet.h" + + +OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options); +void CancelPreviewGeneration(void *thisInterface, QLPreviewRequestRef preview); + +// By default a file is an alignment what ever the extension is. +NSString * typeForContentsOfURL(NSURL *url) { + if ( ![url isFileURL] ) { + return nil; + } + MFReaderCluster *reader = [[MFReaderCluster alloc]initWithFile:url.path]; + NSString *line = nil; + + BOOL isTree = NO; + BOOL isSequence = NO; + BOOL isNexus = NO; + + while ( (line = [reader readLine]) ) { + // Allow the first line to be empty + if( [[line stringByTrimmingPaddingWhitespace] length] == 0 ){ + continue; + } + if ( [[line uppercaseString] hasPrefix:@"#NEXUS"]) { + isNexus = YES; + } + else if( [line hasPrefix:@"("]){ + isTree = YES; + } + break; + } + + if ( isNexus ) { + while ( (line = [reader readLine]) ) { + if( [[line stringByTrimmingPaddingWhitespace] length] == 0 ){ + continue; + } + if( [[line uppercaseString] hasPrefix:@"BEGIN TREES"] ){ + NSLog(@"nexus tree"); + isTree = YES; + } + else if( [[line uppercaseString] hasPrefix:@"BEGIN DATA"] || [[line uppercaseString] hasPrefix:@"BEGIN CHARACTERS"] ){ + isSequence = YES; + NSLog(@"nexus sequence"); + } + if( isTree && isSequence)break; + } + } + [reader release]; + + if(isTree) return @"Tree"; + + return @"Alignment"; +} + +/* ----------------------------------------------------------------------------- + Generate a preview for file + + This function's job is to create preview for designated file + ----------------------------------------------------------------------------- */ + +OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options) +{ +// if (false == QLPreviewRequestIsCancelled(preview)) { + NSURL *myURL = (__bridge NSURL*)url; + + MFSequenceSet *sequenceSet = [MFSequenceReader readSequencesFromFile:myURL.path]; + + if( sequenceSet && [[sequenceSet sequences]count] > 0 ){ + if (false == QLPreviewRequestIsCancelled(preview)) { + + NSArray *sequenceArray = [sequenceSet sequences]; + + NSUInteger numberOfSequences = [sequenceArray count]; + BOOL aligned = YES; + NSUInteger alignmentLength = [[sequenceArray objectAtIndex:0]length]; + for ( NSUInteger i = 1; i < numberOfSequences; i++ ) { + if( alignmentLength != [[sequenceArray objectAtIndex:i]length] ){ + aligned = NO; + break; + } + } + + + + // compose the html + NSMutableString *html = [[NSMutableString alloc] initWithString:@"\n"]; + [html appendString:@"\n"]; + [html appendFormat:@"\n"]; + [html appendString:@"
Number of sequences: %lu Aligned: %@",numberOfSequences, (aligned ? @"yes":@"no")]; + + if( aligned ){ + [html appendFormat:@" Alignment length: %lu", alignmentLength]; + } + [html appendString:@"
"]; + + // add the table rows + BOOL altRow = NO; + for (MFSequence *seq in sequenceArray) { + if( [[seq name]length] > 20 ){ + [html appendFormat:@"", [[seq name]substringToIndex:20]]; + } + else { + [html appendFormat:@"", [seq name]]; + } + if( [[seq sequence]length] > 100 ){ + [html appendFormat:@"", [[seq sequence]substringToIndex:100]]; + } + else { + [html appendFormat:@"", [seq sequence]]; + } + [html appendString:@"\n"]; + + altRow = !altRow; + } + + [html appendString:@"
%@...
%@%@...%@
\n"]; + + [html appendString:@""]; + + // feed the HTML + CFDictionaryRef properties = (__bridge CFDictionaryRef)@{}; + QLPreviewRequestSetDataRepresentation(preview, + (__bridge CFDataRef)[html dataUsingEncoding:NSUTF8StringEncoding], + kUTTypeHTML, + properties + ); + } + } + // To complete your generator please implement the function GeneratePreviewForURL in GeneratePreviewForURL.c + return noErr; +} + +void CancelPreviewGeneration(void *thisInterface, QLPreviewRequestRef preview) +{ + // Implement only if supported +} + + + diff --git a/SeqotronQuicklook/SeqotronQuicklook/GenerateThumbnailForURL.c b/SeqotronQuicklook/SeqotronQuicklook/GenerateThumbnailForURL.c new file mode 100755 index 0000000..3a9d8a3 --- /dev/null +++ b/SeqotronQuicklook/SeqotronQuicklook/GenerateThumbnailForURL.c @@ -0,0 +1,24 @@ +#include +#include +#include + +OSStatus GenerateThumbnailForURL(void *thisInterface, QLThumbnailRequestRef thumbnail, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options, CGSize maxSize); +void CancelThumbnailGeneration(void *thisInterface, QLThumbnailRequestRef thumbnail); + +/* ----------------------------------------------------------------------------- + Generate a thumbnail for file + + This function's job is to create thumbnail for designated file as fast as possible + ----------------------------------------------------------------------------- */ + +OSStatus GenerateThumbnailForURL(void *thisInterface, QLThumbnailRequestRef thumbnail, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options, CGSize maxSize) +{ + //QLThumbnailRequestSetImageAtURL(thumbnail, url, NULL); + // To complete your generator please implement the function GenerateThumbnailForURL in GenerateThumbnailForURL.c + return noErr; +} + +void CancelThumbnailGeneration(void *thisInterface, QLThumbnailRequestRef thumbnail) +{ + // Implement only if supported +} diff --git a/SeqotronQuicklook/SeqotronQuicklook/Info.plist b/SeqotronQuicklook/SeqotronQuicklook/Info.plist new file mode 100755 index 0000000..49165e5 --- /dev/null +++ b/SeqotronQuicklook/SeqotronQuicklook/Info.plist @@ -0,0 +1,91 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDocumentTypes + + + CFBundleTypeRole + QLGenerator + LSItemContentTypes + + tk.phylogenetics.seqotron.sequence + + + + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + usyd.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + CFPlugInDynamicRegisterFunction + + CFPlugInDynamicRegistration + NO + CFPlugInFactories + + 9136D27A-5D31-4C88-8E50-ABAB2347526E + QuickLookGeneratorPluginFactory + + CFPlugInTypes + + 5E2D9680-5022-40FA-B806-43349622E5B9 + + 9136D27A-5D31-4C88-8E50-ABAB2347526E + + + CFPlugInUnloadFunction + + NSHumanReadableCopyright + Copyright © 2015 Mathieu Fourment. All rights reserved. + QLNeedsToBeRunInMainThread + + QLPreviewHeight + 600 + QLPreviewWidth + 800 + QLSupportsConcurrentRequests + + QLThumbnailMinimumSize + 17 + UTExportedTypeDeclarations + + + UTTypeConformsTo + + public.plain-text + + UTTypeDescription + Seqotron sequence file + UTTypeIdentifier + tk.phylogenetics.seqotron.sequence + UTTypeTagSpecification + + public.filename-extension + + seq + fa + fas + fasta + nex + nexus + nxs + phy + phylip + + + + + + diff --git a/SeqotronQuicklook/SeqotronQuicklook/main.c b/SeqotronQuicklook/SeqotronQuicklook/main.c new file mode 100755 index 0000000..02afa9c --- /dev/null +++ b/SeqotronQuicklook/SeqotronQuicklook/main.c @@ -0,0 +1,218 @@ +//============================================================================== +// +// DO NO MODIFY THE CONTENT OF THIS FILE +// +// This file contains the generic CFPlug-in code necessary for your generator +// To complete your generator implement the function in GenerateThumbnailForURL/GeneratePreviewForURL.c +// +//============================================================================== + + + + + + +#include +#include +#include +#include + +// ----------------------------------------------------------------------------- +// constants +// ----------------------------------------------------------------------------- + +// Don't modify this line +#define PLUGIN_ID "9136D27A-5D31-4C88-8E50-ABAB2347526E" + +// +// Below is the generic glue code for all plug-ins. +// +// You should not have to modify this code aside from changing +// names if you decide to change the names defined in the Info.plist +// + + +// ----------------------------------------------------------------------------- +// typedefs +// ----------------------------------------------------------------------------- + +// The thumbnail generation function to be implemented in GenerateThumbnailForURL.c +OSStatus GenerateThumbnailForURL(void *thisInterface, QLThumbnailRequestRef thumbnail, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options, CGSize maxSize); +void CancelThumbnailGeneration(void* thisInterface, QLThumbnailRequestRef thumbnail); + +// The preview generation function to be implemented in GeneratePreviewForURL.c +OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options); +void CancelPreviewGeneration(void *thisInterface, QLPreviewRequestRef preview); + +// The layout for an instance of QuickLookGeneratorPlugIn +typedef struct __QuickLookGeneratorPluginType +{ + void *conduitInterface; + CFUUIDRef factoryID; + UInt32 refCount; +} QuickLookGeneratorPluginType; + +// ----------------------------------------------------------------------------- +// prototypes +// ----------------------------------------------------------------------------- +// Forward declaration for the IUnknown implementation. +// + +QuickLookGeneratorPluginType *AllocQuickLookGeneratorPluginType(CFUUIDRef inFactoryID); +void DeallocQuickLookGeneratorPluginType(QuickLookGeneratorPluginType *thisInstance); +HRESULT QuickLookGeneratorQueryInterface(void *thisInstance,REFIID iid,LPVOID *ppv); +void *QuickLookGeneratorPluginFactory(CFAllocatorRef allocator,CFUUIDRef typeID); +ULONG QuickLookGeneratorPluginAddRef(void *thisInstance); +ULONG QuickLookGeneratorPluginRelease(void *thisInstance); + +// ----------------------------------------------------------------------------- +// myInterfaceFtbl definition +// ----------------------------------------------------------------------------- +// The QLGeneratorInterfaceStruct function table. +// +static QLGeneratorInterfaceStruct myInterfaceFtbl = { + NULL, + QuickLookGeneratorQueryInterface, + QuickLookGeneratorPluginAddRef, + QuickLookGeneratorPluginRelease, + NULL, + NULL, + NULL, + NULL +}; + + +// ----------------------------------------------------------------------------- +// AllocQuickLookGeneratorPluginType +// ----------------------------------------------------------------------------- +// Utility function that allocates a new instance. +// You can do some initial setup for the generator here if you wish +// like allocating globals etc... +// +QuickLookGeneratorPluginType *AllocQuickLookGeneratorPluginType(CFUUIDRef inFactoryID) +{ + QuickLookGeneratorPluginType *theNewInstance; + + theNewInstance = (QuickLookGeneratorPluginType *)malloc(sizeof(QuickLookGeneratorPluginType)); + memset(theNewInstance,0,sizeof(QuickLookGeneratorPluginType)); + + /* Point to the function table Malloc enough to store the stuff and copy the filler from myInterfaceFtbl over */ + theNewInstance->conduitInterface = malloc(sizeof(QLGeneratorInterfaceStruct)); + memcpy(theNewInstance->conduitInterface,&myInterfaceFtbl,sizeof(QLGeneratorInterfaceStruct)); + + /* Retain and keep an open instance refcount for each factory. */ + theNewInstance->factoryID = CFRetain(inFactoryID); + CFPlugInAddInstanceForFactory(inFactoryID); + + /* This function returns the IUnknown interface so set the refCount to one. */ + theNewInstance->refCount = 1; + return theNewInstance; +} + +// ----------------------------------------------------------------------------- +// DeallocQuickLookGeneratorPluginType +// ----------------------------------------------------------------------------- +// Utility function that deallocates the instance when +// the refCount goes to zero. +// In the current implementation generator interfaces are never deallocated +// but implement this as this might change in the future +// +void DeallocQuickLookGeneratorPluginType(QuickLookGeneratorPluginType *thisInstance) +{ + CFUUIDRef theFactoryID; + + theFactoryID = thisInstance->factoryID; + /* Free the conduitInterface table up */ + free(thisInstance->conduitInterface); + + /* Free the instance structure */ + free(thisInstance); + if (theFactoryID){ + CFPlugInRemoveInstanceForFactory(theFactoryID); + CFRelease(theFactoryID); + } +} + +// ----------------------------------------------------------------------------- +// QuickLookGeneratorQueryInterface +// ----------------------------------------------------------------------------- +// Implementation of the IUnknown QueryInterface function. +// +HRESULT QuickLookGeneratorQueryInterface(void *thisInstance,REFIID iid,LPVOID *ppv) +{ + CFUUIDRef interfaceID; + + interfaceID = CFUUIDCreateFromUUIDBytes(kCFAllocatorDefault,iid); + + if (CFEqual(interfaceID,kQLGeneratorCallbacksInterfaceID)){ + /* If the Right interface was requested, bump the ref count, + * set the ppv parameter equal to the instance, and + * return good status. + */ + ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->GenerateThumbnailForURL = GenerateThumbnailForURL; + ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->CancelThumbnailGeneration = CancelThumbnailGeneration; + ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->GeneratePreviewForURL = GeneratePreviewForURL; + ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->CancelPreviewGeneration = CancelPreviewGeneration; + ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType*)thisInstance)->conduitInterface)->AddRef(thisInstance); + *ppv = thisInstance; + CFRelease(interfaceID); + return S_OK; + }else{ + /* Requested interface unknown, bail with error. */ + *ppv = NULL; + CFRelease(interfaceID); + return E_NOINTERFACE; + } +} + +// ----------------------------------------------------------------------------- +// QuickLookGeneratorPluginAddRef +// ----------------------------------------------------------------------------- +// Implementation of reference counting for this type. Whenever an interface +// is requested, bump the refCount for the instance. NOTE: returning the +// refcount is a convention but is not required so don't rely on it. +// +ULONG QuickLookGeneratorPluginAddRef(void *thisInstance) +{ + ((QuickLookGeneratorPluginType *)thisInstance )->refCount += 1; + return ((QuickLookGeneratorPluginType*) thisInstance)->refCount; +} + +// ----------------------------------------------------------------------------- +// QuickLookGeneratorPluginRelease +// ----------------------------------------------------------------------------- +// When an interface is released, decrement the refCount. +// If the refCount goes to zero, deallocate the instance. +// +ULONG QuickLookGeneratorPluginRelease(void *thisInstance) +{ + ((QuickLookGeneratorPluginType*)thisInstance)->refCount -= 1; + if (((QuickLookGeneratorPluginType*)thisInstance)->refCount == 0){ + DeallocQuickLookGeneratorPluginType((QuickLookGeneratorPluginType*)thisInstance ); + return 0; + }else{ + return ((QuickLookGeneratorPluginType*) thisInstance )->refCount; + } +} + +// ----------------------------------------------------------------------------- +// QuickLookGeneratorPluginFactory +// ----------------------------------------------------------------------------- +void *QuickLookGeneratorPluginFactory(CFAllocatorRef allocator,CFUUIDRef typeID) +{ + QuickLookGeneratorPluginType *result; + CFUUIDRef uuid; + + /* If correct type is being requested, allocate an + * instance of kQLGeneratorTypeID and return the IUnknown interface. + */ + if (CFEqual(typeID,kQLGeneratorTypeID)){ + uuid = CFUUIDCreateFromString(kCFAllocatorDefault,CFSTR(PLUGIN_ID)); + result = AllocQuickLookGeneratorPluginType(uuid); + CFRelease(uuid); + return result; + } + /* If the requested type is incorrect, return NULL. */ + return NULL; +} + diff --git a/SeqotronTests/SeqotronTests-Info.plist b/SeqotronTests/SeqotronTests-Info.plist new file mode 100755 index 0000000..7b81abf --- /dev/null +++ b/SeqotronTests/SeqotronTests-Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + tk.phylogenetics.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/SeqotronTests/SeqotronTests.m b/SeqotronTests/SeqotronTests.m new file mode 100755 index 0000000..31432f4 --- /dev/null +++ b/SeqotronTests/SeqotronTests.m @@ -0,0 +1,29 @@ +// +// SeqotronTests.m +// SeqotronTests +// +// Created by Mathieu Fourment on 25/07/2014. +// Copyright (c) 2014 Mathieu Fourment. All rights reserved. +// + +#import + +@interface SeqotronTests : XCTestCase + +@end + +@implementation SeqotronTests + +- (void)setUp +{ + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown +{ + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +@end diff --git a/SeqotronTests/en.lproj/InfoPlist.strings b/SeqotronTests/en.lproj/InfoPlist.strings new file mode 100755 index 0000000..477b28f --- /dev/null +++ b/SeqotronTests/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/packaging.sh b/packaging.sh new file mode 100755 index 0000000..4e66a8d --- /dev/null +++ b/packaging.sh @@ -0,0 +1,163 @@ +#!/bin/bash + +# by Andy Maloney +# http://asmaloney.com/2013/07/howto/packaging-a-mac-os-x-application-using-a-dmg/ + +# set up your app name, version number, and background image file name +APP_NAME="Seqotron" +VERSION="1.0.0" +DMG_BACKGROUND_IMG="Background.png" +APP_DIR="DerivedData/Seqotron/Build/Products/Release" + + + +# you should not need to change these +APP_EXE="${APP_DIR}/${APP_NAME}.app/Contents/MacOS/${APP_NAME}" + +VOL_NAME="${APP_NAME} ${VERSION}" # volume name will be "SuperCoolApp 1.0.0" +DMG_TMP="${VOL_NAME}-temp.dmg" +DMG_FINAL="${VOL_NAME}.dmg" # final DMG name will be "SuperCoolApp 1.0.0.dmg" +STAGING_DIR="./Install" # we copy all our stuff into this dir + + if [ -z "$DMG_BACKGROUND_IMG" ]; then + # Check the background image DPI and convert it if it isn't 72x72 + _BACKGROUND_IMAGE_DPI_H=`sips -g dpiHeight ${DMG_BACKGROUND_IMG} | grep -Eo '[0-9]+\.[0-9]+'` + _BACKGROUND_IMAGE_DPI_W=`sips -g dpiWidth ${DMG_BACKGROUND_IMG} | grep -Eo '[0-9]+\.[0-9]+'` + + if [ $(echo " $_BACKGROUND_IMAGE_DPI_H != 72.0 " | bc) -eq 1 -o $(echo " $_BACKGROUND_IMAGE_DPI_W != 72.0 " | bc) -eq 1 ]; then + echo "WARNING: The background image's DPI is not 72. This will result in distorted backgrounds on Mac OS X 10.7+." + echo " I will convert it to 72 DPI for you." + + _DMG_BACKGROUND_TMP="${DMG_BACKGROUND_IMG%.*}"_dpifix."${DMG_BACKGROUND_IMG##*.}" + + sips -s dpiWidth 72 -s dpiHeight 72 ${DMG_BACKGROUND_IMG} --out ${_DMG_BACKGROUND_TMP} + + DMG_BACKGROUND_IMG="${_DMG_BACKGROUND_TMP}" + fi +fi + +# clear out any old data +rm -rf "${STAGING_DIR}" "${DMG_TMP}" "${DMG_FINAL}" + +# copy over the stuff we want in the final disk image to our staging dir +mkdir -p "${STAGING_DIR}" +cp -rpf "${APP_DIR}/${APP_NAME}.app" "${STAGING_DIR}" +# ... cp anything else you want in the DMG - documentation, etc. + +pushd "${STAGING_DIR}" + +# remove symbol table from the executable +echo "Stripping ${APP_EXE}..." +strip -u -r "${APP_EXE}" + +# compress the executable if we have upx in PATH +# UPX: http://upx.sourceforge.net/ +if hash upx 2>/dev/null; then + echo "Compressing (UPX) ${APP_EXE}..." + upx -9 "${APP_EXE}" +fi + +# ... perform any other stripping/compressing of libs and executables + +popd + +# figure out how big our DMG needs to be +# assumes our contents are at least 1M! +SIZE=`du -sh "${STAGING_DIR}" | sed 's/\([0-9\.]*\)M\(.*\)/\1/'` +SIZE=`echo "${SIZE} + 1.0" | bc | awk '{print int($1+0.5)}'` + +if [ $? -ne 0 ]; then + echo "Error: Cannot compute size of staging dir" + exit +fi + +# create the temp DMG file +hdiutil create -srcfolder "${STAGING_DIR}" -volname "${VOL_NAME}" -fs HFS+ \ + -fsargs "-c c=64,a=16,e=16" -format UDRW -size ${SIZE}M "${DMG_TMP}" + +echo "Created DMG: ${DMG_TMP}" + +# mount it and save the device +DEVICE=$(hdiutil attach -readwrite -noverify "${DMG_TMP}" | \ + egrep '^/dev/' | sed 1q | awk '{print $1}') + +sleep 2 + +# add a link to the Applications dir +echo "Add link to /Applications" +pushd /Volumes/"${VOL_NAME}" +ln -s /Applications +popd + +# add a background image +if [ -z "$DMG_BACKGROUND_IMG" ]; then + mkdir /Volumes/"${VOL_NAME}"/.background + cp "${DMG_BACKGROUND_IMG}" /Volumes/"${VOL_NAME}"/.background/ + + # tell the Finder to resize the window, set the background, + # change the icon size, place the icons in the right position, etc. + echo ' + tell application "Finder" + tell disk "'${VOL_NAME}'" + open + set current view of container window to icon view + set toolbar visible of container window to false + set statusbar visible of container window to false + set the bounds of container window to {400, 100, 920, 440} + set viewOptions to the icon view options of container window + set arrangement of viewOptions to not arranged + set icon size of viewOptions to 72 + set background picture of viewOptions to file ".background:'${DMG_BACKGROUND_IMG}'" + set position of item "'${APP_NAME}'.app" of container window to {160, 205} + set position of item "Applications" of container window to {360, 205} + close + open + update without registering applications + delay 2 + end tell + end tell + ' | osascript +else + mkdir /Volumes/"${VOL_NAME}"/.background + cp "${DMG_BACKGROUND_IMG}" /Volumes/"${VOL_NAME}"/.background/ + + # tell the Finder to resize the window, set the background, + # change the icon size, place the icons in the right position, etc. + echo ' + tell application "Finder" + tell disk "'${VOL_NAME}'" + open + set current view of container window to icon view + set toolbar visible of container window to false + set statusbar visible of container window to false + set the bounds of container window to {400, 100, 920, 440} + set viewOptions to the icon view options of container window + set arrangement of viewOptions to not arranged + set icon size of viewOptions to 72 + set position of item "'${APP_NAME}'.app" of container window to {160, 205} + set position of item "Applications" of container window to {360, 205} + close + open + update without registering applications + delay 2 + end tell + end tell + ' | osascript + +fi +sync + +# unmount it +hdiutil detach "${DEVICE}" + +# now make the final image a compressed disk image +echo "Creating compressed image" +hdiutil convert "${DMG_TMP}" -format UDZO -imagekey zlib-level=9 -o "${DMG_FINAL}" + +# clean up +rm -rf "${DMG_TMP}" +rm -rf "${STAGING_DIR}" + +echo 'Done.' + +exit \ No newline at end of file