source: tags/arb-6.0/EDIT4/ED4_block.cxx

Last change on this file was 12267, checked in by westram, 11 years ago
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 39.1 KB
Line 
1#include <arbdbt.h>
2#include <aw_awars.hxx>
3#include <aw_msg.hxx>
4#include <aw_root.hxx>
5#include <aw_select.hxx>
6
7#include <fast_aligner.hxx>
8
9#include "ed4_awars.hxx"
10#include "ed4_class.hxx"
11#include "ed4_tools.hxx"
12#include "ed4_block.hxx"
13#include "ed4_edit_string.hxx"
14#include "ed4_list.hxx"
15
16#include <climits>
17#include <cctype>
18#include <map>
19#include <awt_sel_boxes.hxx>
20
21using namespace std;
22
23// --------------------------------------------------------------------------------
24
25class ED4_block : virtual Noncopyable {
26    // stores columnrange of selected region
27    // (linerange is stored in EDIT4 marks)
28    ED4_blocktype type;
29    bool          columnBlockUsed;
30    PosRange      range;
31
32public:
33    ED4_block()
34        : type(ED4_BT_NOBLOCK),
35          columnBlockUsed(false)
36    {}
37
38    ED4_blocktype get_type() const { return type; }
39    void set_type(ED4_blocktype bt);
40    void toggle_type();
41    void autocorrect_type();
42
43    const PosRange& get_colblock_range() const {
44        e4_assert(type == ED4_BT_COLUMNBLOCK || type == ED4_BT_MODIFIED_COLUMNBLOCK); // otherwise range may be undefined
45        return range;
46    }
47    void set_range(const PosRange& new_range) { range = new_range; }
48
49    PosRange get_range_according_to_type() {
50        PosRange res;
51
52        switch (type) {
53            case ED4_BT_NOBLOCK:
54                res = PosRange::empty();
55                break;
56
57            case ED4_BT_COLUMNBLOCK:
58            case ED4_BT_MODIFIED_COLUMNBLOCK:
59                res = get_colblock_range();
60                break;
61
62            case ED4_BT_LINEBLOCK:
63                res = PosRange::whole();
64                break;
65        }
66
67        return res;
68    }
69};
70
71static ED4_block block;
72
73// --------------------------------------------------------------------------------
74
75class SeqPart {
76    const char *seq;
77
78    int offset;
79    int len; // of part
80
81    static char to_gap(char c) { return ADPP_IS_ALIGN_CHARACTER(c) ? c : 0; }
82
83public:
84    SeqPart(const char *seq_, int offset_, int len_)
85        : seq(seq_),
86          offset(offset_),
87          len(len_)
88    {}
89
90    const char *data() const { return seq+offset; }
91    int length() const { return len; }
92
93    // detect which gap to use at border of SeqPart:
94    char left_gap() const {
95        char gap                = to_gap(seq[offset]);
96        if (!gap && offset) gap = to_gap(seq[offset-1]);
97        if (!gap) gap           = '-';
98        return gap;
99    }
100    char right_gap() const {
101        char gap      = to_gap(seq[offset+len-1]);
102        if (!gap) gap = to_gap(seq[offset+len]);
103        if (!gap) gap = '-';
104        return gap;
105    }
106};
107
108// --------------------------------------------------------------------------------
109
110static void col_block_refresh_on_seq_term(ED4_sequence_terminal *seq_term) {
111    seq_term->request_refresh();
112
113    // @@@ code below is more than weird. why do sth with column-stat here ? why write probe match awars here ?
114    ED4_columnStat_terminal *colStatTerm = seq_term->corresponding_columnStat_terminal();
115    if (colStatTerm) {
116        const char *probe_match_pattern = colStatTerm->build_probe_match_string(block.get_colblock_range());
117        int len = strlen(probe_match_pattern);
118
119        if (len>=4) {
120            colStatTerm->request_refresh();
121
122            // automatically set probe-match awars to appropriate values:
123
124            ED4_ROOT->aw_root->awar(ED4_AWAR_PROBE_SEARCH_MAX_MISMATCHES)->write_int(0); // no mismatches
125            ED4_ROOT->aw_root->awar(ED4_AWAR_PROBE_SEARCH_AUTO_JUMP)->write_int(0); // disable auto jump
126            ED4_ROOT->aw_root->awar(AWAR_MAX_MISMATCHES)->write_int(0); // probe search w/o mismatches
127            ED4_ROOT->aw_root->awar(AWAR_ITARGET_STRING)->write_string(probe_match_pattern); // set probe search string
128        }
129    }
130}
131
132static void refresh_selected(bool refresh_name_terminals) {
133    ED4_selected_elem *listElem = ED4_ROOT->selected_objects->head();
134    while (listElem) {
135        ED4_species_name_terminal *name_term = listElem->elem()->object;
136        ED4_sequence_terminal     *seq_term  = name_term->corresponding_sequence_terminal();
137
138        if (refresh_name_terminals) name_term->request_refresh();
139        if (seq_term) col_block_refresh_on_seq_term(seq_term);
140
141        listElem = listElem->next();
142    }
143}
144
145// --------------------------------------------------------------------------------
146
147void ED4_block::set_type(ED4_blocktype bt) {
148    if (type != bt) {
149        type = bt;
150        refresh_selected(true);
151        if (type==ED4_BT_COLUMNBLOCK || type==ED4_BT_MODIFIED_COLUMNBLOCK) {
152            columnBlockUsed = true;
153        }
154    }
155}
156
157void ED4_block::toggle_type() {
158    switch (type) {
159        case ED4_BT_NOBLOCK: {
160            aw_message("No block selected.");
161            break;
162        }
163        case ED4_BT_LINEBLOCK: {
164            if (columnBlockUsed) {
165                set_type(ED4_BT_MODIFIED_COLUMNBLOCK);
166            }
167            else {
168                aw_message("No columnblock marked so far  - I can't guess the column range");
169            }
170            break;
171        }
172        case ED4_BT_MODIFIED_COLUMNBLOCK:
173        case ED4_BT_COLUMNBLOCK: {
174            set_type(ED4_BT_LINEBLOCK);
175            break;
176        }
177    }
178}
179
180void ED4_block::autocorrect_type() {
181    // this has to be called every time the selection has changed
182
183    if (ED4_ROOT->selected_objects->head()==0) { // no objects are selected
184        set_type(ED4_BT_NOBLOCK);
185    }
186    else {
187        switch (type) {
188            case ED4_BT_NOBLOCK: {
189                set_type(ED4_BT_LINEBLOCK);
190                break;
191            }
192            case ED4_BT_COLUMNBLOCK: {
193                set_type(ED4_BT_MODIFIED_COLUMNBLOCK);
194                break;
195            }
196            case ED4_BT_LINEBLOCK:
197            case ED4_BT_MODIFIED_COLUMNBLOCK: {
198                break;
199            }
200        }
201    }
202}
203
204// --------------------------------------------------------------------------------
205
206// if block_operation returns NULL => no changes will be made to database
207// otherwise the changed sequence(part) will be written to the database
208
209static GB_ERROR perform_block_operation_on_whole_sequence(const ED4_block_operator& block_operator, ED4_sequence_terminal *term) {
210    GBDATA *gbd = term->get_species_pointer();
211    GB_ERROR error = 0;
212
213    if (gbd) {
214        char *seq = GB_read_string(gbd);
215        int len = GB_read_string_count(gbd);
216
217        int   new_len;
218        char *new_seq = block_operator.operate(SeqPart(seq, 0, len), new_len);
219        error         = block_operator.get_error();
220
221        if (new_seq) {
222            if (new_len<len) {
223                memcpy(seq, new_seq, new_len);
224                char gap = ADPP_IS_ALIGN_CHARACTER(seq[len-1]) ? seq[len-1] : '.';
225                int l;
226                for (l=new_len; l<len; l++) {
227                    seq[l] = gap;
228                }
229                seq[l] = 0;
230            }
231            else if (new_len>len) {
232                for (int l=new_len-1; l>=len; l--) {
233                    if (!ADPP_IS_ALIGN_CHARACTER(new_seq[l])) {
234                        error = "Result of block-operation to large (not enough gaps at end of sequence data)";
235                        break;
236                    }
237                }
238
239                if (!error) { // there are enough gaps at end of sequence
240                    memcpy(seq, new_seq, len);
241                }
242            }
243            else {
244                memcpy(seq, new_seq, len);
245            }
246
247            if (!error) {
248                error = GB_write_string(gbd, seq);
249                if (!error) term->request_refresh();
250            }
251            free(new_seq);
252        }
253        free(seq);
254    }
255
256    return error;
257}
258
259// uses range_col1 till range_col2 as range
260static GB_ERROR perform_block_operation_on_part_of_sequence(const ED4_block_operator& block_operator, ED4_sequence_terminal *term) {
261    GBDATA *gbd = term->get_species_pointer();
262    GB_ERROR error = 0;
263
264    if (gbd) {
265        char *seq = GB_read_string(gbd);
266        int   len = GB_read_string_count(gbd);
267
268        ExplicitRange range(block.get_colblock_range(), len);
269
270        int   len_part     = range.size();
271        char *seq_part     = seq+range.start();
272        int   new_len;
273        char *new_seq_part = block_operator.operate(SeqPart(seq, range.start(), len_part), new_len);
274        error              = block_operator.get_error();
275
276        if (new_seq_part) {
277            if (new_len<len_part) {
278                memcpy(seq_part, new_seq_part, new_len);
279                char gap = '-';
280                if (seq_part[len_part-1] == '.' || seq_part[len_part] == '.') gap = '.';
281
282                for (int l=new_len; l<len_part; l++) {
283                    seq_part[l] = gap;
284                }
285            }
286            else if (new_len>len_part) {
287                for (int l=new_len-1; l>=len_part; l--) {
288                    if (!ADPP_IS_ALIGN_CHARACTER(new_seq_part[l])) {
289                        error = "Result of block-operation to large (not enough gaps at end of marked columnblock)";
290                        break;
291                    }
292                }
293
294                if (!error) { // there are enough gaps at end of sequence
295                    memcpy(seq_part, new_seq_part, len_part);
296                }
297            }
298            else {
299                memcpy(seq_part, new_seq_part, len_part);
300            }
301            delete new_seq_part;
302
303            if (!error) {
304                error = GB_write_as_string(gbd, seq);
305                if (!error) term->request_refresh();
306            }
307        }
308
309        delete seq;
310    }
311
312    return error;
313}
314
315static void ED4_with_whole_block(const ED4_block_operator& block_operator) {
316    GB_ERROR error = GB_begin_transaction(GLOBAL_gb_main);
317
318    typedef map<ED4_window*, int> CursorPositions;
319    CursorPositions at_base;
320
321    for (ED4_window *win = ED4_ROOT->first_window; win; win = win->next) {
322        ED4_cursor& cursor = win->cursor;
323        if (cursor.owner_of_cursor) at_base[win] = cursor.get_base_position();
324    }
325
326
327    switch (block.get_type()) {
328        case ED4_BT_NOBLOCK: {
329            aw_message("No block marked -- use right mouse button");
330            break;
331        }
332        case ED4_BT_LINEBLOCK:
333        case ED4_BT_MODIFIED_COLUMNBLOCK:
334        case ED4_BT_COLUMNBLOCK: {
335            ED4_selected_elem *listElem = ED4_ROOT->selected_objects->head();
336            while (listElem && !error) {
337                ED4_species_name_terminal *nameTerm = listElem->elem()->object;
338                ED4_sequence_terminal     *seqTerm  = nameTerm->corresponding_sequence_terminal();
339
340                error = block.get_type() == ED4_BT_LINEBLOCK
341                    ? perform_block_operation_on_whole_sequence(block_operator, seqTerm)
342                    : perform_block_operation_on_part_of_sequence(block_operator, seqTerm);
343
344                listElem = listElem->next();
345            }
346            break;
347        }
348        default: {
349            error = "Illegal blocktype";
350            break;
351        }
352    }
353
354    if (error) error = GBS_global_string("[In block operation] %s", error);
355    GB_end_transaction_show_error(GLOBAL_gb_main, error, aw_message);
356
357    if (!error) {
358        for (CursorPositions::iterator ab = at_base.begin(); ab != at_base.end(); ++ab) {
359            ED4_window *win = ab->first;
360            win->cursor.jump_base_pos(ab->second, ED4_JUMP_KEEP_VISIBLE); // restore cursor at same base
361        }
362    }
363}
364
365bool ED4_get_selected_range(ED4_terminal *term, PosRange& range) { // @@@ function will get useless, when multi-column-blocks are possible
366    if (block.get_type() == ED4_BT_NOBLOCK) return false;
367    if (!term->containing_species_manager()->is_selected()) return false;
368
369    range = block.get_range_according_to_type();
370    return true;
371}
372
373ED4_blocktype ED4_getBlocktype() { return block.get_type(); }
374void ED4_setBlocktype(ED4_blocktype bt) { block.set_type(bt); }
375void ED4_toggle_block_type() { block.toggle_type(); }
376void ED4_correctBlocktypeAfterSelection() { block.autocorrect_type(); }
377
378static void select_and_update(ED4_sequence_terminal *term1, ED4_sequence_terminal *term2, ED4_index pos1, ED4_index pos2, int initial_call) {
379    static ED4_sequence_terminal *last_term1, *last_term2;
380    static PosRange               last_range;
381
382    if (!term1) return; // e.g. when 1st click was into consensus
383
384    if (pos1>pos2) {
385        block.set_range(PosRange(pos2, pos1));
386    }
387    else {
388        block.set_range(PosRange(pos1, pos2));
389    }
390
391    if (block.get_type()==ED4_BT_MODIFIED_COLUMNBLOCK) {
392        refresh_selected(false);
393    }
394    else {
395        { // ensure term1 is the upper terminal
396            AW_pos dummy, y1, y2;
397
398            term1->calc_world_coords(&dummy, &y1);
399            term2->calc_world_coords(&dummy, &y2);
400
401            if (y1>y2) {
402                ED4_sequence_terminal *t = term1; term1 = term2; term2 = t;
403                AW_pos y = y1; y1 = y2; y2 = y;
404            }
405        }
406
407        int do_above = 1; // we have to update terminals between last_term1 and term1
408        int do_below = 1; // we have to update terminals between term2 and last_term2
409
410        ED4_terminal *term          = term1;
411        int           xRangeChanged = block.get_colblock_range() != last_range;
412
413        while (term) {
414            if (term->is_sequence_terminal()) {
415                ED4_sequence_terminal *seq_term = term->to_sequence_terminal();
416
417                if (seq_term==last_term1) {
418                    do_above = 0;
419                }
420                if (seq_term==last_term2) {
421                    do_below = 0;
422                }
423
424                ED4_species_name_terminal *name_term = seq_term->corresponding_species_name_terminal();
425                ED4_species_manager *species_man = name_term->containing_species_manager();
426                if (species_man->is_selected()) { // already selected
427                    if (xRangeChanged) {
428                        col_block_refresh_on_seq_term(seq_term);
429                    }
430                }
431                else { // select it
432                    if (!species_man->is_consensus_manager()) {
433                        ED4_ROOT->add_to_selected(name_term);
434                    }
435                }
436            }
437            if (term==term2) {
438                break;
439            }
440            term = term->get_next_terminal();
441        }
442
443        if (!initial_call) {
444            if (do_below) {
445                while (term) {
446                    if (term->is_species_name_terminal()) {
447                        ED4_species_manager *species_man = term->containing_species_manager();
448                        if (species_man->is_selected() && !species_man->is_consensus_manager()) {
449                            ED4_ROOT->remove_from_selected(term->to_species_name_terminal());
450                        }
451                    }
452                    if (term==last_term2) break;
453                    term = term->get_next_terminal();
454                }
455            }
456
457            if (do_above) {
458                term = last_term1->corresponding_species_name_terminal();
459                while (term && term!=term1) {
460                    if (term->is_species_name_terminal()) {
461                        ED4_species_manager *species_man = term->containing_species_manager();
462                        if (species_man->is_selected() && !species_man->is_consensus_manager()) {
463                            ED4_ROOT->remove_from_selected(term->to_species_name_terminal());
464                        }
465                    }
466                    term = term->get_next_terminal();
467                }
468            }
469        }
470    }
471
472    last_term1 = term1;
473    last_term2 = term2;
474    last_range = block.get_colblock_range();
475}
476
477void ED4_setColumnblockCorner(AW_event *event, ED4_sequence_terminal *seq_term) {
478    static ED4_sequence_terminal *fix_term = 0;
479    static ED4_index fix_pos = 0;
480
481    ED4_index seq_pos;
482    {
483        AW_pos termw_x, termw_y;
484        seq_term->calc_world_coords(&termw_x, &termw_y);
485
486        ED4_index scr_pos = ED4_ROOT->pixel2pos(event->x - termw_x);
487        ED4_remap *remap = ED4_ROOT->root_group_man->remap();
488        seq_pos = remap->screen_to_sequence(scr_pos);
489    }
490
491    switch (event->type) {
492        case AW_Mouse_Press: {
493            if (block.get_type() == ED4_BT_NOBLOCK) { // initial columnblock
494                if (!seq_term->is_consensus_terminal()) {
495                    block.set_type(ED4_BT_COLUMNBLOCK);
496
497                    fix_term = seq_term;
498                    fix_pos = seq_pos;
499
500                    select_and_update(fix_term, seq_term, fix_pos, seq_pos, 1);
501                }
502            }
503            else if (block.get_type()==ED4_BT_LINEBLOCK) { // change lineblock to columnblock
504                block.set_type(ED4_BT_MODIFIED_COLUMNBLOCK);
505
506                fix_term = seq_term;
507                if (seq_pos<(MAXSEQUENCECHARACTERLENGTH/2)) { // in first half of sequence
508                    fix_pos = MAXSEQUENCECHARACTERLENGTH;
509                }
510                else {
511                    fix_pos = 0;
512                }
513
514                select_and_update(fix_term, seq_term, fix_pos, seq_pos, 1);
515            }
516            else { // expand columnblock (search nearest corner/border -> fix opposite corner/border)
517                e4_assert(block.get_type()==ED4_BT_COLUMNBLOCK || block.get_type()==ED4_BT_MODIFIED_COLUMNBLOCK);
518
519                ED4_selected_elem *listElem = ED4_ROOT->selected_objects->head();
520                e4_assert(listElem);
521
522                if (block.get_type()==ED4_BT_COLUMNBLOCK) {
523                    AW_pos min_term_y = LONG_MAX;
524                    AW_pos max_term_y = LONG_MIN;
525                    ED4_species_name_terminal *min_term = 0;
526                    ED4_species_name_terminal *max_term = 0;
527                    AW_pos xpos, ypos;
528
529                    while (listElem) {
530                        ED4_species_name_terminal *name_term = listElem->elem()->object;
531                        name_term->calc_world_coords(&xpos, &ypos);
532
533                        if (ypos<min_term_y) {
534                            min_term_y = ypos;
535                            min_term = name_term;
536                        }
537                        if (ypos>max_term_y) {
538                            max_term_y = ypos;
539                            max_term = name_term;
540                        }
541
542                        listElem = listElem->next();
543                    }
544
545                    seq_term->calc_world_coords(&xpos, &ypos);
546                    ED4_species_name_terminal *fix_name_term;
547                    if (fabs(ypos-min_term_y)<fabs(ypos-max_term_y)) { // seq_term is closer to min_term
548                        fix_name_term = max_term; // select max_term as fixed corner
549                    }
550                    else {
551                        fix_name_term = min_term;
552                    }
553                    e4_assert(fix_name_term);
554                    fix_term = fix_name_term->corresponding_sequence_terminal();
555                }
556
557                AW_screen_area area_rect;
558                {
559                    AW_pos ex = event->x;
560                    AW_pos ey = event->y;
561                    current_ed4w()->world_to_win_coords(&ex, &ey);
562
563                    if (ED4_ROOT->get_area_rectangle(&area_rect, ex, ey)!=ED4_R_OK) {
564                        e4_assert(0);
565                        break;
566                    }
567                }
568
569
570                const ED4_remap *rm = ED4_ROOT->root_group_man->remap();
571
572                PosRange screen_range = rm->clip_screen_range(seq_term->calc_interval_displayed_in_rectangle(&area_rect));
573                int      scr_pos      = rm->sequence_to_screen(seq_pos);
574
575                PosRange block_screen_range = rm->sequence_to_screen(block.get_colblock_range());
576                PosRange block_visible_part = intersection(screen_range, block_screen_range);
577               
578                if (block_visible_part.is_empty()) {
579                    if (scr_pos>block_screen_range.end()) {
580                        fix_pos = block.get_colblock_range().start();
581                    }
582                    else {
583                        e4_assert(scr_pos<block_screen_range.start());
584                        fix_pos = block.get_colblock_range().end();
585                    }
586                }
587                else {
588                    int dist_left  = abs(scr_pos-block_visible_part.start());
589                    int dist_right = abs(scr_pos-block_visible_part.end());
590
591                    if (dist_left < dist_right) {   // click nearer to left border of visible part of block
592                        fix_pos = block.get_colblock_range().end(); // keep right block-border
593                    }
594                    else {
595                        fix_pos = block.get_colblock_range().start();
596                    }
597                }
598
599                select_and_update(fix_term, seq_term, fix_pos, seq_pos, 0);
600            }
601            break;
602        }
603        case AW_Mouse_Drag: {
604            select_and_update(fix_term, seq_term, fix_pos, seq_pos, 0);
605            break;
606        }
607        case AW_Mouse_Release: {
608            select_and_update(fix_term, seq_term, fix_pos, seq_pos, 0);
609            break;
610        }
611        default: {
612            e4_assert(0);
613            break;
614        }
615    }
616}
617
618// --------------------------------------------------------------------------------
619//      Replace
620
621inline bool matchesUsingWildcard(GB_CSTR s1, GB_CSTR s2, int len) {
622    // s2 may contain '?' as wildcard
623    int cmp = 0;
624
625    for (int i = 0; i<len && !cmp; ++i) {
626        cmp = int(s1[i])-int(s2[i]);
627        if (cmp && s2[i] == '?') cmp = 0;
628    }
629
630    return !cmp;
631}
632
633class replace_op : public ED4_block_operator { // derived from Noncopyable
634    const char *oldString;
635    const char *newString;
636   
637    int olen;
638    int nlen;
639
640public:
641    replace_op(const char *oldString_, const char *newString_)
642        : oldString(oldString_),
643          newString(newString_)
644    {
645        olen = strlen(oldString);
646        nlen = strlen(newString);
647    }
648
649    char* operate(const SeqPart& part, int& new_len) const OVERRIDE {
650        int maxlen;
651        int len   = part.length();
652        int max_o = len-olen;
653
654        if (nlen<=olen) {
655            maxlen = len;
656        }
657        else {
658            maxlen = (len/olen+1)*nlen;
659        }
660
661        char *new_seq  = (char*)GB_calloc(maxlen+1, sizeof(*new_seq));
662        int   replaced = 0;
663        int   o        = 0;
664        int   n        = 0;
665
666        const char *sequence = part.data();
667        while (o<len) {
668            if (o <= max_o && matchesUsingWildcard(sequence+o, oldString, olen)) {
669                memcpy(new_seq+n, newString, nlen);
670                n += nlen;
671                o += olen;
672                replaced++;
673            }
674            else {
675                new_seq[n++] = sequence[o++];
676            }
677        }
678        new_seq[n] = 0;
679
680        if (replaced) {
681            new_len = n;
682        }
683        else {
684            freenull(new_seq);
685        }
686
687        return new_seq;
688    }
689};
690
691static void replace_in_block(AW_window*) {
692    AW_root *awr = ED4_ROOT->aw_root;
693    ED4_with_whole_block(
694        replace_op(awr->awar(ED4_AWAR_REP_SEARCH_PATTERN)->read_char_pntr(),
695                   awr->awar(ED4_AWAR_REP_REPLACE_PATTERN)->read_char_pntr()));
696}
697
698AW_window *ED4_create_replace_window(AW_root *root) {
699    AW_window_simple *aws = new AW_window_simple;
700
701    aws->init(root, "REPLACE", "Search & Replace");
702    aws->load_xfig("edit4/replace.fig");
703
704    aws->at("close");
705    aws->callback((AW_CB0)AW_POPDOWN);
706    aws->create_button("CLOSE", "Close", "C");
707
708    aws->at("help");
709    aws->callback(makeHelpCallback("e4_replace.hlp"));
710    aws->create_button("HELP", "Help", "H");
711
712    aws->at("spattern");
713    aws->create_input_field(ED4_AWAR_REP_SEARCH_PATTERN, 30);
714
715    aws->at("rpattern");
716    aws->create_input_field(ED4_AWAR_REP_REPLACE_PATTERN, 30);
717
718    aws->at("go");
719    aws->callback(replace_in_block);
720    aws->create_button("GO", "Go", "G");
721
722    return aws;
723}
724
725// --------------------------------------------------------------------------------
726//      Other block operations
727
728inline char *dont_return_unchanged(char *result, int& new_len, const SeqPart& part) {
729    if (result) {
730        if (new_len == part.length()) {
731            if (memcmp(result, part.data(), new_len) == 0) {
732                freenull(result);
733            }
734        }
735    }
736    return result;
737}
738
739class case_op : public ED4_block_operator {
740    bool to_upper;
741public:
742    case_op(bool to_upper_) : to_upper(to_upper_) {}
743
744    char *operate(const SeqPart& part, int& new_len) const OVERRIDE {
745        int         len     = part.length();
746        const char *seq     = part.data();
747        char       *new_seq = (char*)GB_calloc(len+1, sizeof(*new_seq));
748
749        if (to_upper) {
750            for (int i=0; i<len; i++) new_seq[i] = toupper(seq[i]);
751        }
752        else {
753            for (int i=0; i<len; i++) new_seq[i] = tolower(seq[i]);
754        }
755       
756        new_len = len;
757        return dont_return_unchanged(new_seq, new_len, part);
758    }
759};
760
761class revcomp_op : public ED4_block_operator {
762    bool reverse;
763    bool complement;
764    char T_or_U;
765
766public:
767    revcomp_op(GB_alignment_type aliType, bool reverse_, bool complement_)
768        : reverse(reverse_),
769          complement(complement_),
770          T_or_U(0)
771    {
772        if (complement) {
773            error = GBT_determine_T_or_U(aliType, &T_or_U, reverse ? "reverse-complement" : "complement");
774        }
775    }
776
777    char *operate(const SeqPart& part, int& new_len) const OVERRIDE {
778        char *result = NULL;
779        if (!error) {
780            int len = part.length();
781            if (complement) {
782                result = GBT_complementNucSequence(part.data(), len, T_or_U);
783                if (reverse) freeset(result, GBT_reverseNucSequence(result, len));
784            }
785            else if (reverse) result = GBT_reverseNucSequence(part.data(), len);
786
787            new_len = len;
788        }
789        return dont_return_unchanged(result, new_len, part);
790    }
791
792};
793
794class unalign_op : public ED4_block_operator {
795    int direction;
796public:
797    unalign_op(int direction_) : direction(direction_) {}
798
799    char *operate(const SeqPart& part, int& new_len) const OVERRIDE {
800        int         len    = part.length();
801        const char *seq    = part.data();
802        char       *result = (char*)GB_calloc(len+1, sizeof(*result));
803
804        int o = 0;
805        int n = 0;
806
807        while (o<len) {
808            if (!ADPP_IS_ALIGN_CHARACTER(seq[o])) result[n++] = seq[o];
809            o++;
810        }
811
812        if (n<len) { // (move and) dot rest
813            int gapcount = len-n;
814            switch (direction) {
815                case 1: // rightwards
816                    memmove(result+gapcount, result, n);
817                    memset(result, part.left_gap(), gapcount);
818                    break;
819                case 0: { // center
820                    int leftgaps  = gapcount/2;
821                    int rightgaps = gapcount-leftgaps;
822
823                    memmove(result+leftgaps, result, n);
824                    memset(result, part.left_gap(), leftgaps);
825                    memset(result+leftgaps+n, part.right_gap(), rightgaps);
826                   
827                    break;
828                }
829                case -1: // leftwards
830                    memset(result+n, part.right_gap(), gapcount);
831                    break;
832            }
833        }
834
835        new_len = len;
836        return dont_return_unchanged(result, new_len, part);
837    }
838
839
840};
841
842class shift_op : public ED4_block_operator {
843    int direction;
844
845    char *shift_left_sequence(const SeqPart& part, int& new_len) const {
846        char       *result = 0;
847        const char *seq    = part.data();
848
849        if (!ADPP_IS_ALIGN_CHARACTER(seq[0])) {
850            error = "Need a gap at block start for shifting left";
851        }
852        else {
853            int len       = part.length();
854            result        = (char*)GB_calloc(len+1, sizeof(*result));
855            new_len       = len;
856            result[len-1] = part.right_gap();
857            memcpy(result, seq+1, len-1);
858        }
859        return result;
860    }
861
862    char *shift_right_sequence(const SeqPart& part, int& new_len) const {
863        char       *result = 0;
864        const char *seq    = part.data();
865        int         len    = part.length();
866
867        if (!ADPP_IS_ALIGN_CHARACTER(seq[len-1])) {
868            error = "Need a gap at block end for shifting right";
869        }
870        else {
871            result    = (char*)GB_calloc(len+1, sizeof(*result));
872            new_len   = len;
873            result[0] = part.left_gap();
874            memcpy(result+1, seq, len-1);
875        }
876        return result;
877    }
878
879   
880public:
881    shift_op(int direction_) : direction(direction_) {}
882
883    char *operate(const SeqPart& part, int& new_len) const OVERRIDE {
884        char *result = direction<0
885            ? shift_left_sequence(part, new_len)
886            : shift_right_sequence(part, new_len);
887        return dont_return_unchanged(result, new_len, part);
888    }
889};
890
891void ED4_perform_block_operation(ED4_blockoperation_type operationType) {
892    switch (operationType) {
893        case ED4_BO_UPPER_CASE: ED4_with_whole_block(case_op(true));  break;
894        case ED4_BO_LOWER_CASE: ED4_with_whole_block(case_op(false)); break;
895
896        case ED4_BO_REVERSE:            ED4_with_whole_block(revcomp_op(ED4_ROOT->alignment_type, true,  false)); break;
897        case ED4_BO_COMPLEMENT:         ED4_with_whole_block(revcomp_op(ED4_ROOT->alignment_type, false, true));  break;
898        case ED4_BO_REVERSE_COMPLEMENT: ED4_with_whole_block(revcomp_op(ED4_ROOT->alignment_type, true,  true));  break;
899
900        case ED4_BO_UNALIGN_LEFT:   ED4_with_whole_block(unalign_op(-1)); break;
901        case ED4_BO_UNALIGN_CENTER: ED4_with_whole_block(unalign_op(0));  break;
902        case ED4_BO_UNALIGN_RIGHT:  ED4_with_whole_block(unalign_op(1));  break;
903
904        case ED4_BO_SHIFT_LEFT:  ED4_with_whole_block(shift_op(-1)); break;
905        case ED4_BO_SHIFT_RIGHT: ED4_with_whole_block(shift_op(1));  break;
906
907        default: {
908            e4_assert(0);
909            break;
910        }
911    }
912}
913
914// --------------------------------------
915//      modify range of selected sai
916
917#define AWAR_MOD_SAI_SCRIPT "modsai/script"
918
919static void modsai_cb(AW_window *aww) {
920    ED4_MostRecentWinContext context;
921
922    ED4_cursor *cursor = &current_cursor();
923    GB_ERROR    error  = NULL;
924
925    if (!cursor->in_SAI_terminal()) {
926        error = "Please select the SAI you like to modify";
927    }
928    else if (block.get_type() == ED4_BT_NOBLOCK) {
929        error = "Please select the range where the SAI shall be modified";
930    }
931    else {
932        AW_root    *aw_root = aww->get_root();
933        char       *script  = aw_root->awar(AWAR_MOD_SAI_SCRIPT)->read_string();
934        const char *sainame = aw_root->awar(AWAR_SAI_NAME)->read_char_pntr();
935
936        GB_transaction ta(GLOBAL_gb_main);
937        GBDATA *gb_sai = GBT_find_SAI(GLOBAL_gb_main, sainame);
938
939        if (!gb_sai) {
940            error = GB_have_error()
941                ? GB_await_error()
942                : GBS_global_string("Failed to find SAI '%s'", sainame);
943        }
944        else {
945            GBDATA *gb_data = GBT_read_sequence(gb_sai, ED4_ROOT->alignment_name);
946
947            if (!gb_data) error = GB_await_error();
948            else {
949                char *seq = GB_read_string(gb_data);
950                int   len = GB_read_string_count(gb_data);
951
952                ExplicitRange  range(block.get_range_according_to_type(), len);
953                char          *part = range.dup_corresponding_part(seq, len);
954
955                char *result       = GB_command_interpreter(GLOBAL_gb_main, part, script, gb_sai, NULL);
956                if (!result) error = GB_await_error();
957                else {
958                    int reslen   = strlen(result);
959                    if (reslen>range.size()) {
960                        error = GBS_global_string("Cannot insert modified range (result too long; %i>%i)", reslen, range.size());
961                    }
962                    else {
963                        memcpy(seq+range.start(), result, reslen);
964                        int rest = range.size()-reslen;
965                        if (rest>0) memset(seq+range.start()+reslen, '-', rest);
966
967                        error = GB_write_string(gb_data, seq);
968                    }
969                    free(result);
970                }
971
972                free(part);
973                free(seq);
974            }
975        }
976        free(script);
977        error = ta.close(error);
978    }
979
980    aw_message_if(error);
981}
982
983AW_window *ED4_create_modsai_window(AW_root *root) {
984    root->awar_string(AWAR_MOD_SAI_SCRIPT)->write_string("");
985
986    AW_window_simple *aws = new AW_window_simple;
987    aws->init(root, "modsai", "Modify SAI range");
988    aws->load_xfig("edit4/modsai.fig");
989
990    aws->at("close");
991    aws->callback((AW_CB0)AW_POPDOWN);
992    aws->create_button("CLOSE", "Close", "C");
993
994    aws->at("help");
995    aws->callback(makeHelpCallback("e4_modsai.hlp"));
996    aws->create_button("HELP", "Help", "H");
997   
998    aws->at("script");
999    aws->create_input_field(AWAR_MOD_SAI_SCRIPT);
1000
1001    aws->at("go");
1002    aws->callback(modsai_cb);
1003    aws->create_button("go", "GO");
1004
1005    aws->at("box");
1006    AW_selection_list *sellist = aws->create_selection_list(AWAR_MOD_SAI_SCRIPT, false);
1007    GB_ERROR error;
1008    {
1009        StorableSelectionList storable_sellist(TypedSelectionList("sellst", sellist, "SRT/ACI scripts", "srt_aci"));
1010        error = storable_sellist.load(GB_path_in_ARBLIB("sellists/mod_sequence*.sellst"), false);
1011    }
1012    aw_message_if(error);
1013   
1014    return aws;
1015}
1016
1017
1018// --------------------------------------------------------------------------------
1019
1020#ifdef UNIT_TESTS
1021#ifndef TEST_UNIT_H
1022#include <test_unit.h>
1023#endif
1024
1025static arb_test::match_expectation blockop_expected_io(const ED4_block_operator& blockop, const char *oversized_input, const char *expected_result, const char *part_of_error) {
1026    int      whole_len = strlen(oversized_input);
1027    SeqPart  part(oversized_input, 1, whole_len-2);
1028    int      new_len   = 0;
1029    char    *result    = blockop.operate(part, new_len);
1030
1031    using namespace arb_test;
1032
1033    expectation_group expectations;
1034    expectations.add(part_of_error
1035                     ? that(blockop.get_error()).does_contain(part_of_error)
1036                     : that(blockop.get_error()).is_equal_to_NULL());
1037    expectations.add(that(result).is_equal_to(expected_result));
1038    if (expected_result) expectations.add(that(new_len).is_equal_to(whole_len-2));
1039
1040    free(result);
1041
1042    return all().ofgroup(expectations);
1043}
1044
1045#define TEST_EXPECT_BLOCKOP_PERFORMS(oversized_input,blockop,expected)         TEST_EXPECTATION(blockop_expected_io(blockop, oversized_input, expected, NULL))
1046#define TEST_EXPECT_BLOCKOP_PERFORMS__BROKEN(oversized_input,blockop,expected) TEST_EXPECTATION__BROKEN(blockop_expected_io(blockop, oversized_input, expected, NULL))
1047#define TEST_EXPECT_BLOCKOP_ERRORHAS(oversized_input,blockop,expected)         TEST_EXPECTATION(blockop_expected_io(blockop, oversized_input, NULL, expected))
1048#define TEST_EXPECT_BLOCKOP_ERRORHAS__BROKEN(oversized_input,blockop,expected) TEST_EXPECTATION__BROKEN(blockop_expected_io(blockop, oversized_input, NULL, expected))
1049
1050void TEST_block_operators() {
1051    ED4_init_is_align_character("-.");
1052
1053    // Note: make sure tests perform an identity block operation at least once for each operator
1054   
1055    // replace_op
1056    TEST_EXPECT_BLOCKOP_PERFORMS("-A-C--", replace_op("-",  "."),  "A.C.");
1057    TEST_EXPECT_BLOCKOP_PERFORMS("-A-C--", replace_op("?",  "."),  "....");
1058    TEST_EXPECT_BLOCKOP_PERFORMS("AACAG-", replace_op("AC", "CA"), "CAAG");
1059    TEST_EXPECT_BLOCKOP_PERFORMS("-ACAG-", replace_op("A?", "Ax"), "AxAx");
1060
1061    TEST_EXPECT_BLOCKOP_PERFORMS("GACAG-", replace_op("GA", "AG"), NULL);   // unchanged
1062    TEST_EXPECT_BLOCKOP_PERFORMS("GAGAGA", replace_op("GA", "AG"), "AAGG");
1063    TEST_EXPECT_BLOCKOP_PERFORMS("GACAGA", replace_op("GA", "AG"), NULL);
1064    TEST_EXPECT_BLOCKOP_PERFORMS("AGAGAG", replace_op("GA", "AG"), "AGAG");
1065
1066    // case_op
1067    TEST_EXPECT_BLOCKOP_PERFORMS("-AcGuT-", case_op(true), "ACGUT");
1068    TEST_EXPECT_BLOCKOP_PERFORMS("-AcGuT-", case_op(false), "acgut");
1069    TEST_EXPECT_BLOCKOP_PERFORMS("-acgut-", case_op(false), NULL);
1070    TEST_EXPECT_BLOCKOP_PERFORMS("-------", case_op(false), NULL);
1071
1072    // revcomp_op
1073    TEST_EXPECT_BLOCKOP_PERFORMS("-Ac-GuT-", revcomp_op(GB_AT_RNA, false, false), NULL);     // noop
1074    TEST_EXPECT_BLOCKOP_PERFORMS("-Ac-GuT-", revcomp_op(GB_AT_RNA, true,  false), "TuG-cA");
1075    TEST_EXPECT_BLOCKOP_PERFORMS("-Ac-GuT-", revcomp_op(GB_AT_RNA, false, true),  "Ug-CaA");
1076    TEST_EXPECT_BLOCKOP_PERFORMS("-Ac-GuT-", revcomp_op(GB_AT_RNA, true,  true),  "AaC-gU");
1077    TEST_EXPECT_BLOCKOP_PERFORMS("-Ac-GuT-", revcomp_op(GB_AT_DNA, true,  true),  "AaC-gT");
1078    TEST_EXPECT_BLOCKOP_PERFORMS("-AcGCgT-", revcomp_op(GB_AT_DNA, true,  true),  NULL);
1079    TEST_EXPECT_BLOCKOP_PERFORMS("--------", revcomp_op(GB_AT_DNA, true,  true),  NULL);
1080
1081    TEST_EXPECT_BLOCKOP_PERFORMS("-AR-DQF-", revcomp_op(GB_AT_AA, false, false), NULL); // noop
1082    TEST_EXPECT_BLOCKOP_PERFORMS("-AR-DQF-", revcomp_op(GB_AT_AA, true,  false), "FQD-RA");
1083    TEST_EXPECT_BLOCKOP_ERRORHAS("-AR-DQF-", revcomp_op(GB_AT_AA, false, true),  "complement not available");
1084    TEST_EXPECT_BLOCKOP_ERRORHAS("-AR-DQF-", revcomp_op(GB_AT_AA, true,  true),  "reverse-complement not available");
1085
1086    // unalign_op
1087    TEST_EXPECT_BLOCKOP_PERFORMS("-A-c-G--T-", unalign_op(-1), "AcGT----");
1088    TEST_EXPECT_BLOCKOP_PERFORMS("-A-c-G-T-T", unalign_op(-1), "AcGT----");
1089    TEST_EXPECT_BLOCKOP_PERFORMS("-A-c-G--TT", unalign_op(-1), "AcGT----");
1090    TEST_EXPECT_BLOCKOP_PERFORMS("-A-c-G--T.", unalign_op(-1), "AcGT....");
1091    TEST_EXPECT_BLOCKOP_PERFORMS("-A-c-G-T.T", unalign_op(-1), "AcGT....");
1092
1093    TEST_EXPECT_BLOCKOP_PERFORMS("A-Ac-G--T-", unalign_op(+1), "----AcGT");
1094    TEST_EXPECT_BLOCKOP_PERFORMS("-A-c-G--T-", unalign_op(+1), "----AcGT");
1095    TEST_EXPECT_BLOCKOP_PERFORMS("A.Ac-G--T-", unalign_op(+1), "....AcGT");
1096    TEST_EXPECT_BLOCKOP_PERFORMS(".A-c-G--T-", unalign_op(+1), "....AcGT");
1097    TEST_EXPECT_BLOCKOP_PERFORMS("AA-c-G--T-", unalign_op(+1), "----AcGT");
1098
1099    TEST_EXPECT_BLOCKOP_PERFORMS("AA-c-G--TT", unalign_op(0), "--AcGT--");
1100    TEST_EXPECT_BLOCKOP_PERFORMS(".A-c-G--T-", unalign_op(0), "..AcGT--");
1101    TEST_EXPECT_BLOCKOP_PERFORMS(".A-c-G--T.", unalign_op(0), "..AcGT..");
1102    TEST_EXPECT_BLOCKOP_PERFORMS("-A-c-G--T.", unalign_op(0), "--AcGT..");
1103    TEST_EXPECT_BLOCKOP_PERFORMS("-A-c-Gc-T.", unalign_op(0), "-AcGcT..");
1104
1105    TEST_EXPECT_BLOCKOP_PERFORMS("-AcGcT.",  unalign_op(0), NULL);
1106    TEST_EXPECT_BLOCKOP_PERFORMS("-AcGcT..", unalign_op(0), NULL);
1107    TEST_EXPECT_BLOCKOP_PERFORMS("--AcGcT.", unalign_op(0), "AcGcT.");
1108
1109    TEST_EXPECT_BLOCKOP_PERFORMS("-ACGT-", unalign_op(-1), NULL);
1110    TEST_EXPECT_BLOCKOP_PERFORMS("-ACGT-", unalign_op(+1), NULL);
1111    TEST_EXPECT_BLOCKOP_PERFORMS("------", unalign_op(+1), NULL);
1112
1113    // shift_op
1114    TEST_EXPECT_BLOCKOP_PERFORMS("-A-C--", shift_op(+1), "-A-C"); // take gap outside region
1115    TEST_EXPECT_BLOCKOP_PERFORMS(".A-C--", shift_op(+1), ".A-C"); // -"-
1116    TEST_EXPECT_BLOCKOP_PERFORMS("-.AC--", shift_op(+1), "..AC"); // take gap inside region
1117
1118    TEST_EXPECT_BLOCKOP_PERFORMS("--A-C-", shift_op(-1), "A-C-"); // same for other direction
1119    TEST_EXPECT_BLOCKOP_PERFORMS("--A-C.", shift_op(-1), "A-C.");
1120    TEST_EXPECT_BLOCKOP_PERFORMS("--AC..", shift_op(-1), "AC..");
1121    TEST_EXPECT_BLOCKOP_PERFORMS("------", shift_op(-1), NULL);
1122
1123    TEST_EXPECT_BLOCKOP_PERFORMS("G-TTAC", shift_op(-1), "TTA-"); // no gap reachable
1124
1125    TEST_EXPECT_BLOCKOP_ERRORHAS("GATTAC", shift_op(-1), "Need a gap at block");  // no space
1126    TEST_EXPECT_BLOCKOP_ERRORHAS("GATTAC", shift_op(+1), "Need a gap at block");  // no space
1127}
1128
1129#endif // UNIT_TESTS
1130
1131// --------------------------------------------------------------------------------
1132
Note: See TracBrowser for help on using the repository browser.