Adjust features when cutting and pasting
[jalview.git] / src / jalview / commands / EditCommand.java
1 /*\r
2  * Jalview - A Sequence Alignment Editor and Viewer\r
3  * Copyright (C) 2006 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle\r
4  *\r
5  * This program is free software; you can redistribute it and/or\r
6  * modify it under the terms of the GNU General Public License\r
7  * as published by the Free Software Foundation; either version 2\r
8  * of the License, or (at your option) any later version.\r
9  *\r
10  * This program is distributed in the hope that it will be useful,\r
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13  * GNU General Public License for more details.\r
14  *\r
15  * You should have received a copy of the GNU General Public License\r
16  * along with this program; if not, write to the Free Software\r
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA\r
18  */\r
19 package jalview.commands;\r
20 \r
21 import jalview.datamodel.*;\r
22 \r
23 import java.util.Hashtable;\r
24 \r
25 /**\r
26  *\r
27  * <p>Title: EditCommmand</p>\r
28  *\r
29  * <p>Description: Essential information for performing\r
30  * undo and redo for cut/paste insert/delete gap\r
31  * which can be stored in the HistoryList </p>\r
32  *\r
33  * <p>Copyright: Copyright (c) 2006</p>\r
34  *\r
35  * <p>Company: Dundee University</p>\r
36  *\r
37  * @author not attributable\r
38  * @version 1.0\r
39  */\r
40 public class EditCommand implements CommandI\r
41 {\r
42   public static final int INSERT_GAP = 0;\r
43   public static final int DELETE_GAP = 1;\r
44   public static final int CUT = 2;\r
45   public static final int PASTE = 3;\r
46 \r
47   Edit[] edits;\r
48 \r
49   String description;\r
50 \r
51   public EditCommand()\r
52   {}\r
53 \r
54   public EditCommand(String description)\r
55   {\r
56     this.description = description;\r
57   }\r
58 \r
59   public EditCommand( String description,\r
60                       int command,\r
61                       SequenceI[] seqs,\r
62                       int position,\r
63                       int number,\r
64                       AlignmentI al)\r
65    {\r
66      this.description = description;\r
67      if ( command==CUT || command==PASTE)\r
68      {\r
69        edits = new Edit[]{new Edit(command, seqs, position, number, al)};\r
70      }\r
71 \r
72      performEdit(0);\r
73   }\r
74 \r
75 \r
76   final public String getDescription()\r
77   {\r
78     return description;\r
79   }\r
80 \r
81   public int getSize()\r
82   {\r
83     return edits==null?0:edits.length;\r
84   }\r
85 \r
86   final public AlignmentI getAlignment()\r
87   {\r
88     return edits[0].al;\r
89   }\r
90 \r
91 \r
92   final public void appendEdit(int command,\r
93                          SequenceI[] seqs,\r
94                          int position,\r
95                          int number,\r
96                          AlignmentI al,\r
97                          boolean performEdit)\r
98   {\r
99     Edit edit = new Edit(command, seqs, position, number, al.getGapCharacter());\r
100     if(al.getHeight()==seqs.length)\r
101     {\r
102       edit.al = al;\r
103       edit.fullAlignmentHeight = true;\r
104     }\r
105 \r
106     if (edits != null)\r
107     {\r
108       Edit[] temp = new Edit[edits.length + 1];\r
109       System.arraycopy(edits, 0, temp, 0, edits.length);\r
110       edits = temp;\r
111       edits[edits.length - 1] = edit;\r
112     }\r
113     else\r
114       edits = new Edit[] {  edit  };\r
115 \r
116     if (performEdit)\r
117       performEdit(edits.length - 1);\r
118   }\r
119 \r
120   final void performEdit(int commandIndex)\r
121   {\r
122     int eSize = edits.length;\r
123     for (int e = commandIndex; e < eSize; e++)\r
124     {\r
125       if (edits[e].command==INSERT_GAP)\r
126       {\r
127         insertGap(edits[e]);\r
128       }\r
129       else if (edits[e].command==DELETE_GAP)\r
130       {\r
131         deleteGap(edits[e]);\r
132       }\r
133       else if(edits[e].command==CUT)\r
134       {\r
135         cut(edits[e]);\r
136       }\r
137       else if(edits[e].command==PASTE)\r
138       {\r
139         paste(edits[e]);\r
140       }\r
141     }\r
142   }\r
143 \r
144   final public void doCommand()\r
145   {\r
146     performEdit(0);\r
147   }\r
148 \r
149   final public void undoCommand()\r
150   {\r
151     int e = 0, eSize = edits.length;\r
152     for (e = eSize-1; e > -1; e--)\r
153     {\r
154       if (edits[e].command==INSERT_GAP)\r
155       {\r
156         deleteGap(edits[e]);\r
157       }\r
158       else if (edits[e].command==DELETE_GAP)\r
159       {\r
160         insertGap(edits[e]);\r
161       }\r
162       else if (edits[e].command==CUT)\r
163       {\r
164         paste(edits[e]);\r
165       }\r
166       else if (edits[e].command==PASTE)\r
167       {\r
168         cut(edits[e]);\r
169       }\r
170     }\r
171   }\r
172 \r
173   final void insertGap(Edit command)\r
174   {\r
175     for(int s=0; s<command.seqs.length; s++)\r
176     {\r
177         command.seqs[s].insertCharAt(command.position,\r
178                                      command.number,\r
179                                      command.gapChar);\r
180     }\r
181 \r
182     adjustAnnotations(command, true);\r
183   }\r
184 \r
185   final void deleteGap(Edit command)\r
186   {\r
187     for (int s = 0; s < command.seqs.length; s++)\r
188     {\r
189       command.seqs[s].deleteChars(command.position, command.position+command.number);\r
190     }\r
191 \r
192     adjustAnnotations(command, false);\r
193   }\r
194 \r
195   void cut(Edit command)\r
196   {\r
197     command.string = new char [command.seqs.length][];\r
198 \r
199     for(int i=0; i<command.seqs.length; i++)\r
200     {\r
201       if(command.seqs[i].getLength()>command.position)\r
202       {\r
203         command.string[i] = command.seqs[i].getSequence(command.position,\r
204             command.position + command.number);\r
205 \r
206         if(command.seqs[i].getDatasetSequence()!=null\r
207         || command.seqs[i].getSequenceFeatures()!=null)\r
208         {\r
209           for (int s = command.position; s < command.position + command.number; s++)\r
210           {\r
211             if (jalview.schemes.ResidueProperties\r
212                 .aaIndex[command.seqs[i].getCharAt(s)] != 23)\r
213             {\r
214               adjustFeatures(command, i,\r
215                              command.seqs[i].findPosition(command.position),\r
216                              command.seqs[i].findPosition(command.position +\r
217                                                           command.number),\r
218                              false);\r
219               break;\r
220             }\r
221           }\r
222         }\r
223         command.seqs[i].deleteChars(command.position,\r
224                                     command.position + command.number);\r
225       }\r
226 \r
227       if(command.seqs[i].getLength()<1)\r
228       {\r
229         command.al.deleteSequence(command.seqs[i]);\r
230       }\r
231     }\r
232 \r
233     adjustAnnotations(command, false);\r
234   }\r
235 \r
236   void paste(Edit command)\r
237   {\r
238     StringBuffer tmp;\r
239     boolean newDSNeeded;\r
240     int start=0, end=0;\r
241 \r
242     for(int i=0; i<command.seqs.length; i++)\r
243     {\r
244       newDSNeeded = false;\r
245       if(command.seqs[i].getLength()<1)\r
246       {\r
247         // ie this sequence was deleted, we need to\r
248         // read it to the alignment\r
249         if (command.alIndex[i] < command.al.getHeight())\r
250           command.al.getSequences().insertElementAt(command.seqs[i],\r
251               command.alIndex[i]);\r
252         else\r
253           command.al.addSequence(command.seqs[i]);\r
254       }\r
255       tmp = new StringBuffer();\r
256       tmp.append(command.seqs[i].getSequence());\r
257 \r
258       if(command.string!=null && command.string[i]!=null)\r
259       {\r
260         if(command.position>=tmp.length())\r
261         {\r
262           //This occurs if padding is on, and residues\r
263           //are removed from end of alignment\r
264           int length = command.position-tmp.length();\r
265           while (length > 0)\r
266           {\r
267             tmp.append(command.gapChar);\r
268             length--;\r
269           }\r
270         }\r
271         tmp.insert(command.position, command.string[i]);\r
272 \r
273         for (int s = 0; s < command.string[i].length; s++)\r
274         {\r
275           if (jalview.schemes.ResidueProperties.aaIndex[command.string[i][s]] != 23)\r
276           {\r
277             newDSNeeded = true;\r
278             start = command.seqs[i].findPosition(command.position);\r
279             end = command.seqs[i].findPosition(command.position+command.number);\r
280             break;\r
281           }\r
282         }\r
283         command.string[i] = null;\r
284       }\r
285 \r
286 \r
287       command.seqs[i].setSequence(tmp.toString());\r
288 \r
289       if(newDSNeeded)\r
290       {\r
291         if (command.seqs[i].getDatasetSequence() != null)\r
292         {\r
293           Sequence ds = new Sequence(command.seqs[i].getName(),\r
294                                      jalview.analysis.AlignSeq.extractGaps(\r
295                                          jalview.util.Comparison.GapChars,\r
296                                          command.seqs[i].getSequenceAsString()\r
297                                      ),\r
298                                      command.seqs[i].getStart(),\r
299                                      command.seqs[i].getEnd());\r
300           ds.setDescription(command.seqs[i].getDescription());\r
301           command.seqs[i].setDatasetSequence(ds);\r
302         }\r
303 \r
304         adjustFeatures(command, i, start, end, true);\r
305       }\r
306     }\r
307 \r
308 \r
309     adjustAnnotations(command, true);\r
310 \r
311     command.string = null;\r
312   }\r
313 \r
314 \r
315   final void adjustAnnotations(Edit command, boolean insert)\r
316   {\r
317 \r
318     AlignmentAnnotation [] annotations = null;\r
319 \r
320     if (command.fullAlignmentHeight)\r
321     {\r
322       annotations = command.al.getAlignmentAnnotation();\r
323     }\r
324     else\r
325     {\r
326       int aSize = 0;\r
327       AlignmentAnnotation [] tmp;\r
328       for(int s=0; s<command.seqs.length; s++)\r
329       {\r
330         if(command.seqs[s].getAnnotation()==null)\r
331           continue;\r
332 \r
333         if (aSize == 0)\r
334             annotations = command.seqs[s].getAnnotation();\r
335         else\r
336         {\r
337           tmp = new AlignmentAnnotation\r
338               [aSize + command.seqs[s].getAnnotation().length];\r
339 \r
340           System.arraycopy(annotations,0,tmp,0,aSize);\r
341 \r
342           System.arraycopy(command.seqs[s].getAnnotation(),\r
343               0,tmp,aSize,command.seqs[s].getAnnotation().length);\r
344 \r
345           annotations = tmp;\r
346         }\r
347 \r
348 \r
349         aSize = annotations.length;\r
350       }\r
351     }\r
352 \r
353     if(annotations==null)\r
354       return;\r
355 \r
356 \r
357       if(!insert)\r
358         command.deletedAnnotations = new Hashtable();\r
359 \r
360       int aSize, tSize;\r
361       Annotation [] temp;\r
362       for (int a = 0; a < annotations.length; a++)\r
363       {\r
364         if(annotations[a].autoCalculated)\r
365         {\r
366           continue;\r
367         }\r
368 \r
369         aSize = annotations[a].annotations.length;\r
370         if(insert)\r
371           tSize = aSize + command.number;\r
372         else\r
373           tSize = aSize - command.number;\r
374 \r
375         temp = new Annotation[tSize];\r
376 \r
377         if(insert)\r
378         {\r
379           if(command.position < annotations[a].annotations.length)\r
380           {\r
381             System.arraycopy(annotations[a].annotations,\r
382                              0, temp, 0, command.position);\r
383 \r
384             if (command.deletedAnnotations != null\r
385                 &&\r
386                 command.deletedAnnotations.containsKey(annotations[a].annotationId))\r
387             {\r
388               Annotation[] restore = (Annotation[])\r
389                   command.deletedAnnotations.get(annotations[a].annotationId);\r
390 \r
391               System.arraycopy(restore,\r
392                                0,\r
393                                temp,\r
394                                command.position,\r
395                                command.number);\r
396 \r
397             }\r
398 \r
399             System.arraycopy(annotations[a].annotations,\r
400                              command.position, temp,\r
401                              command.position + command.number,\r
402                              aSize - command.position);\r
403           }\r
404           else\r
405             temp = annotations[a].annotations;\r
406         }\r
407         else\r
408         {\r
409           if(command.position < annotations[a].annotations.length)\r
410           {\r
411             System.arraycopy(annotations[a].annotations,\r
412                              0, temp, 0, command.position);\r
413 \r
414             Annotation[] deleted = new Annotation[command.number];\r
415             System.arraycopy(annotations[a].annotations,\r
416                              command.position, deleted, 0, command.number);\r
417 \r
418             command.deletedAnnotations.put(annotations[a].annotationId,\r
419                                            deleted);\r
420 \r
421             System.arraycopy(annotations[a].annotations,\r
422                              command.position + command.number,\r
423                              temp, command.position,\r
424                              aSize - command.position - command.number);\r
425           }\r
426           else\r
427             temp = annotations[a].annotations;\r
428         }\r
429 \r
430         annotations[a].annotations = temp;\r
431      }\r
432   }\r
433 \r
434   final void adjustFeatures(Edit command, int index, int i, int j, boolean insert)\r
435   {\r
436     SequenceI seq = command.seqs[index];\r
437     SequenceI sequence = seq.getDatasetSequence();\r
438     if(sequence==null)\r
439       sequence = seq;\r
440 \r
441     if(insert)\r
442     {\r
443       if (command.editedFeatures != null\r
444           && command.editedFeatures.containsKey(seq))\r
445         sequence.setSequenceFeatures(\r
446             (SequenceFeature[]) command.editedFeatures.get(seq)\r
447             );\r
448 \r
449       return;\r
450     }\r
451 \r
452 \r
453     SequenceFeature [] sf = sequence.getSequenceFeatures();\r
454 \r
455 \r
456     if(sf==null)\r
457     {\r
458       return;\r
459     }\r
460 \r
461     SequenceFeature [] oldsf = new SequenceFeature[sf.length];\r
462 \r
463     int cSize = j - i;\r
464 \r
465     for (int s = 0; s < sf.length; s++)\r
466     {\r
467       SequenceFeature copy = new SequenceFeature(sf[s]);\r
468 \r
469       oldsf[s] = copy;\r
470 \r
471       if (sf[s].getEnd() < i)\r
472         continue;\r
473 \r
474       if (sf[s].getBegin() > j)\r
475       {\r
476         sf[s].setBegin(copy.getBegin() - cSize);\r
477         sf[s].setEnd(copy.getEnd() - cSize);\r
478         continue;\r
479       }\r
480 \r
481       if (sf[s].getBegin() >= i)\r
482         sf[s].setBegin(i);\r
483 \r
484       if (sf[s].getEnd() < j)\r
485         sf[s].setEnd(j - 1);\r
486 \r
487       sf[s].setEnd(sf[s].getEnd() - (cSize));\r
488 \r
489       if (sf[s].getBegin() > sf[s].getEnd())\r
490         sequence.deleteFeature(sf[s]);\r
491     }\r
492 \r
493     if (command.editedFeatures == null)\r
494       command.editedFeatures = new Hashtable();\r
495 \r
496     command.editedFeatures.put(seq, oldsf);\r
497 \r
498   }\r
499 \r
500 \r
501   class Edit\r
502   {\r
503     boolean fullAlignmentHeight = false;\r
504     Hashtable deletedAnnotations;\r
505     Hashtable editedFeatures;\r
506     AlignmentI al;\r
507     int command;\r
508     char [][] string;\r
509     SequenceI[] seqs;\r
510     int [] alIndex;\r
511     int position, number;\r
512     char gapChar;\r
513 \r
514     Edit(int command,\r
515          SequenceI[] seqs,\r
516          int position,\r
517          int number,\r
518          char gapChar)\r
519     {\r
520       this.command = command;\r
521       this.seqs = seqs;\r
522       this.position = position;\r
523       this.number = number;\r
524       this.gapChar = gapChar;\r
525     }\r
526 \r
527 \r
528     Edit(int command,\r
529          SequenceI[] seqs,\r
530          int position,\r
531          int number,\r
532          AlignmentI al)\r
533     {\r
534       this.gapChar = al.getGapCharacter();\r
535       this.command = command;\r
536       this.seqs = seqs;\r
537       this.position = position;\r
538       this.number = number;\r
539       this.al = al;\r
540 \r
541       alIndex = new int[seqs.length];\r
542       for(int i=0; i<seqs.length; i++)\r
543       {\r
544         alIndex[i] = al.findIndex(seqs[i]);\r
545       }\r
546 \r
547       fullAlignmentHeight = (al.getHeight()==seqs.length);\r
548     }\r
549   }\r
550 \r
551 }\r