source: trunk/EDIT4/ED4_block.cxx

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