root/trunk/EDIT4/ED4_block.cxx

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